Keep list of excluded moves in Engine Output header
[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         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5430                 DisplayNote("Yes!");
5431                 return;
5432         }
5433         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5434         *start = startPV; *end = index-1;
5435         return TRUE;
5436 }
5437
5438 char *
5439 PvToSAN (char *pv)
5440 {
5441         static char buf[10*MSG_SIZ];
5442         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5443         *buf = NULLCHAR;
5444         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5445         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5446         for(i = forwardMostMove; i<endPV; i++){
5447             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5448             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5449             k += strlen(buf+k);
5450         }
5451         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5452         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5453         endPV = savedEnd;
5454         return buf;
5455 }
5456
5457 Boolean
5458 LoadPV (int x, int y)
5459 { // called on right mouse click to load PV
5460   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5461   lastX = x; lastY = y;
5462   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5463   return TRUE;
5464 }
5465
5466 void
5467 UnLoadPV ()
5468 {
5469   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5470   if(endPV < 0) return;
5471   if(appData.autoCopyPV) CopyFENToClipboard();
5472   endPV = -1;
5473   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5474         Boolean saveAnimate = appData.animate;
5475         if(pushed) {
5476             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5477                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5478             } else storedGames--; // abandon shelved tail of original game
5479         }
5480         pushed = FALSE;
5481         forwardMostMove = currentMove;
5482         currentMove = oldFMM;
5483         appData.animate = FALSE;
5484         ToNrEvent(forwardMostMove);
5485         appData.animate = saveAnimate;
5486   }
5487   currentMove = forwardMostMove;
5488   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5489   ClearPremoveHighlights();
5490   DrawPosition(TRUE, boards[currentMove]);
5491 }
5492
5493 void
5494 MovePV (int x, int y, int h)
5495 { // step through PV based on mouse coordinates (called on mouse move)
5496   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5497
5498   // we must somehow check if right button is still down (might be released off board!)
5499   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5500   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5501   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5502   if(!step) return;
5503   lastX = x; lastY = y;
5504
5505   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5506   if(endPV < 0) return;
5507   if(y < margin) step = 1; else
5508   if(y > h - margin) step = -1;
5509   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5510   currentMove += step;
5511   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5512   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5513                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5514   DrawPosition(FALSE, boards[currentMove]);
5515 }
5516
5517
5518 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5519 // All positions will have equal probability, but the current method will not provide a unique
5520 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5521 #define DARK 1
5522 #define LITE 2
5523 #define ANY 3
5524
5525 int squaresLeft[4];
5526 int piecesLeft[(int)BlackPawn];
5527 int seed, nrOfShuffles;
5528
5529 void
5530 GetPositionNumber ()
5531 {       // sets global variable seed
5532         int i;
5533
5534         seed = appData.defaultFrcPosition;
5535         if(seed < 0) { // randomize based on time for negative FRC position numbers
5536                 for(i=0; i<50; i++) seed += random();
5537                 seed = random() ^ random() >> 8 ^ random() << 8;
5538                 if(seed<0) seed = -seed;
5539         }
5540 }
5541
5542 int
5543 put (Board board, int pieceType, int rank, int n, int shade)
5544 // put the piece on the (n-1)-th empty squares of the given shade
5545 {
5546         int i;
5547
5548         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5549                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5550                         board[rank][i] = (ChessSquare) pieceType;
5551                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5552                         squaresLeft[ANY]--;
5553                         piecesLeft[pieceType]--;
5554                         return i;
5555                 }
5556         }
5557         return -1;
5558 }
5559
5560
5561 void
5562 AddOnePiece (Board board, int pieceType, int rank, int shade)
5563 // calculate where the next piece goes, (any empty square), and put it there
5564 {
5565         int i;
5566
5567         i = seed % squaresLeft[shade];
5568         nrOfShuffles *= squaresLeft[shade];
5569         seed /= squaresLeft[shade];
5570         put(board, pieceType, rank, i, shade);
5571 }
5572
5573 void
5574 AddTwoPieces (Board board, int pieceType, int rank)
5575 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5576 {
5577         int i, n=squaresLeft[ANY], j=n-1, k;
5578
5579         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5580         i = seed % k;  // pick one
5581         nrOfShuffles *= k;
5582         seed /= k;
5583         while(i >= j) i -= j--;
5584         j = n - 1 - j; i += j;
5585         put(board, pieceType, rank, j, ANY);
5586         put(board, pieceType, rank, i, ANY);
5587 }
5588
5589 void
5590 SetUpShuffle (Board board, int number)
5591 {
5592         int i, p, first=1;
5593
5594         GetPositionNumber(); nrOfShuffles = 1;
5595
5596         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5597         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5598         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5599
5600         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5601
5602         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5603             p = (int) board[0][i];
5604             if(p < (int) BlackPawn) piecesLeft[p] ++;
5605             board[0][i] = EmptySquare;
5606         }
5607
5608         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5609             // shuffles restricted to allow normal castling put KRR first
5610             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5611                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5612             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5613                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5614             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5615                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5616             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5617                 put(board, WhiteRook, 0, 0, ANY);
5618             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5619         }
5620
5621         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5622             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5623             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5624                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5625                 while(piecesLeft[p] >= 2) {
5626                     AddOnePiece(board, p, 0, LITE);
5627                     AddOnePiece(board, p, 0, DARK);
5628                 }
5629                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5630             }
5631
5632         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5633             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5634             // but we leave King and Rooks for last, to possibly obey FRC restriction
5635             if(p == (int)WhiteRook) continue;
5636             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5637             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5638         }
5639
5640         // now everything is placed, except perhaps King (Unicorn) and Rooks
5641
5642         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5643             // Last King gets castling rights
5644             while(piecesLeft[(int)WhiteUnicorn]) {
5645                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5646                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5647             }
5648
5649             while(piecesLeft[(int)WhiteKing]) {
5650                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5651                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5652             }
5653
5654
5655         } else {
5656             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5657             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5658         }
5659
5660         // Only Rooks can be left; simply place them all
5661         while(piecesLeft[(int)WhiteRook]) {
5662                 i = put(board, WhiteRook, 0, 0, ANY);
5663                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5664                         if(first) {
5665                                 first=0;
5666                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5667                         }
5668                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5669                 }
5670         }
5671         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5672             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5673         }
5674
5675         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5676 }
5677
5678 int
5679 SetCharTable (char *table, const char * map)
5680 /* [HGM] moved here from winboard.c because of its general usefulness */
5681 /*       Basically a safe strcpy that uses the last character as King */
5682 {
5683     int result = FALSE; int NrPieces;
5684
5685     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5686                     && NrPieces >= 12 && !(NrPieces&1)) {
5687         int i; /* [HGM] Accept even length from 12 to 34 */
5688
5689         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5690         for( i=0; i<NrPieces/2-1; i++ ) {
5691             table[i] = map[i];
5692             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5693         }
5694         table[(int) WhiteKing]  = map[NrPieces/2-1];
5695         table[(int) BlackKing]  = map[NrPieces-1];
5696
5697         result = TRUE;
5698     }
5699
5700     return result;
5701 }
5702
5703 void
5704 Prelude (Board board)
5705 {       // [HGM] superchess: random selection of exo-pieces
5706         int i, j, k; ChessSquare p;
5707         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5708
5709         GetPositionNumber(); // use FRC position number
5710
5711         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5712             SetCharTable(pieceToChar, appData.pieceToCharTable);
5713             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5714                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5715         }
5716
5717         j = seed%4;                 seed /= 4;
5718         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5719         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5720         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5721         j = seed%3 + (seed%3 >= j); seed /= 3;
5722         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5723         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5724         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5725         j = seed%3;                 seed /= 3;
5726         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5727         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5728         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5729         j = seed%2 + (seed%2 >= j); seed /= 2;
5730         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5731         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5732         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5733         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5734         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5735         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5736         put(board, exoPieces[0],    0, 0, ANY);
5737         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5738 }
5739
5740 void
5741 InitPosition (int redraw)
5742 {
5743     ChessSquare (* pieces)[BOARD_FILES];
5744     int i, j, pawnRow, overrule,
5745     oldx = gameInfo.boardWidth,
5746     oldy = gameInfo.boardHeight,
5747     oldh = gameInfo.holdingsWidth;
5748     static int oldv;
5749
5750     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5751
5752     /* [AS] Initialize pv info list [HGM] and game status */
5753     {
5754         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5755             pvInfoList[i].depth = 0;
5756             boards[i][EP_STATUS] = EP_NONE;
5757             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5758         }
5759
5760         initialRulePlies = 0; /* 50-move counter start */
5761
5762         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5763         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5764     }
5765
5766
5767     /* [HGM] logic here is completely changed. In stead of full positions */
5768     /* the initialized data only consist of the two backranks. The switch */
5769     /* selects which one we will use, which is than copied to the Board   */
5770     /* initialPosition, which for the rest is initialized by Pawns and    */
5771     /* empty squares. This initial position is then copied to boards[0],  */
5772     /* possibly after shuffling, so that it remains available.            */
5773
5774     gameInfo.holdingsWidth = 0; /* default board sizes */
5775     gameInfo.boardWidth    = 8;
5776     gameInfo.boardHeight   = 8;
5777     gameInfo.holdingsSize  = 0;
5778     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5779     for(i=0; i<BOARD_FILES-2; i++)
5780       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5781     initialPosition[EP_STATUS] = EP_NONE;
5782     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5783     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5784          SetCharTable(pieceNickName, appData.pieceNickNames);
5785     else SetCharTable(pieceNickName, "............");
5786     pieces = FIDEArray;
5787
5788     switch (gameInfo.variant) {
5789     case VariantFischeRandom:
5790       shuffleOpenings = TRUE;
5791     default:
5792       break;
5793     case VariantShatranj:
5794       pieces = ShatranjArray;
5795       nrCastlingRights = 0;
5796       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5797       break;
5798     case VariantMakruk:
5799       pieces = makrukArray;
5800       nrCastlingRights = 0;
5801       startedFromSetupPosition = TRUE;
5802       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5803       break;
5804     case VariantTwoKings:
5805       pieces = twoKingsArray;
5806       break;
5807     case VariantGrand:
5808       pieces = GrandArray;
5809       nrCastlingRights = 0;
5810       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5811       gameInfo.boardWidth = 10;
5812       gameInfo.boardHeight = 10;
5813       gameInfo.holdingsSize = 7;
5814       break;
5815     case VariantCapaRandom:
5816       shuffleOpenings = TRUE;
5817     case VariantCapablanca:
5818       pieces = CapablancaArray;
5819       gameInfo.boardWidth = 10;
5820       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5821       break;
5822     case VariantGothic:
5823       pieces = GothicArray;
5824       gameInfo.boardWidth = 10;
5825       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5826       break;
5827     case VariantSChess:
5828       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5829       gameInfo.holdingsSize = 7;
5830       break;
5831     case VariantJanus:
5832       pieces = JanusArray;
5833       gameInfo.boardWidth = 10;
5834       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5835       nrCastlingRights = 6;
5836         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5837         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5838         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5839         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5840         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5841         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5842       break;
5843     case VariantFalcon:
5844       pieces = FalconArray;
5845       gameInfo.boardWidth = 10;
5846       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5847       break;
5848     case VariantXiangqi:
5849       pieces = XiangqiArray;
5850       gameInfo.boardWidth  = 9;
5851       gameInfo.boardHeight = 10;
5852       nrCastlingRights = 0;
5853       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5854       break;
5855     case VariantShogi:
5856       pieces = ShogiArray;
5857       gameInfo.boardWidth  = 9;
5858       gameInfo.boardHeight = 9;
5859       gameInfo.holdingsSize = 7;
5860       nrCastlingRights = 0;
5861       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5862       break;
5863     case VariantCourier:
5864       pieces = CourierArray;
5865       gameInfo.boardWidth  = 12;
5866       nrCastlingRights = 0;
5867       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5868       break;
5869     case VariantKnightmate:
5870       pieces = KnightmateArray;
5871       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5872       break;
5873     case VariantSpartan:
5874       pieces = SpartanArray;
5875       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5876       break;
5877     case VariantFairy:
5878       pieces = fairyArray;
5879       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5880       break;
5881     case VariantGreat:
5882       pieces = GreatArray;
5883       gameInfo.boardWidth = 10;
5884       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5885       gameInfo.holdingsSize = 8;
5886       break;
5887     case VariantSuper:
5888       pieces = FIDEArray;
5889       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5890       gameInfo.holdingsSize = 8;
5891       startedFromSetupPosition = TRUE;
5892       break;
5893     case VariantCrazyhouse:
5894     case VariantBughouse:
5895       pieces = FIDEArray;
5896       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5897       gameInfo.holdingsSize = 5;
5898       break;
5899     case VariantWildCastle:
5900       pieces = FIDEArray;
5901       /* !!?shuffle with kings guaranteed to be on d or e file */
5902       shuffleOpenings = 1;
5903       break;
5904     case VariantNoCastle:
5905       pieces = FIDEArray;
5906       nrCastlingRights = 0;
5907       /* !!?unconstrained back-rank shuffle */
5908       shuffleOpenings = 1;
5909       break;
5910     }
5911
5912     overrule = 0;
5913     if(appData.NrFiles >= 0) {
5914         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5915         gameInfo.boardWidth = appData.NrFiles;
5916     }
5917     if(appData.NrRanks >= 0) {
5918         gameInfo.boardHeight = appData.NrRanks;
5919     }
5920     if(appData.holdingsSize >= 0) {
5921         i = appData.holdingsSize;
5922         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5923         gameInfo.holdingsSize = i;
5924     }
5925     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5926     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5927         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5928
5929     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5930     if(pawnRow < 1) pawnRow = 1;
5931     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5932
5933     /* User pieceToChar list overrules defaults */
5934     if(appData.pieceToCharTable != NULL)
5935         SetCharTable(pieceToChar, appData.pieceToCharTable);
5936
5937     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5938
5939         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5940             s = (ChessSquare) 0; /* account holding counts in guard band */
5941         for( i=0; i<BOARD_HEIGHT; i++ )
5942             initialPosition[i][j] = s;
5943
5944         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5945         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5946         initialPosition[pawnRow][j] = WhitePawn;
5947         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5948         if(gameInfo.variant == VariantXiangqi) {
5949             if(j&1) {
5950                 initialPosition[pawnRow][j] =
5951                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5952                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5953                    initialPosition[2][j] = WhiteCannon;
5954                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5955                 }
5956             }
5957         }
5958         if(gameInfo.variant == VariantGrand) {
5959             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5960                initialPosition[0][j] = WhiteRook;
5961                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5962             }
5963         }
5964         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5965     }
5966     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5967
5968             j=BOARD_LEFT+1;
5969             initialPosition[1][j] = WhiteBishop;
5970             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5971             j=BOARD_RGHT-2;
5972             initialPosition[1][j] = WhiteRook;
5973             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5974     }
5975
5976     if( nrCastlingRights == -1) {
5977         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5978         /*       This sets default castling rights from none to normal corners   */
5979         /* Variants with other castling rights must set them themselves above    */
5980         nrCastlingRights = 6;
5981
5982         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5983         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5984         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5985         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5986         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5987         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5988      }
5989
5990      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5991      if(gameInfo.variant == VariantGreat) { // promotion commoners
5992         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5993         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5994         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5995         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5996      }
5997      if( gameInfo.variant == VariantSChess ) {
5998       initialPosition[1][0] = BlackMarshall;
5999       initialPosition[2][0] = BlackAngel;
6000       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6001       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6002       initialPosition[1][1] = initialPosition[2][1] = 
6003       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6004      }
6005   if (appData.debugMode) {
6006     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6007   }
6008     if(shuffleOpenings) {
6009         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6010         startedFromSetupPosition = TRUE;
6011     }
6012     if(startedFromPositionFile) {
6013       /* [HGM] loadPos: use PositionFile for every new game */
6014       CopyBoard(initialPosition, filePosition);
6015       for(i=0; i<nrCastlingRights; i++)
6016           initialRights[i] = filePosition[CASTLING][i];
6017       startedFromSetupPosition = TRUE;
6018     }
6019
6020     CopyBoard(boards[0], initialPosition);
6021
6022     if(oldx != gameInfo.boardWidth ||
6023        oldy != gameInfo.boardHeight ||
6024        oldv != gameInfo.variant ||
6025        oldh != gameInfo.holdingsWidth
6026                                          )
6027             InitDrawingSizes(-2 ,0);
6028
6029     oldv = gameInfo.variant;
6030     if (redraw)
6031       DrawPosition(TRUE, boards[currentMove]);
6032 }
6033
6034 void
6035 SendBoard (ChessProgramState *cps, int moveNum)
6036 {
6037     char message[MSG_SIZ];
6038
6039     if (cps->useSetboard) {
6040       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6041       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6042       SendToProgram(message, cps);
6043       free(fen);
6044
6045     } else {
6046       ChessSquare *bp;
6047       int i, j, left=0, right=BOARD_WIDTH;
6048       /* Kludge to set black to move, avoiding the troublesome and now
6049        * deprecated "black" command.
6050        */
6051       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6052         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6053
6054       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6055
6056       SendToProgram("edit\n", cps);
6057       SendToProgram("#\n", cps);
6058       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6059         bp = &boards[moveNum][i][left];
6060         for (j = left; j < right; j++, bp++) {
6061           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6062           if ((int) *bp < (int) BlackPawn) {
6063             if(j == BOARD_RGHT+1)
6064                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6065             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6066             if(message[0] == '+' || message[0] == '~') {
6067               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6068                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6069                         AAA + j, ONE + i);
6070             }
6071             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6072                 message[1] = BOARD_RGHT   - 1 - j + '1';
6073                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6074             }
6075             SendToProgram(message, cps);
6076           }
6077         }
6078       }
6079
6080       SendToProgram("c\n", cps);
6081       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6082         bp = &boards[moveNum][i][left];
6083         for (j = left; j < right; j++, bp++) {
6084           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6085           if (((int) *bp != (int) EmptySquare)
6086               && ((int) *bp >= (int) BlackPawn)) {
6087             if(j == BOARD_LEFT-2)
6088                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6089             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6090                     AAA + j, ONE + i);
6091             if(message[0] == '+' || message[0] == '~') {
6092               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6093                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6094                         AAA + j, ONE + i);
6095             }
6096             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6097                 message[1] = BOARD_RGHT   - 1 - j + '1';
6098                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6099             }
6100             SendToProgram(message, cps);
6101           }
6102         }
6103       }
6104
6105       SendToProgram(".\n", cps);
6106     }
6107     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6108 }
6109
6110 char exclusionHeader[MSG_SIZ];
6111 int exCnt, excludePtr, mappedMove = -1;
6112 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6113 static Exclusion excluTab[200];
6114 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6115
6116 void
6117 ClearMap ()
6118 {
6119     int j;
6120     mappedMove = -1;
6121     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = 0;
6122     safeStrCpy(exclusionHeader, "exclude: none best  tail                                          \n", MSG_SIZ);
6123     excludePtr = 24; exCnt = 0;
6124 }
6125
6126 void
6127 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, int incl)
6128 {
6129     char buf[2*MOVE_LEN], *p, c = incl ? '+' : '-';
6130     Exclusion *e = excluTab;
6131     int i;
6132     for(i=0; i<exCnt; i++)
6133         if(e[i].ff == fromX && e[i].fr == fromY &&
6134            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6135     if(i == exCnt) { // was not in exclude list; add it
6136         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6137         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6138             if(c != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6139             return; // abort
6140         }
6141         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6142         excludePtr++; e[i].mark = excludePtr++;
6143         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6144         exCnt++;
6145     }
6146     exclusionHeader[e[i].mark] = c;
6147 }
6148
6149 ChessSquare
6150 DefaultPromoChoice (int white)
6151 {
6152     ChessSquare result;
6153     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6154         result = WhiteFerz; // no choice
6155     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6156         result= WhiteKing; // in Suicide Q is the last thing we want
6157     else if(gameInfo.variant == VariantSpartan)
6158         result = white ? WhiteQueen : WhiteAngel;
6159     else result = WhiteQueen;
6160     if(!white) result = WHITE_TO_BLACK result;
6161     return result;
6162 }
6163
6164 static int autoQueen; // [HGM] oneclick
6165
6166 int
6167 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6168 {
6169     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6170     /* [HGM] add Shogi promotions */
6171     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6172     ChessSquare piece;
6173     ChessMove moveType;
6174     Boolean premove;
6175
6176     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6177     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6178
6179     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6180       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6181         return FALSE;
6182
6183     piece = boards[currentMove][fromY][fromX];
6184     if(gameInfo.variant == VariantShogi) {
6185         promotionZoneSize = BOARD_HEIGHT/3;
6186         highestPromotingPiece = (int)WhiteFerz;
6187     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6188         promotionZoneSize = 3;
6189     }
6190
6191     // Treat Lance as Pawn when it is not representing Amazon
6192     if(gameInfo.variant != VariantSuper) {
6193         if(piece == WhiteLance) piece = WhitePawn; else
6194         if(piece == BlackLance) piece = BlackPawn;
6195     }
6196
6197     // next weed out all moves that do not touch the promotion zone at all
6198     if((int)piece >= BlackPawn) {
6199         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6200              return FALSE;
6201         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6202     } else {
6203         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6204            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6205     }
6206
6207     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6208
6209     // weed out mandatory Shogi promotions
6210     if(gameInfo.variant == VariantShogi) {
6211         if(piece >= BlackPawn) {
6212             if(toY == 0 && piece == BlackPawn ||
6213                toY == 0 && piece == BlackQueen ||
6214                toY <= 1 && piece == BlackKnight) {
6215                 *promoChoice = '+';
6216                 return FALSE;
6217             }
6218         } else {
6219             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6220                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6221                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6222                 *promoChoice = '+';
6223                 return FALSE;
6224             }
6225         }
6226     }
6227
6228     // weed out obviously illegal Pawn moves
6229     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6230         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6231         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6232         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6233         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6234         // note we are not allowed to test for valid (non-)capture, due to premove
6235     }
6236
6237     // we either have a choice what to promote to, or (in Shogi) whether to promote
6238     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6239         *promoChoice = PieceToChar(BlackFerz);  // no choice
6240         return FALSE;
6241     }
6242     // no sense asking what we must promote to if it is going to explode...
6243     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6244         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6245         return FALSE;
6246     }
6247     // give caller the default choice even if we will not make it
6248     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6249     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6250     if(        sweepSelect && gameInfo.variant != VariantGreat
6251                            && gameInfo.variant != VariantGrand
6252                            && gameInfo.variant != VariantSuper) return FALSE;
6253     if(autoQueen) return FALSE; // predetermined
6254
6255     // suppress promotion popup on illegal moves that are not premoves
6256     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6257               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6258     if(appData.testLegality && !premove) {
6259         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6260                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6261         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6262             return FALSE;
6263     }
6264
6265     return TRUE;
6266 }
6267
6268 int
6269 InPalace (int row, int column)
6270 {   /* [HGM] for Xiangqi */
6271     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6272          column < (BOARD_WIDTH + 4)/2 &&
6273          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6274     return FALSE;
6275 }
6276
6277 int
6278 PieceForSquare (int x, int y)
6279 {
6280   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6281      return -1;
6282   else
6283      return boards[currentMove][y][x];
6284 }
6285
6286 int
6287 OKToStartUserMove (int x, int y)
6288 {
6289     ChessSquare from_piece;
6290     int white_piece;
6291
6292     if (matchMode) return FALSE;
6293     if (gameMode == EditPosition) return TRUE;
6294
6295     if (x >= 0 && y >= 0)
6296       from_piece = boards[currentMove][y][x];
6297     else
6298       from_piece = EmptySquare;
6299
6300     if (from_piece == EmptySquare) return FALSE;
6301
6302     white_piece = (int)from_piece >= (int)WhitePawn &&
6303       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6304
6305     switch (gameMode) {
6306       case AnalyzeFile:
6307       case TwoMachinesPlay:
6308       case EndOfGame:
6309         return FALSE;
6310
6311       case IcsObserving:
6312       case IcsIdle:
6313         return FALSE;
6314
6315       case MachinePlaysWhite:
6316       case IcsPlayingBlack:
6317         if (appData.zippyPlay) return FALSE;
6318         if (white_piece) {
6319             DisplayMoveError(_("You are playing Black"));
6320             return FALSE;
6321         }
6322         break;
6323
6324       case MachinePlaysBlack:
6325       case IcsPlayingWhite:
6326         if (appData.zippyPlay) return FALSE;
6327         if (!white_piece) {
6328             DisplayMoveError(_("You are playing White"));
6329             return FALSE;
6330         }
6331         break;
6332
6333       case PlayFromGameFile:
6334             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6335       case EditGame:
6336         if (!white_piece && WhiteOnMove(currentMove)) {
6337             DisplayMoveError(_("It is White's turn"));
6338             return FALSE;
6339         }
6340         if (white_piece && !WhiteOnMove(currentMove)) {
6341             DisplayMoveError(_("It is Black's turn"));
6342             return FALSE;
6343         }
6344         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6345             /* Editing correspondence game history */
6346             /* Could disallow this or prompt for confirmation */
6347             cmailOldMove = -1;
6348         }
6349         break;
6350
6351       case BeginningOfGame:
6352         if (appData.icsActive) return FALSE;
6353         if (!appData.noChessProgram) {
6354             if (!white_piece) {
6355                 DisplayMoveError(_("You are playing White"));
6356                 return FALSE;
6357             }
6358         }
6359         break;
6360
6361       case Training:
6362         if (!white_piece && WhiteOnMove(currentMove)) {
6363             DisplayMoveError(_("It is White's turn"));
6364             return FALSE;
6365         }
6366         if (white_piece && !WhiteOnMove(currentMove)) {
6367             DisplayMoveError(_("It is Black's turn"));
6368             return FALSE;
6369         }
6370         break;
6371
6372       default:
6373       case IcsExamining:
6374         break;
6375     }
6376     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6377         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6378         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6379         && gameMode != AnalyzeFile && gameMode != Training) {
6380         DisplayMoveError(_("Displayed position is not current"));
6381         return FALSE;
6382     }
6383     return TRUE;
6384 }
6385
6386 Boolean
6387 OnlyMove (int *x, int *y, Boolean captures) 
6388 {
6389     DisambiguateClosure cl;
6390     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6391     switch(gameMode) {
6392       case MachinePlaysBlack:
6393       case IcsPlayingWhite:
6394       case BeginningOfGame:
6395         if(!WhiteOnMove(currentMove)) return FALSE;
6396         break;
6397       case MachinePlaysWhite:
6398       case IcsPlayingBlack:
6399         if(WhiteOnMove(currentMove)) return FALSE;
6400         break;
6401       case EditGame:
6402         break;
6403       default:
6404         return FALSE;
6405     }
6406     cl.pieceIn = EmptySquare;
6407     cl.rfIn = *y;
6408     cl.ffIn = *x;
6409     cl.rtIn = -1;
6410     cl.ftIn = -1;
6411     cl.promoCharIn = NULLCHAR;
6412     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6413     if( cl.kind == NormalMove ||
6414         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6415         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6416         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6417       fromX = cl.ff;
6418       fromY = cl.rf;
6419       *x = cl.ft;
6420       *y = cl.rt;
6421       return TRUE;
6422     }
6423     if(cl.kind != ImpossibleMove) return FALSE;
6424     cl.pieceIn = EmptySquare;
6425     cl.rfIn = -1;
6426     cl.ffIn = -1;
6427     cl.rtIn = *y;
6428     cl.ftIn = *x;
6429     cl.promoCharIn = NULLCHAR;
6430     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6431     if( cl.kind == NormalMove ||
6432         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6433         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6434         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6435       fromX = cl.ff;
6436       fromY = cl.rf;
6437       *x = cl.ft;
6438       *y = cl.rt;
6439       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6440       return TRUE;
6441     }
6442     return FALSE;
6443 }
6444
6445 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6446 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6447 int lastLoadGameUseList = FALSE;
6448 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6449 ChessMove lastLoadGameStart = EndOfFile;
6450 int doubleClick;
6451
6452 void
6453 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6454 {
6455     ChessMove moveType;
6456     ChessSquare pdown, pup;
6457     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6458
6459
6460     /* Check if the user is playing in turn.  This is complicated because we
6461        let the user "pick up" a piece before it is his turn.  So the piece he
6462        tried to pick up may have been captured by the time he puts it down!
6463        Therefore we use the color the user is supposed to be playing in this
6464        test, not the color of the piece that is currently on the starting
6465        square---except in EditGame mode, where the user is playing both
6466        sides; fortunately there the capture race can't happen.  (It can
6467        now happen in IcsExamining mode, but that's just too bad.  The user
6468        will get a somewhat confusing message in that case.)
6469        */
6470
6471     switch (gameMode) {
6472       case AnalyzeFile:
6473       case TwoMachinesPlay:
6474       case EndOfGame:
6475       case IcsObserving:
6476       case IcsIdle:
6477         /* We switched into a game mode where moves are not accepted,
6478            perhaps while the mouse button was down. */
6479         return;
6480
6481       case MachinePlaysWhite:
6482         /* User is moving for Black */
6483         if (WhiteOnMove(currentMove)) {
6484             DisplayMoveError(_("It is White's turn"));
6485             return;
6486         }
6487         break;
6488
6489       case MachinePlaysBlack:
6490         /* User is moving for White */
6491         if (!WhiteOnMove(currentMove)) {
6492             DisplayMoveError(_("It is Black's turn"));
6493             return;
6494         }
6495         break;
6496
6497       case PlayFromGameFile:
6498             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6499       case EditGame:
6500       case IcsExamining:
6501       case BeginningOfGame:
6502       case AnalyzeMode:
6503       case Training:
6504         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6505         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6506             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6507             /* User is moving for Black */
6508             if (WhiteOnMove(currentMove)) {
6509                 DisplayMoveError(_("It is White's turn"));
6510                 return;
6511             }
6512         } else {
6513             /* User is moving for White */
6514             if (!WhiteOnMove(currentMove)) {
6515                 DisplayMoveError(_("It is Black's turn"));
6516                 return;
6517             }
6518         }
6519         break;
6520
6521       case IcsPlayingBlack:
6522         /* User is moving for Black */
6523         if (WhiteOnMove(currentMove)) {
6524             if (!appData.premove) {
6525                 DisplayMoveError(_("It is White's turn"));
6526             } else if (toX >= 0 && toY >= 0) {
6527                 premoveToX = toX;
6528                 premoveToY = toY;
6529                 premoveFromX = fromX;
6530                 premoveFromY = fromY;
6531                 premovePromoChar = promoChar;
6532                 gotPremove = 1;
6533                 if (appData.debugMode)
6534                     fprintf(debugFP, "Got premove: fromX %d,"
6535                             "fromY %d, toX %d, toY %d\n",
6536                             fromX, fromY, toX, toY);
6537             }
6538             return;
6539         }
6540         break;
6541
6542       case IcsPlayingWhite:
6543         /* User is moving for White */
6544         if (!WhiteOnMove(currentMove)) {
6545             if (!appData.premove) {
6546                 DisplayMoveError(_("It is Black's turn"));
6547             } else if (toX >= 0 && toY >= 0) {
6548                 premoveToX = toX;
6549                 premoveToY = toY;
6550                 premoveFromX = fromX;
6551                 premoveFromY = fromY;
6552                 premovePromoChar = promoChar;
6553                 gotPremove = 1;
6554                 if (appData.debugMode)
6555                     fprintf(debugFP, "Got premove: fromX %d,"
6556                             "fromY %d, toX %d, toY %d\n",
6557                             fromX, fromY, toX, toY);
6558             }
6559             return;
6560         }
6561         break;
6562
6563       default:
6564         break;
6565
6566       case EditPosition:
6567         /* EditPosition, empty square, or different color piece;
6568            click-click move is possible */
6569         if (toX == -2 || toY == -2) {
6570             boards[0][fromY][fromX] = EmptySquare;
6571             DrawPosition(FALSE, boards[currentMove]);
6572             return;
6573         } else if (toX >= 0 && toY >= 0) {
6574             boards[0][toY][toX] = boards[0][fromY][fromX];
6575             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6576                 if(boards[0][fromY][0] != EmptySquare) {
6577                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6578                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6579                 }
6580             } else
6581             if(fromX == BOARD_RGHT+1) {
6582                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6583                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6584                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6585                 }
6586             } else
6587             boards[0][fromY][fromX] = EmptySquare;
6588             DrawPosition(FALSE, boards[currentMove]);
6589             return;
6590         }
6591         return;
6592     }
6593
6594     if(doubleClick && (toX == -2 || toY == -2)) { // [HGM] exclude: off-board move means exclude all
6595         int i; // note that drop moves still have holdings coords as from-square at this point
6596         ChessMove moveType; char pc;
6597         for(i=0; i<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; i++) excludeMap[i] = -(toY == -2);
6598         mappedMove = currentMove;
6599         if(toY != -2) { SendToProgram("include all\n", &first); return; }
6600         SendToProgram("exclude all\n", &first);
6601         i = ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &pc);
6602         ff=fromX, rf=fromY, ft=toX, rt=toY, promoChar = pc; // make copy that will survive drop encoding
6603         if(!i) return; // kludge: continue with move changed to engine's last-reported best, so it gets included again.
6604     }
6605
6606     if(toX < 0 || toY < 0) return;
6607     pdown = boards[currentMove][fromY][fromX];
6608     pup = boards[currentMove][toY][toX];
6609
6610     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6611     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6612          if( pup != EmptySquare ) return;
6613          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6614            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6615                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6616            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6617            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6618            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6619            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6620          fromY = DROP_RANK;
6621     }
6622
6623     /* [HGM] always test for legality, to get promotion info */
6624     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6625                                          fromY, fromX, toY, toX, promoChar);
6626
6627     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6628
6629     /* [HGM] but possibly ignore an IllegalMove result */
6630     if (appData.testLegality) {
6631         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6632             DisplayMoveError(_("Illegal move"));
6633             return;
6634         }
6635     }
6636
6637     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6638         int i=(BOARD_FILES*rf+ff)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*rt+ft), j;
6639         char buf[MSG_SIZ];
6640         if(mappedMove != currentMove) ClearMap();
6641         j = i%8; i >>= 3;
6642         snprintf(buf, MSG_SIZ, "%sclude ", excludeMap[i] & 1<<j ? "in" : "ex");
6643         if(excludeMap[i] & 1<<j) ClearPremoveHighlights();
6644         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt);
6645         if(!promoChar) excludeMap[i] ^= 1<<j;
6646         mappedMove = currentMove;
6647         CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6648         SendToProgram(buf, &first);
6649         UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, buf[0] == 'i');
6650         return;
6651     }
6652
6653     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6654 }
6655
6656 /* Common tail of UserMoveEvent and DropMenuEvent */
6657 int
6658 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6659 {
6660     char *bookHit = 0;
6661
6662     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6663         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6664         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6665         if(WhiteOnMove(currentMove)) {
6666             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6667         } else {
6668             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6669         }
6670     }
6671
6672     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6673        move type in caller when we know the move is a legal promotion */
6674     if(moveType == NormalMove && promoChar)
6675         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6676
6677     /* [HGM] <popupFix> The following if has been moved here from
6678        UserMoveEvent(). Because it seemed to belong here (why not allow
6679        piece drops in training games?), and because it can only be
6680        performed after it is known to what we promote. */
6681     if (gameMode == Training) {
6682       /* compare the move played on the board to the next move in the
6683        * game. If they match, display the move and the opponent's response.
6684        * If they don't match, display an error message.
6685        */
6686       int saveAnimate;
6687       Board testBoard;
6688       CopyBoard(testBoard, boards[currentMove]);
6689       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6690
6691       if (CompareBoards(testBoard, boards[currentMove+1])) {
6692         ForwardInner(currentMove+1);
6693
6694         /* Autoplay the opponent's response.
6695          * if appData.animate was TRUE when Training mode was entered,
6696          * the response will be animated.
6697          */
6698         saveAnimate = appData.animate;
6699         appData.animate = animateTraining;
6700         ForwardInner(currentMove+1);
6701         appData.animate = saveAnimate;
6702
6703         /* check for the end of the game */
6704         if (currentMove >= forwardMostMove) {
6705           gameMode = PlayFromGameFile;
6706           ModeHighlight();
6707           SetTrainingModeOff();
6708           DisplayInformation(_("End of game"));
6709         }
6710       } else {
6711         DisplayError(_("Incorrect move"), 0);
6712       }
6713       return 1;
6714     }
6715
6716   /* Ok, now we know that the move is good, so we can kill
6717      the previous line in Analysis Mode */
6718   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6719                                 && currentMove < forwardMostMove) {
6720     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6721     else forwardMostMove = currentMove;
6722   }
6723
6724   ClearMap();
6725
6726   /* If we need the chess program but it's dead, restart it */
6727   ResurrectChessProgram();
6728
6729   /* A user move restarts a paused game*/
6730   if (pausing)
6731     PauseEvent();
6732
6733   thinkOutput[0] = NULLCHAR;
6734
6735   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6736
6737   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6738     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6739     return 1;
6740   }
6741
6742   if (gameMode == BeginningOfGame) {
6743     if (appData.noChessProgram) {
6744       gameMode = EditGame;
6745       SetGameInfo();
6746     } else {
6747       char buf[MSG_SIZ];
6748       gameMode = MachinePlaysBlack;
6749       StartClocks();
6750       SetGameInfo();
6751       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6752       DisplayTitle(buf);
6753       if (first.sendName) {
6754         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6755         SendToProgram(buf, &first);
6756       }
6757       StartClocks();
6758     }
6759     ModeHighlight();
6760   }
6761
6762   /* Relay move to ICS or chess engine */
6763   if (appData.icsActive) {
6764     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6765         gameMode == IcsExamining) {
6766       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6767         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6768         SendToICS("draw ");
6769         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6770       }
6771       // also send plain move, in case ICS does not understand atomic claims
6772       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6773       ics_user_moved = 1;
6774     }
6775   } else {
6776     if (first.sendTime && (gameMode == BeginningOfGame ||
6777                            gameMode == MachinePlaysWhite ||
6778                            gameMode == MachinePlaysBlack)) {
6779       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6780     }
6781     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6782          // [HGM] book: if program might be playing, let it use book
6783         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6784         first.maybeThinking = TRUE;
6785     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6786         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6787         SendBoard(&first, currentMove+1);
6788     } else SendMoveToProgram(forwardMostMove-1, &first);
6789     if (currentMove == cmailOldMove + 1) {
6790       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6791     }
6792   }
6793
6794   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6795
6796   switch (gameMode) {
6797   case EditGame:
6798     if(appData.testLegality)
6799     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6800     case MT_NONE:
6801     case MT_CHECK:
6802       break;
6803     case MT_CHECKMATE:
6804     case MT_STAINMATE:
6805       if (WhiteOnMove(currentMove)) {
6806         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6807       } else {
6808         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6809       }
6810       break;
6811     case MT_STALEMATE:
6812       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6813       break;
6814     }
6815     break;
6816
6817   case MachinePlaysBlack:
6818   case MachinePlaysWhite:
6819     /* disable certain menu options while machine is thinking */
6820     SetMachineThinkingEnables();
6821     break;
6822
6823   default:
6824     break;
6825   }
6826
6827   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6828   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6829
6830   if(bookHit) { // [HGM] book: simulate book reply
6831         static char bookMove[MSG_SIZ]; // a bit generous?
6832
6833         programStats.nodes = programStats.depth = programStats.time =
6834         programStats.score = programStats.got_only_move = 0;
6835         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6836
6837         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6838         strcat(bookMove, bookHit);
6839         HandleMachineMove(bookMove, &first);
6840   }
6841   return 1;
6842 }
6843
6844 void
6845 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6846 {
6847     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6848     Markers *m = (Markers *) closure;
6849     if(rf == fromY && ff == fromX)
6850         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6851                          || kind == WhiteCapturesEnPassant
6852                          || kind == BlackCapturesEnPassant);
6853     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6854 }
6855
6856 void
6857 MarkTargetSquares (int clear)
6858 {
6859   int x, y;
6860   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6861      !appData.testLegality || gameMode == EditPosition) return;
6862   if(clear) {
6863     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6864   } else {
6865     int capt = 0;
6866     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6867     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6868       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6869       if(capt)
6870       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6871     }
6872   }
6873   DrawPosition(TRUE, NULL);
6874 }
6875
6876 int
6877 Explode (Board board, int fromX, int fromY, int toX, int toY)
6878 {
6879     if(gameInfo.variant == VariantAtomic &&
6880        (board[toY][toX] != EmptySquare ||                     // capture?
6881         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6882                          board[fromY][fromX] == BlackPawn   )
6883       )) {
6884         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6885         return TRUE;
6886     }
6887     return FALSE;
6888 }
6889
6890 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6891
6892 int
6893 CanPromote (ChessSquare piece, int y)
6894 {
6895         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6896         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6897         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6898            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6899            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6900                                                   gameInfo.variant == VariantMakruk) return FALSE;
6901         return (piece == BlackPawn && y == 1 ||
6902                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6903                 piece == BlackLance && y == 1 ||
6904                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6905 }
6906
6907 void
6908 LeftClick (ClickType clickType, int xPix, int yPix)
6909 {
6910     int x, y;
6911     Boolean saveAnimate;
6912     static int second = 0, promotionChoice = 0, clearFlag = 0;
6913     char promoChoice = NULLCHAR;
6914     ChessSquare piece;
6915     static TimeMark lastClickTime, prevClickTime;
6916
6917     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6918
6919     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6920
6921     if (clickType == Press) ErrorPopDown();
6922
6923     x = EventToSquare(xPix, BOARD_WIDTH);
6924     y = EventToSquare(yPix, BOARD_HEIGHT);
6925     if (!flipView && y >= 0) {
6926         y = BOARD_HEIGHT - 1 - y;
6927     }
6928     if (flipView && x >= 0) {
6929         x = BOARD_WIDTH - 1 - x;
6930     }
6931
6932     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6933         defaultPromoChoice = promoSweep;
6934         promoSweep = EmptySquare;   // terminate sweep
6935         promoDefaultAltered = TRUE;
6936         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6937     }
6938
6939     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6940         if(clickType == Release) return; // ignore upclick of click-click destination
6941         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6942         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6943         if(gameInfo.holdingsWidth &&
6944                 (WhiteOnMove(currentMove)
6945                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6946                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6947             // click in right holdings, for determining promotion piece
6948             ChessSquare p = boards[currentMove][y][x];
6949             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6950             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6951             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6952                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6953                 fromX = fromY = -1;
6954                 return;
6955             }
6956         }
6957         DrawPosition(FALSE, boards[currentMove]);
6958         return;
6959     }
6960
6961     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6962     if(clickType == Press
6963             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6964               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6965               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6966         return;
6967
6968     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
6969         // could be static click on premove from-square: abort premove
6970         gotPremove = 0;
6971         ClearPremoveHighlights();
6972     }
6973
6974     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
6975         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6976
6977     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6978         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6979                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6980         defaultPromoChoice = DefaultPromoChoice(side);
6981     }
6982
6983     autoQueen = appData.alwaysPromoteToQueen;
6984
6985     if (fromX == -1) {
6986       int originalY = y;
6987       gatingPiece = EmptySquare;
6988       if (clickType != Press) {
6989         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6990             DragPieceEnd(xPix, yPix); dragging = 0;
6991             DrawPosition(FALSE, NULL);
6992         }
6993         return;
6994       }
6995       doubleClick = FALSE;
6996       fromX = x; fromY = y; toX = toY = -1;
6997       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6998          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6999          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7000             /* First square */
7001             if (OKToStartUserMove(fromX, fromY)) {
7002                 second = 0;
7003                 MarkTargetSquares(0);
7004                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7005                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7006                     promoSweep = defaultPromoChoice;
7007                     selectFlag = 0; lastX = xPix; lastY = yPix;
7008                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7009                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7010                 }
7011                 if (appData.highlightDragging) {
7012                     SetHighlights(fromX, fromY, -1, -1);
7013                 }
7014             } else fromX = fromY = -1;
7015             return;
7016         }
7017     }
7018
7019     /* fromX != -1 */
7020     if (clickType == Press && gameMode != EditPosition) {
7021         ChessSquare fromP;
7022         ChessSquare toP;
7023         int frc;
7024
7025         // ignore off-board to clicks
7026         if(y < 0 || x < 0) return;
7027
7028         /* Check if clicking again on the same color piece */
7029         fromP = boards[currentMove][fromY][fromX];
7030         toP = boards[currentMove][y][x];
7031         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7032         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7033              WhitePawn <= toP && toP <= WhiteKing &&
7034              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7035              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7036             (BlackPawn <= fromP && fromP <= BlackKing &&
7037              BlackPawn <= toP && toP <= BlackKing &&
7038              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7039              !(fromP == BlackKing && toP == BlackRook && frc))) {
7040             /* Clicked again on same color piece -- changed his mind */
7041             second = (x == fromX && y == fromY);
7042             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7043                 second = FALSE; // first double-click rather than scond click
7044                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7045             }
7046             promoDefaultAltered = FALSE;
7047             MarkTargetSquares(1);
7048            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7049             if (appData.highlightDragging) {
7050                 SetHighlights(x, y, -1, -1);
7051             } else {
7052                 ClearHighlights();
7053             }
7054             if (OKToStartUserMove(x, y)) {
7055                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7056                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7057                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7058                  gatingPiece = boards[currentMove][fromY][fromX];
7059                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7060                 fromX = x;
7061                 fromY = y; dragging = 1;
7062                 MarkTargetSquares(0);
7063                 DragPieceBegin(xPix, yPix, FALSE);
7064                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7065                     promoSweep = defaultPromoChoice;
7066                     selectFlag = 0; lastX = xPix; lastY = yPix;
7067                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7068                 }
7069             }
7070            }
7071            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7072            second = FALSE; 
7073         }
7074         // ignore clicks on holdings
7075         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7076     }
7077
7078     if (clickType == Release && x == fromX && y == fromY) {
7079         DragPieceEnd(xPix, yPix); dragging = 0;
7080         if(clearFlag) {
7081             // a deferred attempt to click-click move an empty square on top of a piece
7082             boards[currentMove][y][x] = EmptySquare;
7083             ClearHighlights();
7084             DrawPosition(FALSE, boards[currentMove]);
7085             fromX = fromY = -1; clearFlag = 0;
7086             return;
7087         }
7088         if (appData.animateDragging) {
7089             /* Undo animation damage if any */
7090             DrawPosition(FALSE, NULL);
7091         }
7092         if (second) {
7093             /* Second up/down in same square; just abort move */
7094             second = 0;
7095             fromX = fromY = -1;
7096             gatingPiece = EmptySquare;
7097             ClearHighlights();
7098             gotPremove = 0;
7099             ClearPremoveHighlights();
7100         } else {
7101             /* First upclick in same square; start click-click mode */
7102             SetHighlights(x, y, -1, -1);
7103         }
7104         return;
7105     }
7106
7107     clearFlag = 0;
7108
7109     /* we now have a different from- and (possibly off-board) to-square */
7110     /* Completed move */
7111     toX = x;
7112     toY = y;
7113     saveAnimate = appData.animate;
7114     MarkTargetSquares(1);
7115     if (clickType == Press) {
7116         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7117             // must be Edit Position mode with empty-square selected
7118             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7119             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7120             return;
7121         }
7122         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7123             ChessSquare piece = boards[currentMove][fromY][fromX];
7124             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7125             promoSweep = defaultPromoChoice;
7126             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7127             selectFlag = 0; lastX = xPix; lastY = yPix;
7128             Sweep(0); // Pawn that is going to promote: preview promotion piece
7129             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7130             DrawPosition(FALSE, boards[currentMove]);
7131             return;
7132         }
7133         /* Finish clickclick move */
7134         if (appData.animate || appData.highlightLastMove) {
7135             SetHighlights(fromX, fromY, toX, toY);
7136         } else {
7137             ClearHighlights();
7138         }
7139     } else {
7140         /* Finish drag move */
7141         if (appData.highlightLastMove) {
7142             SetHighlights(fromX, fromY, toX, toY);
7143         } else {
7144             ClearHighlights();
7145         }
7146         DragPieceEnd(xPix, yPix); dragging = 0;
7147         /* Don't animate move and drag both */
7148         appData.animate = FALSE;
7149     }
7150
7151     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7152     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7153         ChessSquare piece = boards[currentMove][fromY][fromX];
7154         if(gameMode == EditPosition && piece != EmptySquare &&
7155            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7156             int n;
7157
7158             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7159                 n = PieceToNumber(piece - (int)BlackPawn);
7160                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7161                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7162                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7163             } else
7164             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7165                 n = PieceToNumber(piece);
7166                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7167                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7168                 boards[currentMove][n][BOARD_WIDTH-2]++;
7169             }
7170             boards[currentMove][fromY][fromX] = EmptySquare;
7171         }
7172         ClearHighlights();
7173         fromX = fromY = -1;
7174         DrawPosition(TRUE, boards[currentMove]);
7175         return;
7176     }
7177
7178     // off-board moves should not be highlighted
7179     if(x < 0 || y < 0) ClearHighlights();
7180
7181     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7182
7183     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7184         SetHighlights(fromX, fromY, toX, toY);
7185         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7186             // [HGM] super: promotion to captured piece selected from holdings
7187             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7188             promotionChoice = TRUE;
7189             // kludge follows to temporarily execute move on display, without promoting yet
7190             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7191             boards[currentMove][toY][toX] = p;
7192             DrawPosition(FALSE, boards[currentMove]);
7193             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7194             boards[currentMove][toY][toX] = q;
7195             DisplayMessage("Click in holdings to choose piece", "");
7196             return;
7197         }
7198         PromotionPopUp();
7199     } else {
7200         int oldMove = currentMove;
7201         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7202         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7203         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7204         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7205            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7206             DrawPosition(TRUE, boards[currentMove]);
7207         fromX = fromY = -1;
7208     }
7209     appData.animate = saveAnimate;
7210     if (appData.animate || appData.animateDragging) {
7211         /* Undo animation damage if needed */
7212         DrawPosition(FALSE, NULL);
7213     }
7214 }
7215
7216 int
7217 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7218 {   // front-end-free part taken out of PieceMenuPopup
7219     int whichMenu; int xSqr, ySqr;
7220
7221     if(seekGraphUp) { // [HGM] seekgraph
7222         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7223         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7224         return -2;
7225     }
7226
7227     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7228          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7229         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7230         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7231         if(action == Press)   {
7232             originalFlip = flipView;
7233             flipView = !flipView; // temporarily flip board to see game from partners perspective
7234             DrawPosition(TRUE, partnerBoard);
7235             DisplayMessage(partnerStatus, "");
7236             partnerUp = TRUE;
7237         } else if(action == Release) {
7238             flipView = originalFlip;
7239             DrawPosition(TRUE, boards[currentMove]);
7240             partnerUp = FALSE;
7241         }
7242         return -2;
7243     }
7244
7245     xSqr = EventToSquare(x, BOARD_WIDTH);
7246     ySqr = EventToSquare(y, BOARD_HEIGHT);
7247     if (action == Release) {
7248         if(pieceSweep != EmptySquare) {
7249             EditPositionMenuEvent(pieceSweep, toX, toY);
7250             pieceSweep = EmptySquare;
7251         } else UnLoadPV(); // [HGM] pv
7252     }
7253     if (action != Press) return -2; // return code to be ignored
7254     switch (gameMode) {
7255       case IcsExamining:
7256         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7257       case EditPosition:
7258         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7259         if (xSqr < 0 || ySqr < 0) return -1;
7260         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7261         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7262         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7263         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7264         NextPiece(0);
7265         return 2; // grab
7266       case IcsObserving:
7267         if(!appData.icsEngineAnalyze) return -1;
7268       case IcsPlayingWhite:
7269       case IcsPlayingBlack:
7270         if(!appData.zippyPlay) goto noZip;
7271       case AnalyzeMode:
7272       case AnalyzeFile:
7273       case MachinePlaysWhite:
7274       case MachinePlaysBlack:
7275       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7276         if (!appData.dropMenu) {
7277           LoadPV(x, y);
7278           return 2; // flag front-end to grab mouse events
7279         }
7280         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7281            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7282       case EditGame:
7283       noZip:
7284         if (xSqr < 0 || ySqr < 0) return -1;
7285         if (!appData.dropMenu || appData.testLegality &&
7286             gameInfo.variant != VariantBughouse &&
7287             gameInfo.variant != VariantCrazyhouse) return -1;
7288         whichMenu = 1; // drop menu
7289         break;
7290       default:
7291         return -1;
7292     }
7293
7294     if (((*fromX = xSqr) < 0) ||
7295         ((*fromY = ySqr) < 0)) {
7296         *fromX = *fromY = -1;
7297         return -1;
7298     }
7299     if (flipView)
7300       *fromX = BOARD_WIDTH - 1 - *fromX;
7301     else
7302       *fromY = BOARD_HEIGHT - 1 - *fromY;
7303
7304     return whichMenu;
7305 }
7306
7307 void
7308 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7309 {
7310 //    char * hint = lastHint;
7311     FrontEndProgramStats stats;
7312
7313     stats.which = cps == &first ? 0 : 1;
7314     stats.depth = cpstats->depth;
7315     stats.nodes = cpstats->nodes;
7316     stats.score = cpstats->score;
7317     stats.time = cpstats->time;
7318     stats.pv = cpstats->movelist;
7319     stats.hint = lastHint;
7320     stats.an_move_index = 0;
7321     stats.an_move_count = 0;
7322
7323     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7324         stats.hint = cpstats->move_name;
7325         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7326         stats.an_move_count = cpstats->nr_moves;
7327     }
7328
7329     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
7330
7331     SetProgramStats( &stats );
7332 }
7333
7334 void
7335 ClearEngineOutputPane (int which)
7336 {
7337     static FrontEndProgramStats dummyStats;
7338     dummyStats.which = which;
7339     dummyStats.pv = "#";
7340     SetProgramStats( &dummyStats );
7341 }
7342
7343 #define MAXPLAYERS 500
7344
7345 char *
7346 TourneyStandings (int display)
7347 {
7348     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7349     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7350     char result, *p, *names[MAXPLAYERS];
7351
7352     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7353         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7354     names[0] = p = strdup(appData.participants);
7355     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7356
7357     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7358
7359     while(result = appData.results[nr]) {
7360         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7361         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7362         wScore = bScore = 0;
7363         switch(result) {
7364           case '+': wScore = 2; break;
7365           case '-': bScore = 2; break;
7366           case '=': wScore = bScore = 1; break;
7367           case ' ':
7368           case '*': return strdup("busy"); // tourney not finished
7369         }
7370         score[w] += wScore;
7371         score[b] += bScore;
7372         games[w]++;
7373         games[b]++;
7374         nr++;
7375     }
7376     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7377     for(w=0; w<nPlayers; w++) {
7378         bScore = -1;
7379         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7380         ranking[w] = b; points[w] = bScore; score[b] = -2;
7381     }
7382     p = malloc(nPlayers*34+1);
7383     for(w=0; w<nPlayers && w<display; w++)
7384         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7385     free(names[0]);
7386     return p;
7387 }
7388
7389 void
7390 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7391 {       // count all piece types
7392         int p, f, r;
7393         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7394         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7395         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7396                 p = board[r][f];
7397                 pCnt[p]++;
7398                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7399                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7400                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7401                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7402                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7403                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7404         }
7405 }
7406
7407 int
7408 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7409 {
7410         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7411         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7412
7413         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7414         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7415         if(myPawns == 2 && nMine == 3) // KPP
7416             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7417         if(myPawns == 1 && nMine == 2) // KP
7418             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7419         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7420             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7421         if(myPawns) return FALSE;
7422         if(pCnt[WhiteRook+side])
7423             return pCnt[BlackRook-side] ||
7424                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7425                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7426                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7427         if(pCnt[WhiteCannon+side]) {
7428             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7429             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7430         }
7431         if(pCnt[WhiteKnight+side])
7432             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7433         return FALSE;
7434 }
7435
7436 int
7437 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7438 {
7439         VariantClass v = gameInfo.variant;
7440
7441         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7442         if(v == VariantShatranj) return TRUE; // always winnable through baring
7443         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7444         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7445
7446         if(v == VariantXiangqi) {
7447                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7448
7449                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7450                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7451                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7452                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7453                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7454                 if(stale) // we have at least one last-rank P plus perhaps C
7455                     return majors // KPKX
7456                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7457                 else // KCA*E*
7458                     return pCnt[WhiteFerz+side] // KCAK
7459                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7460                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7461                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7462
7463         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7464                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7465
7466                 if(nMine == 1) return FALSE; // bare King
7467                 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
7468                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7469                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7470                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7471                 if(pCnt[WhiteKnight+side])
7472                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7473                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7474                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7475                 if(nBishops)
7476                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7477                 if(pCnt[WhiteAlfil+side])
7478                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7479                 if(pCnt[WhiteWazir+side])
7480                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7481         }
7482
7483         return TRUE;
7484 }
7485
7486 int
7487 CompareWithRights (Board b1, Board b2)
7488 {
7489     int rights = 0;
7490     if(!CompareBoards(b1, b2)) return FALSE;
7491     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7492     /* compare castling rights */
7493     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7494            rights++; /* King lost rights, while rook still had them */
7495     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7496         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7497            rights++; /* but at least one rook lost them */
7498     }
7499     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7500            rights++;
7501     if( b1[CASTLING][5] != NoRights ) {
7502         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7503            rights++;
7504     }
7505     return rights == 0;
7506 }
7507
7508 int
7509 Adjudicate (ChessProgramState *cps)
7510 {       // [HGM] some adjudications useful with buggy engines
7511         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7512         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7513         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7514         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7515         int k, count = 0; static int bare = 1;
7516         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7517         Boolean canAdjudicate = !appData.icsActive;
7518
7519         // most tests only when we understand the game, i.e. legality-checking on
7520             if( appData.testLegality )
7521             {   /* [HGM] Some more adjudications for obstinate engines */
7522                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7523                 static int moveCount = 6;
7524                 ChessMove result;
7525                 char *reason = NULL;
7526
7527                 /* Count what is on board. */
7528                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7529
7530                 /* Some material-based adjudications that have to be made before stalemate test */
7531                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7532                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7533                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7534                      if(canAdjudicate && appData.checkMates) {
7535                          if(engineOpponent)
7536                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7537                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7538                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7539                          return 1;
7540                      }
7541                 }
7542
7543                 /* Bare King in Shatranj (loses) or Losers (wins) */
7544                 if( nrW == 1 || nrB == 1) {
7545                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7546                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7547                      if(canAdjudicate && appData.checkMates) {
7548                          if(engineOpponent)
7549                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7550                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7551                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7552                          return 1;
7553                      }
7554                   } else
7555                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7556                   {    /* bare King */
7557                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7558                         if(canAdjudicate && appData.checkMates) {
7559                             /* but only adjudicate if adjudication enabled */
7560                             if(engineOpponent)
7561                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7562                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7563                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7564                             return 1;
7565                         }
7566                   }
7567                 } else bare = 1;
7568
7569
7570             // don't wait for engine to announce game end if we can judge ourselves
7571             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7572               case MT_CHECK:
7573                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7574                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7575                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7576                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7577                             checkCnt++;
7578                         if(checkCnt >= 2) {
7579                             reason = "Xboard adjudication: 3rd check";
7580                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7581                             break;
7582                         }
7583                     }
7584                 }
7585               case MT_NONE:
7586               default:
7587                 break;
7588               case MT_STALEMATE:
7589               case MT_STAINMATE:
7590                 reason = "Xboard adjudication: Stalemate";
7591                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7592                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7593                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7594                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7595                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7596                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7597                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7598                                                                         EP_CHECKMATE : EP_WINS);
7599                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7600                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7601                 }
7602                 break;
7603               case MT_CHECKMATE:
7604                 reason = "Xboard adjudication: Checkmate";
7605                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7606                 break;
7607             }
7608
7609                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7610                     case EP_STALEMATE:
7611                         result = GameIsDrawn; break;
7612                     case EP_CHECKMATE:
7613                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7614                     case EP_WINS:
7615                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7616                     default:
7617                         result = EndOfFile;
7618                 }
7619                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7620                     if(engineOpponent)
7621                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7622                     GameEnds( result, reason, GE_XBOARD );
7623                     return 1;
7624                 }
7625
7626                 /* Next absolutely insufficient mating material. */
7627                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7628                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7629                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7630
7631                      /* always flag draws, for judging claims */
7632                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7633
7634                      if(canAdjudicate && appData.materialDraws) {
7635                          /* but only adjudicate them if adjudication enabled */
7636                          if(engineOpponent) {
7637                            SendToProgram("force\n", engineOpponent); // suppress reply
7638                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7639                          }
7640                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7641                          return 1;
7642                      }
7643                 }
7644
7645                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7646                 if(gameInfo.variant == VariantXiangqi ?
7647                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7648                  : nrW + nrB == 4 &&
7649                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7650                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7651                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7652                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7653                    ) ) {
7654                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7655                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7656                           if(engineOpponent) {
7657                             SendToProgram("force\n", engineOpponent); // suppress reply
7658                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7659                           }
7660                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7661                           return 1;
7662                      }
7663                 } else moveCount = 6;
7664             }
7665
7666         // Repetition draws and 50-move rule can be applied independently of legality testing
7667
7668                 /* Check for rep-draws */
7669                 count = 0;
7670                 for(k = forwardMostMove-2;
7671                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7672                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7673                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7674                     k-=2)
7675                 {   int rights=0;
7676                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7677                         /* compare castling rights */
7678                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7679                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7680                                 rights++; /* King lost rights, while rook still had them */
7681                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7682                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7683                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7684                                    rights++; /* but at least one rook lost them */
7685                         }
7686                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7687                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7688                                 rights++;
7689                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7690                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7691                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7692                                    rights++;
7693                         }
7694                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7695                             && appData.drawRepeats > 1) {
7696                              /* adjudicate after user-specified nr of repeats */
7697                              int result = GameIsDrawn;
7698                              char *details = "XBoard adjudication: repetition draw";
7699                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7700                                 // [HGM] xiangqi: check for forbidden perpetuals
7701                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7702                                 for(m=forwardMostMove; m>k; m-=2) {
7703                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7704                                         ourPerpetual = 0; // the current mover did not always check
7705                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7706                                         hisPerpetual = 0; // the opponent did not always check
7707                                 }
7708                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7709                                                                         ourPerpetual, hisPerpetual);
7710                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7711                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7712                                     details = "Xboard adjudication: perpetual checking";
7713                                 } else
7714                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7715                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7716                                 } else
7717                                 // Now check for perpetual chases
7718                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7719                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7720                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7721                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7722                                         static char resdet[MSG_SIZ];
7723                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7724                                         details = resdet;
7725                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7726                                     } else
7727                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7728                                         break; // Abort repetition-checking loop.
7729                                 }
7730                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7731                              }
7732                              if(engineOpponent) {
7733                                SendToProgram("force\n", engineOpponent); // suppress reply
7734                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7735                              }
7736                              GameEnds( result, details, GE_XBOARD );
7737                              return 1;
7738                         }
7739                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7740                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7741                     }
7742                 }
7743
7744                 /* Now we test for 50-move draws. Determine ply count */
7745                 count = forwardMostMove;
7746                 /* look for last irreversble move */
7747                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7748                     count--;
7749                 /* if we hit starting position, add initial plies */
7750                 if( count == backwardMostMove )
7751                     count -= initialRulePlies;
7752                 count = forwardMostMove - count;
7753                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7754                         // adjust reversible move counter for checks in Xiangqi
7755                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7756                         if(i < backwardMostMove) i = backwardMostMove;
7757                         while(i <= forwardMostMove) {
7758                                 lastCheck = inCheck; // check evasion does not count
7759                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7760                                 if(inCheck || lastCheck) count--; // check does not count
7761                                 i++;
7762                         }
7763                 }
7764                 if( count >= 100)
7765                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7766                          /* this is used to judge if draw claims are legal */
7767                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7768                          if(engineOpponent) {
7769                            SendToProgram("force\n", engineOpponent); // suppress reply
7770                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7771                          }
7772                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7773                          return 1;
7774                 }
7775
7776                 /* if draw offer is pending, treat it as a draw claim
7777                  * when draw condition present, to allow engines a way to
7778                  * claim draws before making their move to avoid a race
7779                  * condition occurring after their move
7780                  */
7781                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7782                          char *p = NULL;
7783                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7784                              p = "Draw claim: 50-move rule";
7785                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7786                              p = "Draw claim: 3-fold repetition";
7787                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7788                              p = "Draw claim: insufficient mating material";
7789                          if( p != NULL && canAdjudicate) {
7790                              if(engineOpponent) {
7791                                SendToProgram("force\n", engineOpponent); // suppress reply
7792                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7793                              }
7794                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7795                              return 1;
7796                          }
7797                 }
7798
7799                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7800                     if(engineOpponent) {
7801                       SendToProgram("force\n", engineOpponent); // suppress reply
7802                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7803                     }
7804                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7805                     return 1;
7806                 }
7807         return 0;
7808 }
7809
7810 char *
7811 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7812 {   // [HGM] book: this routine intercepts moves to simulate book replies
7813     char *bookHit = NULL;
7814
7815     //first determine if the incoming move brings opponent into his book
7816     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7817         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7818     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7819     if(bookHit != NULL && !cps->bookSuspend) {
7820         // make sure opponent is not going to reply after receiving move to book position
7821         SendToProgram("force\n", cps);
7822         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7823     }
7824     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7825     // now arrange restart after book miss
7826     if(bookHit) {
7827         // after a book hit we never send 'go', and the code after the call to this routine
7828         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7829         char buf[MSG_SIZ], *move = bookHit;
7830         if(cps->useSAN) {
7831             int fromX, fromY, toX, toY;
7832             char promoChar;
7833             ChessMove moveType;
7834             move = buf + 30;
7835             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7836                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7837                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7838                                     PosFlags(forwardMostMove),
7839                                     fromY, fromX, toY, toX, promoChar, move);
7840             } else {
7841                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7842                 bookHit = NULL;
7843             }
7844         }
7845         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7846         SendToProgram(buf, cps);
7847         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7848     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7849         SendToProgram("go\n", cps);
7850         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7851     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7852         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7853             SendToProgram("go\n", cps);
7854         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7855     }
7856     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7857 }
7858
7859 char *savedMessage;
7860 ChessProgramState *savedState;
7861 void
7862 DeferredBookMove (void)
7863 {
7864         if(savedState->lastPing != savedState->lastPong)
7865                     ScheduleDelayedEvent(DeferredBookMove, 10);
7866         else
7867         HandleMachineMove(savedMessage, savedState);
7868 }
7869
7870 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7871
7872 void
7873 HandleMachineMove (char *message, ChessProgramState *cps)
7874 {
7875     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7876     char realname[MSG_SIZ];
7877     int fromX, fromY, toX, toY;
7878     ChessMove moveType;
7879     char promoChar;
7880     char *p, *pv=buf1;
7881     int machineWhite;
7882     char *bookHit;
7883
7884     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7885         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7886         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7887             DisplayError(_("Invalid pairing from pairing engine"), 0);
7888             return;
7889         }
7890         pairingReceived = 1;
7891         NextMatchGame();
7892         return; // Skim the pairing messages here.
7893     }
7894
7895     cps->userError = 0;
7896
7897 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7898     /*
7899      * Kludge to ignore BEL characters
7900      */
7901     while (*message == '\007') message++;
7902
7903     /*
7904      * [HGM] engine debug message: ignore lines starting with '#' character
7905      */
7906     if(cps->debug && *message == '#') return;
7907
7908     /*
7909      * Look for book output
7910      */
7911     if (cps == &first && bookRequested) {
7912         if (message[0] == '\t' || message[0] == ' ') {
7913             /* Part of the book output is here; append it */
7914             strcat(bookOutput, message);
7915             strcat(bookOutput, "  \n");
7916             return;
7917         } else if (bookOutput[0] != NULLCHAR) {
7918             /* All of book output has arrived; display it */
7919             char *p = bookOutput;
7920             while (*p != NULLCHAR) {
7921                 if (*p == '\t') *p = ' ';
7922                 p++;
7923             }
7924             DisplayInformation(bookOutput);
7925             bookRequested = FALSE;
7926             /* Fall through to parse the current output */
7927         }
7928     }
7929
7930     /*
7931      * Look for machine move.
7932      */
7933     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7934         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7935     {
7936         /* This method is only useful on engines that support ping */
7937         if (cps->lastPing != cps->lastPong) {
7938           if (gameMode == BeginningOfGame) {
7939             /* Extra move from before last new; ignore */
7940             if (appData.debugMode) {
7941                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7942             }
7943           } else {
7944             if (appData.debugMode) {
7945                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7946                         cps->which, gameMode);
7947             }
7948
7949             SendToProgram("undo\n", cps);
7950           }
7951           return;
7952         }
7953
7954         switch (gameMode) {
7955           case BeginningOfGame:
7956             /* Extra move from before last reset; ignore */
7957             if (appData.debugMode) {
7958                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7959             }
7960             return;
7961
7962           case EndOfGame:
7963           case IcsIdle:
7964           default:
7965             /* Extra move after we tried to stop.  The mode test is
7966                not a reliable way of detecting this problem, but it's
7967                the best we can do on engines that don't support ping.
7968             */
7969             if (appData.debugMode) {
7970                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7971                         cps->which, gameMode);
7972             }
7973             SendToProgram("undo\n", cps);
7974             return;
7975
7976           case MachinePlaysWhite:
7977           case IcsPlayingWhite:
7978             machineWhite = TRUE;
7979             break;
7980
7981           case MachinePlaysBlack:
7982           case IcsPlayingBlack:
7983             machineWhite = FALSE;
7984             break;
7985
7986           case TwoMachinesPlay:
7987             machineWhite = (cps->twoMachinesColor[0] == 'w');
7988             break;
7989         }
7990         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7991             if (appData.debugMode) {
7992                 fprintf(debugFP,
7993                         "Ignoring move out of turn by %s, gameMode %d"
7994                         ", forwardMost %d\n",
7995                         cps->which, gameMode, forwardMostMove);
7996             }
7997             return;
7998         }
7999
8000         if(cps->alphaRank) AlphaRank(machineMove, 4);
8001         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8002                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8003             /* Machine move could not be parsed; ignore it. */
8004           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8005                     machineMove, _(cps->which));
8006             DisplayError(buf1, 0);
8007             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8008                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8009             if (gameMode == TwoMachinesPlay) {
8010               GameEnds(machineWhite ? BlackWins : WhiteWins,
8011                        buf1, GE_XBOARD);
8012             }
8013             return;
8014         }
8015
8016         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8017         /* So we have to redo legality test with true e.p. status here,  */
8018         /* to make sure an illegal e.p. capture does not slip through,   */
8019         /* to cause a forfeit on a justified illegal-move complaint      */
8020         /* of the opponent.                                              */
8021         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8022            ChessMove moveType;
8023            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8024                              fromY, fromX, toY, toX, promoChar);
8025             if(moveType == IllegalMove) {
8026               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8027                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8028                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8029                            buf1, GE_XBOARD);
8030                 return;
8031            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8032            /* [HGM] Kludge to handle engines that send FRC-style castling
8033               when they shouldn't (like TSCP-Gothic) */
8034            switch(moveType) {
8035              case WhiteASideCastleFR:
8036              case BlackASideCastleFR:
8037                toX+=2;
8038                currentMoveString[2]++;
8039                break;
8040              case WhiteHSideCastleFR:
8041              case BlackHSideCastleFR:
8042                toX--;
8043                currentMoveString[2]--;
8044                break;
8045              default: ; // nothing to do, but suppresses warning of pedantic compilers
8046            }
8047         }
8048         hintRequested = FALSE;
8049         lastHint[0] = NULLCHAR;
8050         bookRequested = FALSE;
8051         /* Program may be pondering now */
8052         cps->maybeThinking = TRUE;
8053         if (cps->sendTime == 2) cps->sendTime = 1;
8054         if (cps->offeredDraw) cps->offeredDraw--;
8055
8056         /* [AS] Save move info*/
8057         pvInfoList[ forwardMostMove ].score = programStats.score;
8058         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8059         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8060
8061         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8062
8063         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8064         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8065             int count = 0;
8066
8067             while( count < adjudicateLossPlies ) {
8068                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8069
8070                 if( count & 1 ) {
8071                     score = -score; /* Flip score for winning side */
8072                 }
8073
8074                 if( score > adjudicateLossThreshold ) {
8075                     break;
8076                 }
8077
8078                 count++;
8079             }
8080
8081             if( count >= adjudicateLossPlies ) {
8082                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8083
8084                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8085                     "Xboard adjudication",
8086                     GE_XBOARD );
8087
8088                 return;
8089             }
8090         }
8091
8092         if(Adjudicate(cps)) {
8093             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8094             return; // [HGM] adjudicate: for all automatic game ends
8095         }
8096
8097 #if ZIPPY
8098         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8099             first.initDone) {
8100           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8101                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8102                 SendToICS("draw ");
8103                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8104           }
8105           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8106           ics_user_moved = 1;
8107           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8108                 char buf[3*MSG_SIZ];
8109
8110                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8111                         programStats.score / 100.,
8112                         programStats.depth,
8113                         programStats.time / 100.,
8114                         (unsigned int)programStats.nodes,
8115                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8116                         programStats.movelist);
8117                 SendToICS(buf);
8118 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8119           }
8120         }
8121 #endif
8122
8123         /* [AS] Clear stats for next move */
8124         ClearProgramStats();
8125         thinkOutput[0] = NULLCHAR;
8126         hiddenThinkOutputState = 0;
8127
8128         bookHit = NULL;
8129         if (gameMode == TwoMachinesPlay) {
8130             /* [HGM] relaying draw offers moved to after reception of move */
8131             /* and interpreting offer as claim if it brings draw condition */
8132             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8133                 SendToProgram("draw\n", cps->other);
8134             }
8135             if (cps->other->sendTime) {
8136                 SendTimeRemaining(cps->other,
8137                                   cps->other->twoMachinesColor[0] == 'w');
8138             }
8139             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8140             if (firstMove && !bookHit) {
8141                 firstMove = FALSE;
8142                 if (cps->other->useColors) {
8143                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8144                 }
8145                 SendToProgram("go\n", cps->other);
8146             }
8147             cps->other->maybeThinking = TRUE;
8148         }
8149
8150         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8151
8152         if (!pausing && appData.ringBellAfterMoves) {
8153             RingBell();
8154         }
8155
8156         /*
8157          * Reenable menu items that were disabled while
8158          * machine was thinking
8159          */
8160         if (gameMode != TwoMachinesPlay)
8161             SetUserThinkingEnables();
8162
8163         // [HGM] book: after book hit opponent has received move and is now in force mode
8164         // force the book reply into it, and then fake that it outputted this move by jumping
8165         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8166         if(bookHit) {
8167                 static char bookMove[MSG_SIZ]; // a bit generous?
8168
8169                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8170                 strcat(bookMove, bookHit);
8171                 message = bookMove;
8172                 cps = cps->other;
8173                 programStats.nodes = programStats.depth = programStats.time =
8174                 programStats.score = programStats.got_only_move = 0;
8175                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8176
8177                 if(cps->lastPing != cps->lastPong) {
8178                     savedMessage = message; // args for deferred call
8179                     savedState = cps;
8180                     ScheduleDelayedEvent(DeferredBookMove, 10);
8181                     return;
8182                 }
8183                 goto FakeBookMove;
8184         }
8185
8186         return;
8187     }
8188
8189     /* Set special modes for chess engines.  Later something general
8190      *  could be added here; for now there is just one kludge feature,
8191      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8192      *  when "xboard" is given as an interactive command.
8193      */
8194     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8195         cps->useSigint = FALSE;
8196         cps->useSigterm = FALSE;
8197     }
8198     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8199       ParseFeatures(message+8, cps);
8200       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8201     }
8202
8203     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8204                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8205       int dummy, s=6; char buf[MSG_SIZ];
8206       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8207       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8208       if(startedFromSetupPosition) return;
8209       ParseFEN(boards[0], &dummy, message+s);
8210       DrawPosition(TRUE, boards[0]);
8211       startedFromSetupPosition = TRUE;
8212       return;
8213     }
8214     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8215      * want this, I was asked to put it in, and obliged.
8216      */
8217     if (!strncmp(message, "setboard ", 9)) {
8218         Board initial_position;
8219
8220         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8221
8222         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8223             DisplayError(_("Bad FEN received from engine"), 0);
8224             return ;
8225         } else {
8226            Reset(TRUE, FALSE);
8227            CopyBoard(boards[0], initial_position);
8228            initialRulePlies = FENrulePlies;
8229            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8230            else gameMode = MachinePlaysBlack;
8231            DrawPosition(FALSE, boards[currentMove]);
8232         }
8233         return;
8234     }
8235
8236     /*
8237      * Look for communication commands
8238      */
8239     if (!strncmp(message, "telluser ", 9)) {
8240         if(message[9] == '\\' && message[10] == '\\')
8241             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8242         PlayTellSound();
8243         DisplayNote(message + 9);
8244         return;
8245     }
8246     if (!strncmp(message, "tellusererror ", 14)) {
8247         cps->userError = 1;
8248         if(message[14] == '\\' && message[15] == '\\')
8249             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8250         PlayTellSound();
8251         DisplayError(message + 14, 0);
8252         return;
8253     }
8254     if (!strncmp(message, "tellopponent ", 13)) {
8255       if (appData.icsActive) {
8256         if (loggedOn) {
8257           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8258           SendToICS(buf1);
8259         }
8260       } else {
8261         DisplayNote(message + 13);
8262       }
8263       return;
8264     }
8265     if (!strncmp(message, "tellothers ", 11)) {
8266       if (appData.icsActive) {
8267         if (loggedOn) {
8268           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8269           SendToICS(buf1);
8270         }
8271       }
8272       return;
8273     }
8274     if (!strncmp(message, "tellall ", 8)) {
8275       if (appData.icsActive) {
8276         if (loggedOn) {
8277           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8278           SendToICS(buf1);
8279         }
8280       } else {
8281         DisplayNote(message + 8);
8282       }
8283       return;
8284     }
8285     if (strncmp(message, "warning", 7) == 0) {
8286         /* Undocumented feature, use tellusererror in new code */
8287         DisplayError(message, 0);
8288         return;
8289     }
8290     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8291         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8292         strcat(realname, " query");
8293         AskQuestion(realname, buf2, buf1, cps->pr);
8294         return;
8295     }
8296     /* Commands from the engine directly to ICS.  We don't allow these to be
8297      *  sent until we are logged on. Crafty kibitzes have been known to
8298      *  interfere with the login process.
8299      */
8300     if (loggedOn) {
8301         if (!strncmp(message, "tellics ", 8)) {
8302             SendToICS(message + 8);
8303             SendToICS("\n");
8304             return;
8305         }
8306         if (!strncmp(message, "tellicsnoalias ", 15)) {
8307             SendToICS(ics_prefix);
8308             SendToICS(message + 15);
8309             SendToICS("\n");
8310             return;
8311         }
8312         /* The following are for backward compatibility only */
8313         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8314             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8315             SendToICS(ics_prefix);
8316             SendToICS(message);
8317             SendToICS("\n");
8318             return;
8319         }
8320     }
8321     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8322         return;
8323     }
8324     /*
8325      * If the move is illegal, cancel it and redraw the board.
8326      * Also deal with other error cases.  Matching is rather loose
8327      * here to accommodate engines written before the spec.
8328      */
8329     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8330         strncmp(message, "Error", 5) == 0) {
8331         if (StrStr(message, "name") ||
8332             StrStr(message, "rating") || StrStr(message, "?") ||
8333             StrStr(message, "result") || StrStr(message, "board") ||
8334             StrStr(message, "bk") || StrStr(message, "computer") ||
8335             StrStr(message, "variant") || StrStr(message, "hint") ||
8336             StrStr(message, "random") || StrStr(message, "depth") ||
8337             StrStr(message, "accepted")) {
8338             return;
8339         }
8340         if (StrStr(message, "protover")) {
8341           /* Program is responding to input, so it's apparently done
8342              initializing, and this error message indicates it is
8343              protocol version 1.  So we don't need to wait any longer
8344              for it to initialize and send feature commands. */
8345           FeatureDone(cps, 1);
8346           cps->protocolVersion = 1;
8347           return;
8348         }
8349         cps->maybeThinking = FALSE;
8350
8351         if (StrStr(message, "draw")) {
8352             /* Program doesn't have "draw" command */
8353             cps->sendDrawOffers = 0;
8354             return;
8355         }
8356         if (cps->sendTime != 1 &&
8357             (StrStr(message, "time") || StrStr(message, "otim"))) {
8358           /* Program apparently doesn't have "time" or "otim" command */
8359           cps->sendTime = 0;
8360           return;
8361         }
8362         if (StrStr(message, "analyze")) {
8363             cps->analysisSupport = FALSE;
8364             cps->analyzing = FALSE;
8365 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8366             EditGameEvent(); // [HGM] try to preserve loaded game
8367             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8368             DisplayError(buf2, 0);
8369             return;
8370         }
8371         if (StrStr(message, "(no matching move)st")) {
8372           /* Special kludge for GNU Chess 4 only */
8373           cps->stKludge = TRUE;
8374           SendTimeControl(cps, movesPerSession, timeControl,
8375                           timeIncrement, appData.searchDepth,
8376                           searchTime);
8377           return;
8378         }
8379         if (StrStr(message, "(no matching move)sd")) {
8380           /* Special kludge for GNU Chess 4 only */
8381           cps->sdKludge = TRUE;
8382           SendTimeControl(cps, movesPerSession, timeControl,
8383                           timeIncrement, appData.searchDepth,
8384                           searchTime);
8385           return;
8386         }
8387         if (!StrStr(message, "llegal")) {
8388             return;
8389         }
8390         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8391             gameMode == IcsIdle) return;
8392         if (forwardMostMove <= backwardMostMove) return;
8393         if (pausing) PauseEvent();
8394       if(appData.forceIllegal) {
8395             // [HGM] illegal: machine refused move; force position after move into it
8396           SendToProgram("force\n", cps);
8397           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8398                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8399                 // when black is to move, while there might be nothing on a2 or black
8400                 // might already have the move. So send the board as if white has the move.
8401                 // But first we must change the stm of the engine, as it refused the last move
8402                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8403                 if(WhiteOnMove(forwardMostMove)) {
8404                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8405                     SendBoard(cps, forwardMostMove); // kludgeless board
8406                 } else {
8407                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8408                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8409                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8410                 }
8411           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8412             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8413                  gameMode == TwoMachinesPlay)
8414               SendToProgram("go\n", cps);
8415             return;
8416       } else
8417         if (gameMode == PlayFromGameFile) {
8418             /* Stop reading this game file */
8419             gameMode = EditGame;
8420             ModeHighlight();
8421         }
8422         /* [HGM] illegal-move claim should forfeit game when Xboard */
8423         /* only passes fully legal moves                            */
8424         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8425             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8426                                 "False illegal-move claim", GE_XBOARD );
8427             return; // do not take back move we tested as valid
8428         }
8429         currentMove = forwardMostMove-1;
8430         DisplayMove(currentMove-1); /* before DisplayMoveError */
8431         SwitchClocks(forwardMostMove-1); // [HGM] race
8432         DisplayBothClocks();
8433         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8434                 parseList[currentMove], _(cps->which));
8435         DisplayMoveError(buf1);
8436         DrawPosition(FALSE, boards[currentMove]);
8437
8438         SetUserThinkingEnables();
8439         return;
8440     }
8441     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8442         /* Program has a broken "time" command that
8443            outputs a string not ending in newline.
8444            Don't use it. */
8445         cps->sendTime = 0;
8446     }
8447
8448     /*
8449      * If chess program startup fails, exit with an error message.
8450      * Attempts to recover here are futile. [HGM] Well, we try anyway
8451      */
8452     if ((StrStr(message, "unknown host") != NULL)
8453         || (StrStr(message, "No remote directory") != NULL)
8454         || (StrStr(message, "not found") != NULL)
8455         || (StrStr(message, "No such file") != NULL)
8456         || (StrStr(message, "can't alloc") != NULL)
8457         || (StrStr(message, "Permission denied") != NULL)) {
8458
8459         cps->maybeThinking = FALSE;
8460         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8461                 _(cps->which), cps->program, cps->host, message);
8462         RemoveInputSource(cps->isr);
8463         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8464             cps->isr = NULL;
8465             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8466             cps->pr = NoProc; 
8467             if(cps == &first) {
8468                 appData.noChessProgram = TRUE;
8469                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8470                 gameMode = BeginningOfGame; ModeHighlight();
8471                 SetNCPMode();
8472             }
8473             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8474             DisplayMessage("", ""); // erase waiting message
8475             DisplayError(buf1, 0);
8476         }
8477         return;
8478     }
8479
8480     /*
8481      * Look for hint output
8482      */
8483     if (sscanf(message, "Hint: %s", buf1) == 1) {
8484         if (cps == &first && hintRequested) {
8485             hintRequested = FALSE;
8486             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8487                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8488                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8489                                     PosFlags(forwardMostMove),
8490                                     fromY, fromX, toY, toX, promoChar, buf1);
8491                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8492                 DisplayInformation(buf2);
8493             } else {
8494                 /* Hint move could not be parsed!? */
8495               snprintf(buf2, sizeof(buf2),
8496                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8497                         buf1, _(cps->which));
8498                 DisplayError(buf2, 0);
8499             }
8500         } else {
8501           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8502         }
8503         return;
8504     }
8505
8506     /*
8507      * Ignore other messages if game is not in progress
8508      */
8509     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8510         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8511
8512     /*
8513      * look for win, lose, draw, or draw offer
8514      */
8515     if (strncmp(message, "1-0", 3) == 0) {
8516         char *p, *q, *r = "";
8517         p = strchr(message, '{');
8518         if (p) {
8519             q = strchr(p, '}');
8520             if (q) {
8521                 *q = NULLCHAR;
8522                 r = p + 1;
8523             }
8524         }
8525         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8526         return;
8527     } else if (strncmp(message, "0-1", 3) == 0) {
8528         char *p, *q, *r = "";
8529         p = strchr(message, '{');
8530         if (p) {
8531             q = strchr(p, '}');
8532             if (q) {
8533                 *q = NULLCHAR;
8534                 r = p + 1;
8535             }
8536         }
8537         /* Kludge for Arasan 4.1 bug */
8538         if (strcmp(r, "Black resigns") == 0) {
8539             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8540             return;
8541         }
8542         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8543         return;
8544     } else if (strncmp(message, "1/2", 3) == 0) {
8545         char *p, *q, *r = "";
8546         p = strchr(message, '{');
8547         if (p) {
8548             q = strchr(p, '}');
8549             if (q) {
8550                 *q = NULLCHAR;
8551                 r = p + 1;
8552             }
8553         }
8554
8555         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8556         return;
8557
8558     } else if (strncmp(message, "White resign", 12) == 0) {
8559         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8560         return;
8561     } else if (strncmp(message, "Black resign", 12) == 0) {
8562         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8563         return;
8564     } else if (strncmp(message, "White matches", 13) == 0 ||
8565                strncmp(message, "Black matches", 13) == 0   ) {
8566         /* [HGM] ignore GNUShogi noises */
8567         return;
8568     } else if (strncmp(message, "White", 5) == 0 &&
8569                message[5] != '(' &&
8570                StrStr(message, "Black") == NULL) {
8571         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8572         return;
8573     } else if (strncmp(message, "Black", 5) == 0 &&
8574                message[5] != '(') {
8575         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8576         return;
8577     } else if (strcmp(message, "resign") == 0 ||
8578                strcmp(message, "computer resigns") == 0) {
8579         switch (gameMode) {
8580           case MachinePlaysBlack:
8581           case IcsPlayingBlack:
8582             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8583             break;
8584           case MachinePlaysWhite:
8585           case IcsPlayingWhite:
8586             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8587             break;
8588           case TwoMachinesPlay:
8589             if (cps->twoMachinesColor[0] == 'w')
8590               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8591             else
8592               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8593             break;
8594           default:
8595             /* can't happen */
8596             break;
8597         }
8598         return;
8599     } else if (strncmp(message, "opponent mates", 14) == 0) {
8600         switch (gameMode) {
8601           case MachinePlaysBlack:
8602           case IcsPlayingBlack:
8603             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8604             break;
8605           case MachinePlaysWhite:
8606           case IcsPlayingWhite:
8607             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8608             break;
8609           case TwoMachinesPlay:
8610             if (cps->twoMachinesColor[0] == 'w')
8611               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8612             else
8613               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8614             break;
8615           default:
8616             /* can't happen */
8617             break;
8618         }
8619         return;
8620     } else if (strncmp(message, "computer mates", 14) == 0) {
8621         switch (gameMode) {
8622           case MachinePlaysBlack:
8623           case IcsPlayingBlack:
8624             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8625             break;
8626           case MachinePlaysWhite:
8627           case IcsPlayingWhite:
8628             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8629             break;
8630           case TwoMachinesPlay:
8631             if (cps->twoMachinesColor[0] == 'w')
8632               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8633             else
8634               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8635             break;
8636           default:
8637             /* can't happen */
8638             break;
8639         }
8640         return;
8641     } else if (strncmp(message, "checkmate", 9) == 0) {
8642         if (WhiteOnMove(forwardMostMove)) {
8643             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8644         } else {
8645             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8646         }
8647         return;
8648     } else if (strstr(message, "Draw") != NULL ||
8649                strstr(message, "game is a draw") != NULL) {
8650         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8651         return;
8652     } else if (strstr(message, "offer") != NULL &&
8653                strstr(message, "draw") != NULL) {
8654 #if ZIPPY
8655         if (appData.zippyPlay && first.initDone) {
8656             /* Relay offer to ICS */
8657             SendToICS(ics_prefix);
8658             SendToICS("draw\n");
8659         }
8660 #endif
8661         cps->offeredDraw = 2; /* valid until this engine moves twice */
8662         if (gameMode == TwoMachinesPlay) {
8663             if (cps->other->offeredDraw) {
8664                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8665             /* [HGM] in two-machine mode we delay relaying draw offer      */
8666             /* until after we also have move, to see if it is really claim */
8667             }
8668         } else if (gameMode == MachinePlaysWhite ||
8669                    gameMode == MachinePlaysBlack) {
8670           if (userOfferedDraw) {
8671             DisplayInformation(_("Machine accepts your draw offer"));
8672             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8673           } else {
8674             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8675           }
8676         }
8677     }
8678
8679
8680     /*
8681      * Look for thinking output
8682      */
8683     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8684           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8685                                 ) {
8686         int plylev, mvleft, mvtot, curscore, time;
8687         char mvname[MOVE_LEN];
8688         u64 nodes; // [DM]
8689         char plyext;
8690         int ignore = FALSE;
8691         int prefixHint = FALSE;
8692         mvname[0] = NULLCHAR;
8693
8694         switch (gameMode) {
8695           case MachinePlaysBlack:
8696           case IcsPlayingBlack:
8697             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8698             break;
8699           case MachinePlaysWhite:
8700           case IcsPlayingWhite:
8701             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8702             break;
8703           case AnalyzeMode:
8704           case AnalyzeFile:
8705             break;
8706           case IcsObserving: /* [DM] icsEngineAnalyze */
8707             if (!appData.icsEngineAnalyze) ignore = TRUE;
8708             break;
8709           case TwoMachinesPlay:
8710             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8711                 ignore = TRUE;
8712             }
8713             break;
8714           default:
8715             ignore = TRUE;
8716             break;
8717         }
8718
8719         if (!ignore) {
8720             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8721             buf1[0] = NULLCHAR;
8722             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8723                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8724
8725                 if (plyext != ' ' && plyext != '\t') {
8726                     time *= 100;
8727                 }
8728
8729                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8730                 if( cps->scoreIsAbsolute &&
8731                     ( gameMode == MachinePlaysBlack ||
8732                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8733                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8734                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8735                      !WhiteOnMove(currentMove)
8736                     ) )
8737                 {
8738                     curscore = -curscore;
8739                 }
8740
8741                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8742
8743                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8744                         char buf[MSG_SIZ];
8745                         FILE *f;
8746                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8747                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8748                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8749                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8750                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8751                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8752                                 fclose(f);
8753                         } else DisplayError(_("failed writing PV"), 0);
8754                 }
8755
8756                 tempStats.depth = plylev;
8757                 tempStats.nodes = nodes;
8758                 tempStats.time = time;
8759                 tempStats.score = curscore;
8760                 tempStats.got_only_move = 0;
8761
8762                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8763                         int ticklen;
8764
8765                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8766                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8767                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8768                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8769                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8770                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8771                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8772                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8773                 }
8774
8775                 /* Buffer overflow protection */
8776                 if (pv[0] != NULLCHAR) {
8777                     if (strlen(pv) >= sizeof(tempStats.movelist)
8778                         && appData.debugMode) {
8779                         fprintf(debugFP,
8780                                 "PV is too long; using the first %u bytes.\n",
8781                                 (unsigned) sizeof(tempStats.movelist) - 1);
8782                     }
8783
8784                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8785                 } else {
8786                     sprintf(tempStats.movelist, " no PV\n");
8787                 }
8788
8789                 if (tempStats.seen_stat) {
8790                     tempStats.ok_to_send = 1;
8791                 }
8792
8793                 if (strchr(tempStats.movelist, '(') != NULL) {
8794                     tempStats.line_is_book = 1;
8795                     tempStats.nr_moves = 0;
8796                     tempStats.moves_left = 0;
8797                 } else {
8798                     tempStats.line_is_book = 0;
8799                 }
8800
8801                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8802                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8803
8804                 SendProgramStatsToFrontend( cps, &tempStats );
8805
8806                 /*
8807                     [AS] Protect the thinkOutput buffer from overflow... this
8808                     is only useful if buf1 hasn't overflowed first!
8809                 */
8810                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8811                          plylev,
8812                          (gameMode == TwoMachinesPlay ?
8813                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8814                          ((double) curscore) / 100.0,
8815                          prefixHint ? lastHint : "",
8816                          prefixHint ? " " : "" );
8817
8818                 if( buf1[0] != NULLCHAR ) {
8819                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8820
8821                     if( strlen(pv) > max_len ) {
8822                         if( appData.debugMode) {
8823                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8824                         }
8825                         pv[max_len+1] = '\0';
8826                     }
8827
8828                     strcat( thinkOutput, pv);
8829                 }
8830
8831                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8832                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8833                     DisplayMove(currentMove - 1);
8834                 }
8835                 return;
8836
8837             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8838                 /* crafty (9.25+) says "(only move) <move>"
8839                  * if there is only 1 legal move
8840                  */
8841                 sscanf(p, "(only move) %s", buf1);
8842                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8843                 sprintf(programStats.movelist, "%s (only move)", buf1);
8844                 programStats.depth = 1;
8845                 programStats.nr_moves = 1;
8846                 programStats.moves_left = 1;
8847                 programStats.nodes = 1;
8848                 programStats.time = 1;
8849                 programStats.got_only_move = 1;
8850
8851                 /* Not really, but we also use this member to
8852                    mean "line isn't going to change" (Crafty
8853                    isn't searching, so stats won't change) */
8854                 programStats.line_is_book = 1;
8855
8856                 SendProgramStatsToFrontend( cps, &programStats );
8857
8858                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8859                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8860                     DisplayMove(currentMove - 1);
8861                 }
8862                 return;
8863             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8864                               &time, &nodes, &plylev, &mvleft,
8865                               &mvtot, mvname) >= 5) {
8866                 /* The stat01: line is from Crafty (9.29+) in response
8867                    to the "." command */
8868                 programStats.seen_stat = 1;
8869                 cps->maybeThinking = TRUE;
8870
8871                 if (programStats.got_only_move || !appData.periodicUpdates)
8872                   return;
8873
8874                 programStats.depth = plylev;
8875                 programStats.time = time;
8876                 programStats.nodes = nodes;
8877                 programStats.moves_left = mvleft;
8878                 programStats.nr_moves = mvtot;
8879                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8880                 programStats.ok_to_send = 1;
8881                 programStats.movelist[0] = '\0';
8882
8883                 SendProgramStatsToFrontend( cps, &programStats );
8884
8885                 return;
8886
8887             } else if (strncmp(message,"++",2) == 0) {
8888                 /* Crafty 9.29+ outputs this */
8889                 programStats.got_fail = 2;
8890                 return;
8891
8892             } else if (strncmp(message,"--",2) == 0) {
8893                 /* Crafty 9.29+ outputs this */
8894                 programStats.got_fail = 1;
8895                 return;
8896
8897             } else if (thinkOutput[0] != NULLCHAR &&
8898                        strncmp(message, "    ", 4) == 0) {
8899                 unsigned message_len;
8900
8901                 p = message;
8902                 while (*p && *p == ' ') p++;
8903
8904                 message_len = strlen( p );
8905
8906                 /* [AS] Avoid buffer overflow */
8907                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8908                     strcat(thinkOutput, " ");
8909                     strcat(thinkOutput, p);
8910                 }
8911
8912                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8913                     strcat(programStats.movelist, " ");
8914                     strcat(programStats.movelist, p);
8915                 }
8916
8917                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8918                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8919                     DisplayMove(currentMove - 1);
8920                 }
8921                 return;
8922             }
8923         }
8924         else {
8925             buf1[0] = NULLCHAR;
8926
8927             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8928                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8929             {
8930                 ChessProgramStats cpstats;
8931
8932                 if (plyext != ' ' && plyext != '\t') {
8933                     time *= 100;
8934                 }
8935
8936                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8937                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8938                     curscore = -curscore;
8939                 }
8940
8941                 cpstats.depth = plylev;
8942                 cpstats.nodes = nodes;
8943                 cpstats.time = time;
8944                 cpstats.score = curscore;
8945                 cpstats.got_only_move = 0;
8946                 cpstats.movelist[0] = '\0';
8947
8948                 if (buf1[0] != NULLCHAR) {
8949                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8950                 }
8951
8952                 cpstats.ok_to_send = 0;
8953                 cpstats.line_is_book = 0;
8954                 cpstats.nr_moves = 0;
8955                 cpstats.moves_left = 0;
8956
8957                 SendProgramStatsToFrontend( cps, &cpstats );
8958             }
8959         }
8960     }
8961 }
8962
8963
8964 /* Parse a game score from the character string "game", and
8965    record it as the history of the current game.  The game
8966    score is NOT assumed to start from the standard position.
8967    The display is not updated in any way.
8968    */
8969 void
8970 ParseGameHistory (char *game)
8971 {
8972     ChessMove moveType;
8973     int fromX, fromY, toX, toY, boardIndex;
8974     char promoChar;
8975     char *p, *q;
8976     char buf[MSG_SIZ];
8977
8978     if (appData.debugMode)
8979       fprintf(debugFP, "Parsing game history: %s\n", game);
8980
8981     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8982     gameInfo.site = StrSave(appData.icsHost);
8983     gameInfo.date = PGNDate();
8984     gameInfo.round = StrSave("-");
8985
8986     /* Parse out names of players */
8987     while (*game == ' ') game++;
8988     p = buf;
8989     while (*game != ' ') *p++ = *game++;
8990     *p = NULLCHAR;
8991     gameInfo.white = StrSave(buf);
8992     while (*game == ' ') game++;
8993     p = buf;
8994     while (*game != ' ' && *game != '\n') *p++ = *game++;
8995     *p = NULLCHAR;
8996     gameInfo.black = StrSave(buf);
8997
8998     /* Parse moves */
8999     boardIndex = blackPlaysFirst ? 1 : 0;
9000     yynewstr(game);
9001     for (;;) {
9002         yyboardindex = boardIndex;
9003         moveType = (ChessMove) Myylex();
9004         switch (moveType) {
9005           case IllegalMove:             /* maybe suicide chess, etc. */
9006   if (appData.debugMode) {
9007     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9008     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9009     setbuf(debugFP, NULL);
9010   }
9011           case WhitePromotion:
9012           case BlackPromotion:
9013           case WhiteNonPromotion:
9014           case BlackNonPromotion:
9015           case NormalMove:
9016           case WhiteCapturesEnPassant:
9017           case BlackCapturesEnPassant:
9018           case WhiteKingSideCastle:
9019           case WhiteQueenSideCastle:
9020           case BlackKingSideCastle:
9021           case BlackQueenSideCastle:
9022           case WhiteKingSideCastleWild:
9023           case WhiteQueenSideCastleWild:
9024           case BlackKingSideCastleWild:
9025           case BlackQueenSideCastleWild:
9026           /* PUSH Fabien */
9027           case WhiteHSideCastleFR:
9028           case WhiteASideCastleFR:
9029           case BlackHSideCastleFR:
9030           case BlackASideCastleFR:
9031           /* POP Fabien */
9032             fromX = currentMoveString[0] - AAA;
9033             fromY = currentMoveString[1] - ONE;
9034             toX = currentMoveString[2] - AAA;
9035             toY = currentMoveString[3] - ONE;
9036             promoChar = currentMoveString[4];
9037             break;
9038           case WhiteDrop:
9039           case BlackDrop:
9040             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9041             fromX = moveType == WhiteDrop ?
9042               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9043             (int) CharToPiece(ToLower(currentMoveString[0]));
9044             fromY = DROP_RANK;
9045             toX = currentMoveString[2] - AAA;
9046             toY = currentMoveString[3] - ONE;
9047             promoChar = NULLCHAR;
9048             break;
9049           case AmbiguousMove:
9050             /* bug? */
9051             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9052   if (appData.debugMode) {
9053     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9054     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9055     setbuf(debugFP, NULL);
9056   }
9057             DisplayError(buf, 0);
9058             return;
9059           case ImpossibleMove:
9060             /* bug? */
9061             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9062   if (appData.debugMode) {
9063     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9064     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9065     setbuf(debugFP, NULL);
9066   }
9067             DisplayError(buf, 0);
9068             return;
9069           case EndOfFile:
9070             if (boardIndex < backwardMostMove) {
9071                 /* Oops, gap.  How did that happen? */
9072                 DisplayError(_("Gap in move list"), 0);
9073                 return;
9074             }
9075             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9076             if (boardIndex > forwardMostMove) {
9077                 forwardMostMove = boardIndex;
9078             }
9079             return;
9080           case ElapsedTime:
9081             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9082                 strcat(parseList[boardIndex-1], " ");
9083                 strcat(parseList[boardIndex-1], yy_text);
9084             }
9085             continue;
9086           case Comment:
9087           case PGNTag:
9088           case NAG:
9089           default:
9090             /* ignore */
9091             continue;
9092           case WhiteWins:
9093           case BlackWins:
9094           case GameIsDrawn:
9095           case GameUnfinished:
9096             if (gameMode == IcsExamining) {
9097                 if (boardIndex < backwardMostMove) {
9098                     /* Oops, gap.  How did that happen? */
9099                     return;
9100                 }
9101                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9102                 return;
9103             }
9104             gameInfo.result = moveType;
9105             p = strchr(yy_text, '{');
9106             if (p == NULL) p = strchr(yy_text, '(');
9107             if (p == NULL) {
9108                 p = yy_text;
9109                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9110             } else {
9111                 q = strchr(p, *p == '{' ? '}' : ')');
9112                 if (q != NULL) *q = NULLCHAR;
9113                 p++;
9114             }
9115             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9116             gameInfo.resultDetails = StrSave(p);
9117             continue;
9118         }
9119         if (boardIndex >= forwardMostMove &&
9120             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9121             backwardMostMove = blackPlaysFirst ? 1 : 0;
9122             return;
9123         }
9124         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9125                                  fromY, fromX, toY, toX, promoChar,
9126                                  parseList[boardIndex]);
9127         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9128         /* currentMoveString is set as a side-effect of yylex */
9129         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9130         strcat(moveList[boardIndex], "\n");
9131         boardIndex++;
9132         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9133         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9134           case MT_NONE:
9135           case MT_STALEMATE:
9136           default:
9137             break;
9138           case MT_CHECK:
9139             if(gameInfo.variant != VariantShogi)
9140                 strcat(parseList[boardIndex - 1], "+");
9141             break;
9142           case MT_CHECKMATE:
9143           case MT_STAINMATE:
9144             strcat(parseList[boardIndex - 1], "#");
9145             break;
9146         }
9147     }
9148 }
9149
9150
9151 /* Apply a move to the given board  */
9152 void
9153 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9154 {
9155   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9156   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9157
9158     /* [HGM] compute & store e.p. status and castling rights for new position */
9159     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9160
9161       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9162       oldEP = (signed char)board[EP_STATUS];
9163       board[EP_STATUS] = EP_NONE;
9164
9165   if (fromY == DROP_RANK) {
9166         /* must be first */
9167         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9168             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9169             return;
9170         }
9171         piece = board[toY][toX] = (ChessSquare) fromX;
9172   } else {
9173       int i;
9174
9175       if( board[toY][toX] != EmptySquare )
9176            board[EP_STATUS] = EP_CAPTURE;
9177
9178       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9179            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9180                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9181       } else
9182       if( board[fromY][fromX] == WhitePawn ) {
9183            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9184                board[EP_STATUS] = EP_PAWN_MOVE;
9185            if( toY-fromY==2) {
9186                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9187                         gameInfo.variant != VariantBerolina || toX < fromX)
9188                       board[EP_STATUS] = toX | berolina;
9189                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9190                         gameInfo.variant != VariantBerolina || toX > fromX)
9191                       board[EP_STATUS] = toX;
9192            }
9193       } else
9194       if( board[fromY][fromX] == BlackPawn ) {
9195            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9196                board[EP_STATUS] = EP_PAWN_MOVE;
9197            if( toY-fromY== -2) {
9198                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9199                         gameInfo.variant != VariantBerolina || toX < fromX)
9200                       board[EP_STATUS] = toX | berolina;
9201                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9202                         gameInfo.variant != VariantBerolina || toX > fromX)
9203                       board[EP_STATUS] = toX;
9204            }
9205        }
9206
9207        for(i=0; i<nrCastlingRights; i++) {
9208            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9209               board[CASTLING][i] == toX   && castlingRank[i] == toY
9210              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9211        }
9212
9213      if (fromX == toX && fromY == toY) return;
9214
9215      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9216      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9217      if(gameInfo.variant == VariantKnightmate)
9218          king += (int) WhiteUnicorn - (int) WhiteKing;
9219
9220     /* Code added by Tord: */
9221     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9222     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9223         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9224       board[fromY][fromX] = EmptySquare;
9225       board[toY][toX] = EmptySquare;
9226       if((toX > fromX) != (piece == WhiteRook)) {
9227         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9228       } else {
9229         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9230       }
9231     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9232                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9233       board[fromY][fromX] = EmptySquare;
9234       board[toY][toX] = EmptySquare;
9235       if((toX > fromX) != (piece == BlackRook)) {
9236         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9237       } else {
9238         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9239       }
9240     /* End of code added by Tord */
9241
9242     } else if (board[fromY][fromX] == king
9243         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9244         && toY == fromY && toX > fromX+1) {
9245         board[fromY][fromX] = EmptySquare;
9246         board[toY][toX] = king;
9247         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9248         board[fromY][BOARD_RGHT-1] = EmptySquare;
9249     } else if (board[fromY][fromX] == king
9250         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9251                && toY == fromY && toX < fromX-1) {
9252         board[fromY][fromX] = EmptySquare;
9253         board[toY][toX] = king;
9254         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9255         board[fromY][BOARD_LEFT] = EmptySquare;
9256     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9257                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9258                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9259                ) {
9260         /* white pawn promotion */
9261         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9262         if(gameInfo.variant==VariantBughouse ||
9263            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9264             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9265         board[fromY][fromX] = EmptySquare;
9266     } else if ((fromY >= BOARD_HEIGHT>>1)
9267                && (toX != fromX)
9268                && gameInfo.variant != VariantXiangqi
9269                && gameInfo.variant != VariantBerolina
9270                && (board[fromY][fromX] == WhitePawn)
9271                && (board[toY][toX] == EmptySquare)) {
9272         board[fromY][fromX] = EmptySquare;
9273         board[toY][toX] = WhitePawn;
9274         captured = board[toY - 1][toX];
9275         board[toY - 1][toX] = EmptySquare;
9276     } else if ((fromY == BOARD_HEIGHT-4)
9277                && (toX == fromX)
9278                && gameInfo.variant == VariantBerolina
9279                && (board[fromY][fromX] == WhitePawn)
9280                && (board[toY][toX] == EmptySquare)) {
9281         board[fromY][fromX] = EmptySquare;
9282         board[toY][toX] = WhitePawn;
9283         if(oldEP & EP_BEROLIN_A) {
9284                 captured = board[fromY][fromX-1];
9285                 board[fromY][fromX-1] = EmptySquare;
9286         }else{  captured = board[fromY][fromX+1];
9287                 board[fromY][fromX+1] = EmptySquare;
9288         }
9289     } else if (board[fromY][fromX] == king
9290         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9291                && toY == fromY && toX > fromX+1) {
9292         board[fromY][fromX] = EmptySquare;
9293         board[toY][toX] = king;
9294         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9295         board[fromY][BOARD_RGHT-1] = EmptySquare;
9296     } else if (board[fromY][fromX] == king
9297         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9298                && toY == fromY && toX < fromX-1) {
9299         board[fromY][fromX] = EmptySquare;
9300         board[toY][toX] = king;
9301         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9302         board[fromY][BOARD_LEFT] = EmptySquare;
9303     } else if (fromY == 7 && fromX == 3
9304                && board[fromY][fromX] == BlackKing
9305                && toY == 7 && toX == 5) {
9306         board[fromY][fromX] = EmptySquare;
9307         board[toY][toX] = BlackKing;
9308         board[fromY][7] = EmptySquare;
9309         board[toY][4] = BlackRook;
9310     } else if (fromY == 7 && fromX == 3
9311                && board[fromY][fromX] == BlackKing
9312                && toY == 7 && toX == 1) {
9313         board[fromY][fromX] = EmptySquare;
9314         board[toY][toX] = BlackKing;
9315         board[fromY][0] = EmptySquare;
9316         board[toY][2] = BlackRook;
9317     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9318                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9319                && toY < promoRank && promoChar
9320                ) {
9321         /* black pawn promotion */
9322         board[toY][toX] = CharToPiece(ToLower(promoChar));
9323         if(gameInfo.variant==VariantBughouse ||
9324            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9325             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9326         board[fromY][fromX] = EmptySquare;
9327     } else if ((fromY < BOARD_HEIGHT>>1)
9328                && (toX != fromX)
9329                && gameInfo.variant != VariantXiangqi
9330                && gameInfo.variant != VariantBerolina
9331                && (board[fromY][fromX] == BlackPawn)
9332                && (board[toY][toX] == EmptySquare)) {
9333         board[fromY][fromX] = EmptySquare;
9334         board[toY][toX] = BlackPawn;
9335         captured = board[toY + 1][toX];
9336         board[toY + 1][toX] = EmptySquare;
9337     } else if ((fromY == 3)
9338                && (toX == fromX)
9339                && gameInfo.variant == VariantBerolina
9340                && (board[fromY][fromX] == BlackPawn)
9341                && (board[toY][toX] == EmptySquare)) {
9342         board[fromY][fromX] = EmptySquare;
9343         board[toY][toX] = BlackPawn;
9344         if(oldEP & EP_BEROLIN_A) {
9345                 captured = board[fromY][fromX-1];
9346                 board[fromY][fromX-1] = EmptySquare;
9347         }else{  captured = board[fromY][fromX+1];
9348                 board[fromY][fromX+1] = EmptySquare;
9349         }
9350     } else {
9351         board[toY][toX] = board[fromY][fromX];
9352         board[fromY][fromX] = EmptySquare;
9353     }
9354   }
9355
9356     if (gameInfo.holdingsWidth != 0) {
9357
9358       /* !!A lot more code needs to be written to support holdings  */
9359       /* [HGM] OK, so I have written it. Holdings are stored in the */
9360       /* penultimate board files, so they are automaticlly stored   */
9361       /* in the game history.                                       */
9362       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9363                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9364         /* Delete from holdings, by decreasing count */
9365         /* and erasing image if necessary            */
9366         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9367         if(p < (int) BlackPawn) { /* white drop */
9368              p -= (int)WhitePawn;
9369                  p = PieceToNumber((ChessSquare)p);
9370              if(p >= gameInfo.holdingsSize) p = 0;
9371              if(--board[p][BOARD_WIDTH-2] <= 0)
9372                   board[p][BOARD_WIDTH-1] = EmptySquare;
9373              if((int)board[p][BOARD_WIDTH-2] < 0)
9374                         board[p][BOARD_WIDTH-2] = 0;
9375         } else {                  /* black drop */
9376              p -= (int)BlackPawn;
9377                  p = PieceToNumber((ChessSquare)p);
9378              if(p >= gameInfo.holdingsSize) p = 0;
9379              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9380                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9381              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9382                         board[BOARD_HEIGHT-1-p][1] = 0;
9383         }
9384       }
9385       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9386           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9387         /* [HGM] holdings: Add to holdings, if holdings exist */
9388         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9389                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9390                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9391         }
9392         p = (int) captured;
9393         if (p >= (int) BlackPawn) {
9394           p -= (int)BlackPawn;
9395           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9396                   /* in Shogi restore piece to its original  first */
9397                   captured = (ChessSquare) (DEMOTED captured);
9398                   p = DEMOTED p;
9399           }
9400           p = PieceToNumber((ChessSquare)p);
9401           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9402           board[p][BOARD_WIDTH-2]++;
9403           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9404         } else {
9405           p -= (int)WhitePawn;
9406           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9407                   captured = (ChessSquare) (DEMOTED captured);
9408                   p = DEMOTED p;
9409           }
9410           p = PieceToNumber((ChessSquare)p);
9411           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9412           board[BOARD_HEIGHT-1-p][1]++;
9413           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9414         }
9415       }
9416     } else if (gameInfo.variant == VariantAtomic) {
9417       if (captured != EmptySquare) {
9418         int y, x;
9419         for (y = toY-1; y <= toY+1; y++) {
9420           for (x = toX-1; x <= toX+1; x++) {
9421             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9422                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9423               board[y][x] = EmptySquare;
9424             }
9425           }
9426         }
9427         board[toY][toX] = EmptySquare;
9428       }
9429     }
9430     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9431         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9432     } else
9433     if(promoChar == '+') {
9434         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9435         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9436     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9437         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9438     }
9439     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9440                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9441         // [HGM] superchess: take promotion piece out of holdings
9442         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9443         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9444             if(!--board[k][BOARD_WIDTH-2])
9445                 board[k][BOARD_WIDTH-1] = EmptySquare;
9446         } else {
9447             if(!--board[BOARD_HEIGHT-1-k][1])
9448                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9449         }
9450     }
9451
9452 }
9453
9454 /* Updates forwardMostMove */
9455 void
9456 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9457 {
9458 //    forwardMostMove++; // [HGM] bare: moved downstream
9459
9460     (void) CoordsToAlgebraic(boards[forwardMostMove],
9461                              PosFlags(forwardMostMove),
9462                              fromY, fromX, toY, toX, promoChar,
9463                              parseList[forwardMostMove]);
9464
9465     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9466         int timeLeft; static int lastLoadFlag=0; int king, piece;
9467         piece = boards[forwardMostMove][fromY][fromX];
9468         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9469         if(gameInfo.variant == VariantKnightmate)
9470             king += (int) WhiteUnicorn - (int) WhiteKing;
9471         if(forwardMostMove == 0) {
9472             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9473                 fprintf(serverMoves, "%s;", UserName());
9474             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9475                 fprintf(serverMoves, "%s;", second.tidy);
9476             fprintf(serverMoves, "%s;", first.tidy);
9477             if(gameMode == MachinePlaysWhite)
9478                 fprintf(serverMoves, "%s;", UserName());
9479             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9480                 fprintf(serverMoves, "%s;", second.tidy);
9481         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9482         lastLoadFlag = loadFlag;
9483         // print base move
9484         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9485         // print castling suffix
9486         if( toY == fromY && piece == king ) {
9487             if(toX-fromX > 1)
9488                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9489             if(fromX-toX >1)
9490                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9491         }
9492         // e.p. suffix
9493         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9494              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9495              boards[forwardMostMove][toY][toX] == EmptySquare
9496              && fromX != toX && fromY != toY)
9497                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9498         // promotion suffix
9499         if(promoChar != NULLCHAR)
9500                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9501         if(!loadFlag) {
9502                 char buf[MOVE_LEN*2], *p; int len;
9503             fprintf(serverMoves, "/%d/%d",
9504                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9505             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9506             else                      timeLeft = blackTimeRemaining/1000;
9507             fprintf(serverMoves, "/%d", timeLeft);
9508                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9509                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9510                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9511             fprintf(serverMoves, "/%s", buf);
9512         }
9513         fflush(serverMoves);
9514     }
9515
9516     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9517         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9518       return;
9519     }
9520     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9521     if (commentList[forwardMostMove+1] != NULL) {
9522         free(commentList[forwardMostMove+1]);
9523         commentList[forwardMostMove+1] = NULL;
9524     }
9525     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9526     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9527     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9528     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9529     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9530     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9531     adjustedClock = FALSE;
9532     gameInfo.result = GameUnfinished;
9533     if (gameInfo.resultDetails != NULL) {
9534         free(gameInfo.resultDetails);
9535         gameInfo.resultDetails = NULL;
9536     }
9537     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9538                               moveList[forwardMostMove - 1]);
9539     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9540       case MT_NONE:
9541       case MT_STALEMATE:
9542       default:
9543         break;
9544       case MT_CHECK:
9545         if(gameInfo.variant != VariantShogi)
9546             strcat(parseList[forwardMostMove - 1], "+");
9547         break;
9548       case MT_CHECKMATE:
9549       case MT_STAINMATE:
9550         strcat(parseList[forwardMostMove - 1], "#");
9551         break;
9552     }
9553
9554 }
9555
9556 /* Updates currentMove if not pausing */
9557 void
9558 ShowMove (int fromX, int fromY, int toX, int toY)
9559 {
9560     int instant = (gameMode == PlayFromGameFile) ?
9561         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9562     if(appData.noGUI) return;
9563     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9564         if (!instant) {
9565             if (forwardMostMove == currentMove + 1) {
9566                 AnimateMove(boards[forwardMostMove - 1],
9567                             fromX, fromY, toX, toY);
9568             }
9569             if (appData.highlightLastMove) {
9570                 SetHighlights(fromX, fromY, toX, toY);
9571             }
9572         }
9573         currentMove = forwardMostMove;
9574     }
9575
9576     if (instant) return;
9577
9578     DisplayMove(currentMove - 1);
9579     DrawPosition(FALSE, boards[currentMove]);
9580     DisplayBothClocks();
9581     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9582 }
9583
9584 void
9585 SendEgtPath (ChessProgramState *cps)
9586 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9587         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9588
9589         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9590
9591         while(*p) {
9592             char c, *q = name+1, *r, *s;
9593
9594             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9595             while(*p && *p != ',') *q++ = *p++;
9596             *q++ = ':'; *q = 0;
9597             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9598                 strcmp(name, ",nalimov:") == 0 ) {
9599                 // take nalimov path from the menu-changeable option first, if it is defined
9600               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9601                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9602             } else
9603             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9604                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9605                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9606                 s = r = StrStr(s, ":") + 1; // beginning of path info
9607                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9608                 c = *r; *r = 0;             // temporarily null-terminate path info
9609                     *--q = 0;               // strip of trailig ':' from name
9610                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9611                 *r = c;
9612                 SendToProgram(buf,cps);     // send egtbpath command for this format
9613             }
9614             if(*p == ',') p++; // read away comma to position for next format name
9615         }
9616 }
9617
9618 void
9619 InitChessProgram (ChessProgramState *cps, int setup)
9620 /* setup needed to setup FRC opening position */
9621 {
9622     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9623     if (appData.noChessProgram) return;
9624     hintRequested = FALSE;
9625     bookRequested = FALSE;
9626
9627     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9628     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9629     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9630     if(cps->memSize) { /* [HGM] memory */
9631       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9632         SendToProgram(buf, cps);
9633     }
9634     SendEgtPath(cps); /* [HGM] EGT */
9635     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9636       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9637         SendToProgram(buf, cps);
9638     }
9639
9640     SendToProgram(cps->initString, cps);
9641     if (gameInfo.variant != VariantNormal &&
9642         gameInfo.variant != VariantLoadable
9643         /* [HGM] also send variant if board size non-standard */
9644         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9645                                             ) {
9646       char *v = VariantName(gameInfo.variant);
9647       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9648         /* [HGM] in protocol 1 we have to assume all variants valid */
9649         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9650         DisplayFatalError(buf, 0, 1);
9651         return;
9652       }
9653
9654       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9655       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9656       if( gameInfo.variant == VariantXiangqi )
9657            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9658       if( gameInfo.variant == VariantShogi )
9659            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9660       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9661            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9662       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9663           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9664            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9665       if( gameInfo.variant == VariantCourier )
9666            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9667       if( gameInfo.variant == VariantSuper )
9668            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9669       if( gameInfo.variant == VariantGreat )
9670            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9671       if( gameInfo.variant == VariantSChess )
9672            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9673       if( gameInfo.variant == VariantGrand )
9674            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9675
9676       if(overruled) {
9677         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9678                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9679            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9680            if(StrStr(cps->variants, b) == NULL) {
9681                // specific sized variant not known, check if general sizing allowed
9682                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9683                    if(StrStr(cps->variants, "boardsize") == NULL) {
9684                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9685                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9686                        DisplayFatalError(buf, 0, 1);
9687                        return;
9688                    }
9689                    /* [HGM] here we really should compare with the maximum supported board size */
9690                }
9691            }
9692       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9693       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9694       SendToProgram(buf, cps);
9695     }
9696     currentlyInitializedVariant = gameInfo.variant;
9697
9698     /* [HGM] send opening position in FRC to first engine */
9699     if(setup) {
9700           SendToProgram("force\n", cps);
9701           SendBoard(cps, 0);
9702           /* engine is now in force mode! Set flag to wake it up after first move. */
9703           setboardSpoiledMachineBlack = 1;
9704     }
9705
9706     if (cps->sendICS) {
9707       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9708       SendToProgram(buf, cps);
9709     }
9710     cps->maybeThinking = FALSE;
9711     cps->offeredDraw = 0;
9712     if (!appData.icsActive) {
9713         SendTimeControl(cps, movesPerSession, timeControl,
9714                         timeIncrement, appData.searchDepth,
9715                         searchTime);
9716     }
9717     if (appData.showThinking
9718         // [HGM] thinking: four options require thinking output to be sent
9719         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9720                                 ) {
9721         SendToProgram("post\n", cps);
9722     }
9723     SendToProgram("hard\n", cps);
9724     if (!appData.ponderNextMove) {
9725         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9726            it without being sure what state we are in first.  "hard"
9727            is not a toggle, so that one is OK.
9728          */
9729         SendToProgram("easy\n", cps);
9730     }
9731     if (cps->usePing) {
9732       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9733       SendToProgram(buf, cps);
9734     }
9735     cps->initDone = TRUE;
9736     ClearEngineOutputPane(cps == &second);
9737 }
9738
9739
9740 void
9741 StartChessProgram (ChessProgramState *cps)
9742 {
9743     char buf[MSG_SIZ];
9744     int err;
9745
9746     if (appData.noChessProgram) return;
9747     cps->initDone = FALSE;
9748
9749     if (strcmp(cps->host, "localhost") == 0) {
9750         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9751     } else if (*appData.remoteShell == NULLCHAR) {
9752         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9753     } else {
9754         if (*appData.remoteUser == NULLCHAR) {
9755           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9756                     cps->program);
9757         } else {
9758           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9759                     cps->host, appData.remoteUser, cps->program);
9760         }
9761         err = StartChildProcess(buf, "", &cps->pr);
9762     }
9763
9764     if (err != 0) {
9765       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9766         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9767         if(cps != &first) return;
9768         appData.noChessProgram = TRUE;
9769         ThawUI();
9770         SetNCPMode();
9771 //      DisplayFatalError(buf, err, 1);
9772 //      cps->pr = NoProc;
9773 //      cps->isr = NULL;
9774         return;
9775     }
9776
9777     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9778     if (cps->protocolVersion > 1) {
9779       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9780       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9781       cps->comboCnt = 0;  //                and values of combo boxes
9782       SendToProgram(buf, cps);
9783     } else {
9784       SendToProgram("xboard\n", cps);
9785     }
9786 }
9787
9788 void
9789 TwoMachinesEventIfReady P((void))
9790 {
9791   static int curMess = 0;
9792   if (first.lastPing != first.lastPong) {
9793     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9794     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9795     return;
9796   }
9797   if (second.lastPing != second.lastPong) {
9798     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9799     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9800     return;
9801   }
9802   DisplayMessage("", ""); curMess = 0;
9803   ThawUI();
9804   TwoMachinesEvent();
9805 }
9806
9807 char *
9808 MakeName (char *template)
9809 {
9810     time_t clock;
9811     struct tm *tm;
9812     static char buf[MSG_SIZ];
9813     char *p = buf;
9814     int i;
9815
9816     clock = time((time_t *)NULL);
9817     tm = localtime(&clock);
9818
9819     while(*p++ = *template++) if(p[-1] == '%') {
9820         switch(*template++) {
9821           case 0:   *p = 0; return buf;
9822           case 'Y': i = tm->tm_year+1900; break;
9823           case 'y': i = tm->tm_year-100; break;
9824           case 'M': i = tm->tm_mon+1; break;
9825           case 'd': i = tm->tm_mday; break;
9826           case 'h': i = tm->tm_hour; break;
9827           case 'm': i = tm->tm_min; break;
9828           case 's': i = tm->tm_sec; break;
9829           default:  i = 0;
9830         }
9831         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9832     }
9833     return buf;
9834 }
9835
9836 int
9837 CountPlayers (char *p)
9838 {
9839     int n = 0;
9840     while(p = strchr(p, '\n')) p++, n++; // count participants
9841     return n;
9842 }
9843
9844 FILE *
9845 WriteTourneyFile (char *results, FILE *f)
9846 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9847     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9848     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9849         // create a file with tournament description
9850         fprintf(f, "-participants {%s}\n", appData.participants);
9851         fprintf(f, "-seedBase %d\n", appData.seedBase);
9852         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9853         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9854         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9855         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9856         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9857         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9858         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9859         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9860         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9861         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9862         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9863         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9864         if(searchTime > 0)
9865                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9866         else {
9867                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9868                 fprintf(f, "-tc %s\n", appData.timeControl);
9869                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9870         }
9871         fprintf(f, "-results \"%s\"\n", results);
9872     }
9873     return f;
9874 }
9875
9876 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9877
9878 void
9879 Substitute (char *participants, int expunge)
9880 {
9881     int i, changed, changes=0, nPlayers=0;
9882     char *p, *q, *r, buf[MSG_SIZ];
9883     if(participants == NULL) return;
9884     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9885     r = p = participants; q = appData.participants;
9886     while(*p && *p == *q) {
9887         if(*p == '\n') r = p+1, nPlayers++;
9888         p++; q++;
9889     }
9890     if(*p) { // difference
9891         while(*p && *p++ != '\n');
9892         while(*q && *q++ != '\n');
9893       changed = nPlayers;
9894         changes = 1 + (strcmp(p, q) != 0);
9895     }
9896     if(changes == 1) { // a single engine mnemonic was changed
9897         q = r; while(*q) nPlayers += (*q++ == '\n');
9898         p = buf; while(*r && (*p = *r++) != '\n') p++;
9899         *p = NULLCHAR;
9900         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9901         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9902         if(mnemonic[i]) { // The substitute is valid
9903             FILE *f;
9904             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9905                 flock(fileno(f), LOCK_EX);
9906                 ParseArgsFromFile(f);
9907                 fseek(f, 0, SEEK_SET);
9908                 FREE(appData.participants); appData.participants = participants;
9909                 if(expunge) { // erase results of replaced engine
9910                     int len = strlen(appData.results), w, b, dummy;
9911                     for(i=0; i<len; i++) {
9912                         Pairing(i, nPlayers, &w, &b, &dummy);
9913                         if((w == changed || b == changed) && appData.results[i] == '*') {
9914                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9915                             fclose(f);
9916                             return;
9917                         }
9918                     }
9919                     for(i=0; i<len; i++) {
9920                         Pairing(i, nPlayers, &w, &b, &dummy);
9921                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9922                     }
9923                 }
9924                 WriteTourneyFile(appData.results, f);
9925                 fclose(f); // release lock
9926                 return;
9927             }
9928         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9929     }
9930     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9931     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9932     free(participants);
9933     return;
9934 }
9935
9936 int
9937 CreateTourney (char *name)
9938 {
9939         FILE *f;
9940         if(matchMode && strcmp(name, appData.tourneyFile)) {
9941              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9942         }
9943         if(name[0] == NULLCHAR) {
9944             if(appData.participants[0])
9945                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9946             return 0;
9947         }
9948         f = fopen(name, "r");
9949         if(f) { // file exists
9950             ASSIGN(appData.tourneyFile, name);
9951             ParseArgsFromFile(f); // parse it
9952         } else {
9953             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9954             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9955                 DisplayError(_("Not enough participants"), 0);
9956                 return 0;
9957             }
9958             ASSIGN(appData.tourneyFile, name);
9959             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9960             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9961         }
9962         fclose(f);
9963         appData.noChessProgram = FALSE;
9964         appData.clockMode = TRUE;
9965         SetGNUMode();
9966         return 1;
9967 }
9968
9969 int
9970 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9971 {
9972     char buf[MSG_SIZ], *p, *q;
9973     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9974     skip = !all && group[0]; // if group requested, we start in skip mode
9975     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9976         p = names; q = buf; header = 0;
9977         while(*p && *p != '\n') *q++ = *p++;
9978         *q = 0;
9979         if(*p == '\n') p++;
9980         if(buf[0] == '#') {
9981             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9982             depth++; // we must be entering a new group
9983             if(all) continue; // suppress printing group headers when complete list requested
9984             header = 1;
9985             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9986         }
9987         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9988         if(engineList[i]) free(engineList[i]);
9989         engineList[i] = strdup(buf);
9990         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9991         if(engineMnemonic[i]) free(engineMnemonic[i]);
9992         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9993             strcat(buf, " (");
9994             sscanf(q + 8, "%s", buf + strlen(buf));
9995             strcat(buf, ")");
9996         }
9997         engineMnemonic[i] = strdup(buf);
9998         i++;
9999     }
10000     engineList[i] = engineMnemonic[i] = NULL;
10001     return i;
10002 }
10003
10004 // following implemented as macro to avoid type limitations
10005 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10006
10007 void
10008 SwapEngines (int n)
10009 {   // swap settings for first engine and other engine (so far only some selected options)
10010     int h;
10011     char *p;
10012     if(n == 0) return;
10013     SWAP(directory, p)
10014     SWAP(chessProgram, p)
10015     SWAP(isUCI, h)
10016     SWAP(hasOwnBookUCI, h)
10017     SWAP(protocolVersion, h)
10018     SWAP(reuse, h)
10019     SWAP(scoreIsAbsolute, h)
10020     SWAP(timeOdds, h)
10021     SWAP(logo, p)
10022     SWAP(pgnName, p)
10023     SWAP(pvSAN, h)
10024     SWAP(engOptions, p)
10025 }
10026
10027 int
10028 SetPlayer (int player, char *p)
10029 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10030     int i;
10031     char buf[MSG_SIZ], *engineName;
10032     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10033     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10034     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10035     if(mnemonic[i]) {
10036         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10037         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10038         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10039         ParseArgsFromString(buf);
10040     }
10041     free(engineName);
10042     return i;
10043 }
10044
10045 char *recentEngines;
10046
10047 void
10048 RecentEngineEvent (int nr)
10049 {
10050     int n;
10051 //    SwapEngines(1); // bump first to second
10052 //    ReplaceEngine(&second, 1); // and load it there
10053     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10054     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10055     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10056         ReplaceEngine(&first, 0);
10057         FloatToFront(&appData.recentEngineList, command[n]);
10058     }
10059 }
10060
10061 int
10062 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10063 {   // determine players from game number
10064     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10065
10066     if(appData.tourneyType == 0) {
10067         roundsPerCycle = (nPlayers - 1) | 1;
10068         pairingsPerRound = nPlayers / 2;
10069     } else if(appData.tourneyType > 0) {
10070         roundsPerCycle = nPlayers - appData.tourneyType;
10071         pairingsPerRound = appData.tourneyType;
10072     }
10073     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10074     gamesPerCycle = gamesPerRound * roundsPerCycle;
10075     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10076     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10077     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10078     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10079     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10080     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10081
10082     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10083     if(appData.roundSync) *syncInterval = gamesPerRound;
10084
10085     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10086
10087     if(appData.tourneyType == 0) {
10088         if(curPairing == (nPlayers-1)/2 ) {
10089             *whitePlayer = curRound;
10090             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10091         } else {
10092             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10093             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10094             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10095             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10096         }
10097     } else if(appData.tourneyType > 1) {
10098         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10099         *whitePlayer = curRound + appData.tourneyType;
10100     } else if(appData.tourneyType > 0) {
10101         *whitePlayer = curPairing;
10102         *blackPlayer = curRound + appData.tourneyType;
10103     }
10104
10105     // take care of white/black alternation per round. 
10106     // For cycles and games this is already taken care of by default, derived from matchGame!
10107     return curRound & 1;
10108 }
10109
10110 int
10111 NextTourneyGame (int nr, int *swapColors)
10112 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10113     char *p, *q;
10114     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10115     FILE *tf;
10116     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10117     tf = fopen(appData.tourneyFile, "r");
10118     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10119     ParseArgsFromFile(tf); fclose(tf);
10120     InitTimeControls(); // TC might be altered from tourney file
10121
10122     nPlayers = CountPlayers(appData.participants); // count participants
10123     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10124     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10125
10126     if(syncInterval) {
10127         p = q = appData.results;
10128         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10129         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10130             DisplayMessage(_("Waiting for other game(s)"),"");
10131             waitingForGame = TRUE;
10132             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10133             return 0;
10134         }
10135         waitingForGame = FALSE;
10136     }
10137
10138     if(appData.tourneyType < 0) {
10139         if(nr>=0 && !pairingReceived) {
10140             char buf[1<<16];
10141             if(pairing.pr == NoProc) {
10142                 if(!appData.pairingEngine[0]) {
10143                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10144                     return 0;
10145                 }
10146                 StartChessProgram(&pairing); // starts the pairing engine
10147             }
10148             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10149             SendToProgram(buf, &pairing);
10150             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10151             SendToProgram(buf, &pairing);
10152             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10153         }
10154         pairingReceived = 0;                              // ... so we continue here 
10155         *swapColors = 0;
10156         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10157         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10158         matchGame = 1; roundNr = nr / syncInterval + 1;
10159     }
10160
10161     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10162
10163     // redefine engines, engine dir, etc.
10164     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10165     if(first.pr == NoProc) {
10166       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10167       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10168     }
10169     if(second.pr == NoProc) {
10170       SwapEngines(1);
10171       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10172       SwapEngines(1);         // and make that valid for second engine by swapping
10173       InitEngine(&second, 1);
10174     }
10175     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10176     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10177     return 1;
10178 }
10179
10180 void
10181 NextMatchGame ()
10182 {   // performs game initialization that does not invoke engines, and then tries to start the game
10183     int res, firstWhite, swapColors = 0;
10184     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10185     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
10186         char buf[MSG_SIZ];
10187         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10188         if(strcmp(buf, currentDebugFile)) { // name has changed
10189             FILE *f = fopen(buf, "w");
10190             if(f) { // if opening the new file failed, just keep using the old one
10191                 ASSIGN(currentDebugFile, buf);
10192                 fclose(debugFP);
10193                 debugFP = f;
10194             }
10195             if(appData.serverFileName) {
10196                 if(serverFP) fclose(serverFP);
10197                 serverFP = fopen(appData.serverFileName, "w");
10198                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10199                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10200             }
10201         }
10202     }
10203     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10204     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10205     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10206     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10207     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10208     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10209     Reset(FALSE, first.pr != NoProc);
10210     res = LoadGameOrPosition(matchGame); // setup game
10211     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10212     if(!res) return; // abort when bad game/pos file
10213     TwoMachinesEvent();
10214 }
10215
10216 void
10217 UserAdjudicationEvent (int result)
10218 {
10219     ChessMove gameResult = GameIsDrawn;
10220
10221     if( result > 0 ) {
10222         gameResult = WhiteWins;
10223     }
10224     else if( result < 0 ) {
10225         gameResult = BlackWins;
10226     }
10227
10228     if( gameMode == TwoMachinesPlay ) {
10229         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10230     }
10231 }
10232
10233
10234 // [HGM] save: calculate checksum of game to make games easily identifiable
10235 int
10236 StringCheckSum (char *s)
10237 {
10238         int i = 0;
10239         if(s==NULL) return 0;
10240         while(*s) i = i*259 + *s++;
10241         return i;
10242 }
10243
10244 int
10245 GameCheckSum ()
10246 {
10247         int i, sum=0;
10248         for(i=backwardMostMove; i<forwardMostMove; i++) {
10249                 sum += pvInfoList[i].depth;
10250                 sum += StringCheckSum(parseList[i]);
10251                 sum += StringCheckSum(commentList[i]);
10252                 sum *= 261;
10253         }
10254         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10255         return sum + StringCheckSum(commentList[i]);
10256 } // end of save patch
10257
10258 void
10259 GameEnds (ChessMove result, char *resultDetails, int whosays)
10260 {
10261     GameMode nextGameMode;
10262     int isIcsGame;
10263     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10264
10265     if(endingGame) return; /* [HGM] crash: forbid recursion */
10266     endingGame = 1;
10267     if(twoBoards) { // [HGM] dual: switch back to one board
10268         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10269         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10270     }
10271     if (appData.debugMode) {
10272       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10273               result, resultDetails ? resultDetails : "(null)", whosays);
10274     }
10275
10276     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10277
10278     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10279         /* If we are playing on ICS, the server decides when the
10280            game is over, but the engine can offer to draw, claim
10281            a draw, or resign.
10282          */
10283 #if ZIPPY
10284         if (appData.zippyPlay && first.initDone) {
10285             if (result == GameIsDrawn) {
10286                 /* In case draw still needs to be claimed */
10287                 SendToICS(ics_prefix);
10288                 SendToICS("draw\n");
10289             } else if (StrCaseStr(resultDetails, "resign")) {
10290                 SendToICS(ics_prefix);
10291                 SendToICS("resign\n");
10292             }
10293         }
10294 #endif
10295         endingGame = 0; /* [HGM] crash */
10296         return;
10297     }
10298
10299     /* If we're loading the game from a file, stop */
10300     if (whosays == GE_FILE) {
10301       (void) StopLoadGameTimer();
10302       gameFileFP = NULL;
10303     }
10304
10305     /* Cancel draw offers */
10306     first.offeredDraw = second.offeredDraw = 0;
10307
10308     /* If this is an ICS game, only ICS can really say it's done;
10309        if not, anyone can. */
10310     isIcsGame = (gameMode == IcsPlayingWhite ||
10311                  gameMode == IcsPlayingBlack ||
10312                  gameMode == IcsObserving    ||
10313                  gameMode == IcsExamining);
10314
10315     if (!isIcsGame || whosays == GE_ICS) {
10316         /* OK -- not an ICS game, or ICS said it was done */
10317         StopClocks();
10318         if (!isIcsGame && !appData.noChessProgram)
10319           SetUserThinkingEnables();
10320
10321         /* [HGM] if a machine claims the game end we verify this claim */
10322         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10323             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10324                 char claimer;
10325                 ChessMove trueResult = (ChessMove) -1;
10326
10327                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10328                                             first.twoMachinesColor[0] :
10329                                             second.twoMachinesColor[0] ;
10330
10331                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10332                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10333                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10334                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10335                 } else
10336                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10337                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10338                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10339                 } else
10340                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10341                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10342                 }
10343
10344                 // now verify win claims, but not in drop games, as we don't understand those yet
10345                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10346                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10347                     (result == WhiteWins && claimer == 'w' ||
10348                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10349                       if (appData.debugMode) {
10350                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10351                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10352                       }
10353                       if(result != trueResult) {
10354                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10355                               result = claimer == 'w' ? BlackWins : WhiteWins;
10356                               resultDetails = buf;
10357                       }
10358                 } else
10359                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10360                     && (forwardMostMove <= backwardMostMove ||
10361                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10362                         (claimer=='b')==(forwardMostMove&1))
10363                                                                                   ) {
10364                       /* [HGM] verify: draws that were not flagged are false claims */
10365                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10366                       result = claimer == 'w' ? BlackWins : WhiteWins;
10367                       resultDetails = buf;
10368                 }
10369                 /* (Claiming a loss is accepted no questions asked!) */
10370             }
10371             /* [HGM] bare: don't allow bare King to win */
10372             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10373                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10374                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10375                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10376                && result != GameIsDrawn)
10377             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10378                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10379                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10380                         if(p >= 0 && p <= (int)WhiteKing) k++;
10381                 }
10382                 if (appData.debugMode) {
10383                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10384                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10385                 }
10386                 if(k <= 1) {
10387                         result = GameIsDrawn;
10388                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10389                         resultDetails = buf;
10390                 }
10391             }
10392         }
10393
10394
10395         if(serverMoves != NULL && !loadFlag) { char c = '=';
10396             if(result==WhiteWins) c = '+';
10397             if(result==BlackWins) c = '-';
10398             if(resultDetails != NULL)
10399                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10400         }
10401         if (resultDetails != NULL) {
10402             gameInfo.result = result;
10403             gameInfo.resultDetails = StrSave(resultDetails);
10404
10405             /* display last move only if game was not loaded from file */
10406             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10407                 DisplayMove(currentMove - 1);
10408
10409             if (forwardMostMove != 0) {
10410                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10411                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10412                                                                 ) {
10413                     if (*appData.saveGameFile != NULLCHAR) {
10414                         SaveGameToFile(appData.saveGameFile, TRUE);
10415                     } else if (appData.autoSaveGames) {
10416                         AutoSaveGame();
10417                     }
10418                     if (*appData.savePositionFile != NULLCHAR) {
10419                         SavePositionToFile(appData.savePositionFile);
10420                     }
10421                 }
10422             }
10423
10424             /* Tell program how game ended in case it is learning */
10425             /* [HGM] Moved this to after saving the PGN, just in case */
10426             /* engine died and we got here through time loss. In that */
10427             /* case we will get a fatal error writing the pipe, which */
10428             /* would otherwise lose us the PGN.                       */
10429             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10430             /* output during GameEnds should never be fatal anymore   */
10431             if (gameMode == MachinePlaysWhite ||
10432                 gameMode == MachinePlaysBlack ||
10433                 gameMode == TwoMachinesPlay ||
10434                 gameMode == IcsPlayingWhite ||
10435                 gameMode == IcsPlayingBlack ||
10436                 gameMode == BeginningOfGame) {
10437                 char buf[MSG_SIZ];
10438                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10439                         resultDetails);
10440                 if (first.pr != NoProc) {
10441                     SendToProgram(buf, &first);
10442                 }
10443                 if (second.pr != NoProc &&
10444                     gameMode == TwoMachinesPlay) {
10445                     SendToProgram(buf, &second);
10446                 }
10447             }
10448         }
10449
10450         if (appData.icsActive) {
10451             if (appData.quietPlay &&
10452                 (gameMode == IcsPlayingWhite ||
10453                  gameMode == IcsPlayingBlack)) {
10454                 SendToICS(ics_prefix);
10455                 SendToICS("set shout 1\n");
10456             }
10457             nextGameMode = IcsIdle;
10458             ics_user_moved = FALSE;
10459             /* clean up premove.  It's ugly when the game has ended and the
10460              * premove highlights are still on the board.
10461              */
10462             if (gotPremove) {
10463               gotPremove = FALSE;
10464               ClearPremoveHighlights();
10465               DrawPosition(FALSE, boards[currentMove]);
10466             }
10467             if (whosays == GE_ICS) {
10468                 switch (result) {
10469                 case WhiteWins:
10470                     if (gameMode == IcsPlayingWhite)
10471                         PlayIcsWinSound();
10472                     else if(gameMode == IcsPlayingBlack)
10473                         PlayIcsLossSound();
10474                     break;
10475                 case BlackWins:
10476                     if (gameMode == IcsPlayingBlack)
10477                         PlayIcsWinSound();
10478                     else if(gameMode == IcsPlayingWhite)
10479                         PlayIcsLossSound();
10480                     break;
10481                 case GameIsDrawn:
10482                     PlayIcsDrawSound();
10483                     break;
10484                 default:
10485                     PlayIcsUnfinishedSound();
10486                 }
10487             }
10488         } else if (gameMode == EditGame ||
10489                    gameMode == PlayFromGameFile ||
10490                    gameMode == AnalyzeMode ||
10491                    gameMode == AnalyzeFile) {
10492             nextGameMode = gameMode;
10493         } else {
10494             nextGameMode = EndOfGame;
10495         }
10496         pausing = FALSE;
10497         ModeHighlight();
10498     } else {
10499         nextGameMode = gameMode;
10500     }
10501
10502     if (appData.noChessProgram) {
10503         gameMode = nextGameMode;
10504         ModeHighlight();
10505         endingGame = 0; /* [HGM] crash */
10506         return;
10507     }
10508
10509     if (first.reuse) {
10510         /* Put first chess program into idle state */
10511         if (first.pr != NoProc &&
10512             (gameMode == MachinePlaysWhite ||
10513              gameMode == MachinePlaysBlack ||
10514              gameMode == TwoMachinesPlay ||
10515              gameMode == IcsPlayingWhite ||
10516              gameMode == IcsPlayingBlack ||
10517              gameMode == BeginningOfGame)) {
10518             SendToProgram("force\n", &first);
10519             if (first.usePing) {
10520               char buf[MSG_SIZ];
10521               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10522               SendToProgram(buf, &first);
10523             }
10524         }
10525     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10526         /* Kill off first chess program */
10527         if (first.isr != NULL)
10528           RemoveInputSource(first.isr);
10529         first.isr = NULL;
10530
10531         if (first.pr != NoProc) {
10532             ExitAnalyzeMode();
10533             DoSleep( appData.delayBeforeQuit );
10534             SendToProgram("quit\n", &first);
10535             DoSleep( appData.delayAfterQuit );
10536             DestroyChildProcess(first.pr, first.useSigterm);
10537         }
10538         first.pr = NoProc;
10539     }
10540     if (second.reuse) {
10541         /* Put second chess program into idle state */
10542         if (second.pr != NoProc &&
10543             gameMode == TwoMachinesPlay) {
10544             SendToProgram("force\n", &second);
10545             if (second.usePing) {
10546               char buf[MSG_SIZ];
10547               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10548               SendToProgram(buf, &second);
10549             }
10550         }
10551     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10552         /* Kill off second chess program */
10553         if (second.isr != NULL)
10554           RemoveInputSource(second.isr);
10555         second.isr = NULL;
10556
10557         if (second.pr != NoProc) {
10558             DoSleep( appData.delayBeforeQuit );
10559             SendToProgram("quit\n", &second);
10560             DoSleep( appData.delayAfterQuit );
10561             DestroyChildProcess(second.pr, second.useSigterm);
10562         }
10563         second.pr = NoProc;
10564     }
10565
10566     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10567         char resChar = '=';
10568         switch (result) {
10569         case WhiteWins:
10570           resChar = '+';
10571           if (first.twoMachinesColor[0] == 'w') {
10572             first.matchWins++;
10573           } else {
10574             second.matchWins++;
10575           }
10576           break;
10577         case BlackWins:
10578           resChar = '-';
10579           if (first.twoMachinesColor[0] == 'b') {
10580             first.matchWins++;
10581           } else {
10582             second.matchWins++;
10583           }
10584           break;
10585         case GameUnfinished:
10586           resChar = ' ';
10587         default:
10588           break;
10589         }
10590
10591         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10592         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10593             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10594             ReserveGame(nextGame, resChar); // sets nextGame
10595             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10596             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10597         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10598
10599         if (nextGame <= appData.matchGames && !abortMatch) {
10600             gameMode = nextGameMode;
10601             matchGame = nextGame; // this will be overruled in tourney mode!
10602             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10603             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10604             endingGame = 0; /* [HGM] crash */
10605             return;
10606         } else {
10607             gameMode = nextGameMode;
10608             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10609                      first.tidy, second.tidy,
10610                      first.matchWins, second.matchWins,
10611                      appData.matchGames - (first.matchWins + second.matchWins));
10612             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10613             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10614             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10615             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10616                 first.twoMachinesColor = "black\n";
10617                 second.twoMachinesColor = "white\n";
10618             } else {
10619                 first.twoMachinesColor = "white\n";
10620                 second.twoMachinesColor = "black\n";
10621             }
10622         }
10623     }
10624     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10625         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10626       ExitAnalyzeMode();
10627     gameMode = nextGameMode;
10628     ModeHighlight();
10629     endingGame = 0;  /* [HGM] crash */
10630     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10631         if(matchMode == TRUE) { // match through command line: exit with or without popup
10632             if(ranking) {
10633                 ToNrEvent(forwardMostMove);
10634                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10635                 else ExitEvent(0);
10636             } else DisplayFatalError(buf, 0, 0);
10637         } else { // match through menu; just stop, with or without popup
10638             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10639             ModeHighlight();
10640             if(ranking){
10641                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10642             } else DisplayNote(buf);
10643       }
10644       if(ranking) free(ranking);
10645     }
10646 }
10647
10648 /* Assumes program was just initialized (initString sent).
10649    Leaves program in force mode. */
10650 void
10651 FeedMovesToProgram (ChessProgramState *cps, int upto)
10652 {
10653     int i;
10654
10655     if (appData.debugMode)
10656       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10657               startedFromSetupPosition ? "position and " : "",
10658               backwardMostMove, upto, cps->which);
10659     if(currentlyInitializedVariant != gameInfo.variant) {
10660       char buf[MSG_SIZ];
10661         // [HGM] variantswitch: make engine aware of new variant
10662         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10663                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10664         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10665         SendToProgram(buf, cps);
10666         currentlyInitializedVariant = gameInfo.variant;
10667     }
10668     SendToProgram("force\n", cps);
10669     if (startedFromSetupPosition) {
10670         SendBoard(cps, backwardMostMove);
10671     if (appData.debugMode) {
10672         fprintf(debugFP, "feedMoves\n");
10673     }
10674     }
10675     for (i = backwardMostMove; i < upto; i++) {
10676         SendMoveToProgram(i, cps);
10677     }
10678 }
10679
10680
10681 int
10682 ResurrectChessProgram ()
10683 {
10684      /* The chess program may have exited.
10685         If so, restart it and feed it all the moves made so far. */
10686     static int doInit = 0;
10687
10688     if (appData.noChessProgram) return 1;
10689
10690     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10691         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10692         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10693         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10694     } else {
10695         if (first.pr != NoProc) return 1;
10696         StartChessProgram(&first);
10697     }
10698     InitChessProgram(&first, FALSE);
10699     FeedMovesToProgram(&first, currentMove);
10700
10701     if (!first.sendTime) {
10702         /* can't tell gnuchess what its clock should read,
10703            so we bow to its notion. */
10704         ResetClocks();
10705         timeRemaining[0][currentMove] = whiteTimeRemaining;
10706         timeRemaining[1][currentMove] = blackTimeRemaining;
10707     }
10708
10709     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10710                 appData.icsEngineAnalyze) && first.analysisSupport) {
10711       SendToProgram("analyze\n", &first);
10712       first.analyzing = TRUE;
10713     }
10714     return 1;
10715 }
10716
10717 /*
10718  * Button procedures
10719  */
10720 void
10721 Reset (int redraw, int init)
10722 {
10723     int i;
10724
10725     if (appData.debugMode) {
10726         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10727                 redraw, init, gameMode);
10728     }
10729     CleanupTail(); // [HGM] vari: delete any stored variations
10730     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10731     pausing = pauseExamInvalid = FALSE;
10732     startedFromSetupPosition = blackPlaysFirst = FALSE;
10733     firstMove = TRUE;
10734     whiteFlag = blackFlag = FALSE;
10735     userOfferedDraw = FALSE;
10736     hintRequested = bookRequested = FALSE;
10737     first.maybeThinking = FALSE;
10738     second.maybeThinking = FALSE;
10739     first.bookSuspend = FALSE; // [HGM] book
10740     second.bookSuspend = FALSE;
10741     thinkOutput[0] = NULLCHAR;
10742     lastHint[0] = NULLCHAR;
10743     ClearGameInfo(&gameInfo);
10744     gameInfo.variant = StringToVariant(appData.variant);
10745     ics_user_moved = ics_clock_paused = FALSE;
10746     ics_getting_history = H_FALSE;
10747     ics_gamenum = -1;
10748     white_holding[0] = black_holding[0] = NULLCHAR;
10749     ClearProgramStats();
10750     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10751
10752     ResetFrontEnd();
10753     ClearHighlights();
10754     flipView = appData.flipView;
10755     ClearPremoveHighlights();
10756     gotPremove = FALSE;
10757     alarmSounded = FALSE;
10758
10759     GameEnds(EndOfFile, NULL, GE_PLAYER);
10760     if(appData.serverMovesName != NULL) {
10761         /* [HGM] prepare to make moves file for broadcasting */
10762         clock_t t = clock();
10763         if(serverMoves != NULL) fclose(serverMoves);
10764         serverMoves = fopen(appData.serverMovesName, "r");
10765         if(serverMoves != NULL) {
10766             fclose(serverMoves);
10767             /* delay 15 sec before overwriting, so all clients can see end */
10768             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10769         }
10770         serverMoves = fopen(appData.serverMovesName, "w");
10771     }
10772
10773     ExitAnalyzeMode();
10774     gameMode = BeginningOfGame;
10775     ModeHighlight();
10776     if(appData.icsActive) gameInfo.variant = VariantNormal;
10777     currentMove = forwardMostMove = backwardMostMove = 0;
10778     MarkTargetSquares(1);
10779     InitPosition(redraw);
10780     for (i = 0; i < MAX_MOVES; i++) {
10781         if (commentList[i] != NULL) {
10782             free(commentList[i]);
10783             commentList[i] = NULL;
10784         }
10785     }
10786     ResetClocks();
10787     timeRemaining[0][0] = whiteTimeRemaining;
10788     timeRemaining[1][0] = blackTimeRemaining;
10789
10790     if (first.pr == NoProc) {
10791         StartChessProgram(&first);
10792     }
10793     if (init) {
10794             InitChessProgram(&first, startedFromSetupPosition);
10795     }
10796     DisplayTitle("");
10797     DisplayMessage("", "");
10798     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10799     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10800     ClearMap();        // [HGM] exclude: invalidate map
10801 }
10802
10803 void
10804 AutoPlayGameLoop ()
10805 {
10806     for (;;) {
10807         if (!AutoPlayOneMove())
10808           return;
10809         if (matchMode || appData.timeDelay == 0)
10810           continue;
10811         if (appData.timeDelay < 0)
10812           return;
10813         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10814         break;
10815     }
10816 }
10817
10818
10819 int
10820 AutoPlayOneMove ()
10821 {
10822     int fromX, fromY, toX, toY;
10823
10824     if (appData.debugMode) {
10825       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10826     }
10827
10828     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10829       return FALSE;
10830
10831     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10832       pvInfoList[currentMove].depth = programStats.depth;
10833       pvInfoList[currentMove].score = programStats.score;
10834       pvInfoList[currentMove].time  = 0;
10835       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10836     }
10837
10838     if (currentMove >= forwardMostMove) {
10839       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10840 //      gameMode = EndOfGame;
10841 //      ModeHighlight();
10842
10843       /* [AS] Clear current move marker at the end of a game */
10844       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10845
10846       return FALSE;
10847     }
10848
10849     toX = moveList[currentMove][2] - AAA;
10850     toY = moveList[currentMove][3] - ONE;
10851
10852     if (moveList[currentMove][1] == '@') {
10853         if (appData.highlightLastMove) {
10854             SetHighlights(-1, -1, toX, toY);
10855         }
10856     } else {
10857         fromX = moveList[currentMove][0] - AAA;
10858         fromY = moveList[currentMove][1] - ONE;
10859
10860         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10861
10862         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10863
10864         if (appData.highlightLastMove) {
10865             SetHighlights(fromX, fromY, toX, toY);
10866         }
10867     }
10868     DisplayMove(currentMove);
10869     SendMoveToProgram(currentMove++, &first);
10870     DisplayBothClocks();
10871     DrawPosition(FALSE, boards[currentMove]);
10872     // [HGM] PV info: always display, routine tests if empty
10873     DisplayComment(currentMove - 1, commentList[currentMove]);
10874     return TRUE;
10875 }
10876
10877
10878 int
10879 LoadGameOneMove (ChessMove readAhead)
10880 {
10881     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10882     char promoChar = NULLCHAR;
10883     ChessMove moveType;
10884     char move[MSG_SIZ];
10885     char *p, *q;
10886
10887     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10888         gameMode != AnalyzeMode && gameMode != Training) {
10889         gameFileFP = NULL;
10890         return FALSE;
10891     }
10892
10893     yyboardindex = forwardMostMove;
10894     if (readAhead != EndOfFile) {
10895       moveType = readAhead;
10896     } else {
10897       if (gameFileFP == NULL)
10898           return FALSE;
10899       moveType = (ChessMove) Myylex();
10900     }
10901
10902     done = FALSE;
10903     switch (moveType) {
10904       case Comment:
10905         if (appData.debugMode)
10906           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10907         p = yy_text;
10908
10909         /* append the comment but don't display it */
10910         AppendComment(currentMove, p, FALSE);
10911         return TRUE;
10912
10913       case WhiteCapturesEnPassant:
10914       case BlackCapturesEnPassant:
10915       case WhitePromotion:
10916       case BlackPromotion:
10917       case WhiteNonPromotion:
10918       case BlackNonPromotion:
10919       case NormalMove:
10920       case WhiteKingSideCastle:
10921       case WhiteQueenSideCastle:
10922       case BlackKingSideCastle:
10923       case BlackQueenSideCastle:
10924       case WhiteKingSideCastleWild:
10925       case WhiteQueenSideCastleWild:
10926       case BlackKingSideCastleWild:
10927       case BlackQueenSideCastleWild:
10928       /* PUSH Fabien */
10929       case WhiteHSideCastleFR:
10930       case WhiteASideCastleFR:
10931       case BlackHSideCastleFR:
10932       case BlackASideCastleFR:
10933       /* POP Fabien */
10934         if (appData.debugMode)
10935           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10936         fromX = currentMoveString[0] - AAA;
10937         fromY = currentMoveString[1] - ONE;
10938         toX = currentMoveString[2] - AAA;
10939         toY = currentMoveString[3] - ONE;
10940         promoChar = currentMoveString[4];
10941         break;
10942
10943       case WhiteDrop:
10944       case BlackDrop:
10945         if (appData.debugMode)
10946           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10947         fromX = moveType == WhiteDrop ?
10948           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10949         (int) CharToPiece(ToLower(currentMoveString[0]));
10950         fromY = DROP_RANK;
10951         toX = currentMoveString[2] - AAA;
10952         toY = currentMoveString[3] - ONE;
10953         break;
10954
10955       case WhiteWins:
10956       case BlackWins:
10957       case GameIsDrawn:
10958       case GameUnfinished:
10959         if (appData.debugMode)
10960           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10961         p = strchr(yy_text, '{');
10962         if (p == NULL) p = strchr(yy_text, '(');
10963         if (p == NULL) {
10964             p = yy_text;
10965             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10966         } else {
10967             q = strchr(p, *p == '{' ? '}' : ')');
10968             if (q != NULL) *q = NULLCHAR;
10969             p++;
10970         }
10971         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10972         GameEnds(moveType, p, GE_FILE);
10973         done = TRUE;
10974         if (cmailMsgLoaded) {
10975             ClearHighlights();
10976             flipView = WhiteOnMove(currentMove);
10977             if (moveType == GameUnfinished) flipView = !flipView;
10978             if (appData.debugMode)
10979               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10980         }
10981         break;
10982
10983       case EndOfFile:
10984         if (appData.debugMode)
10985           fprintf(debugFP, "Parser hit end of file\n");
10986         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10987           case MT_NONE:
10988           case MT_CHECK:
10989             break;
10990           case MT_CHECKMATE:
10991           case MT_STAINMATE:
10992             if (WhiteOnMove(currentMove)) {
10993                 GameEnds(BlackWins, "Black mates", GE_FILE);
10994             } else {
10995                 GameEnds(WhiteWins, "White mates", GE_FILE);
10996             }
10997             break;
10998           case MT_STALEMATE:
10999             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11000             break;
11001         }
11002         done = TRUE;
11003         break;
11004
11005       case MoveNumberOne:
11006         if (lastLoadGameStart == GNUChessGame) {
11007             /* GNUChessGames have numbers, but they aren't move numbers */
11008             if (appData.debugMode)
11009               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11010                       yy_text, (int) moveType);
11011             return LoadGameOneMove(EndOfFile); /* tail recursion */
11012         }
11013         /* else fall thru */
11014
11015       case XBoardGame:
11016       case GNUChessGame:
11017       case PGNTag:
11018         /* Reached start of next game in file */
11019         if (appData.debugMode)
11020           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11021         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11022           case MT_NONE:
11023           case MT_CHECK:
11024             break;
11025           case MT_CHECKMATE:
11026           case MT_STAINMATE:
11027             if (WhiteOnMove(currentMove)) {
11028                 GameEnds(BlackWins, "Black mates", GE_FILE);
11029             } else {
11030                 GameEnds(WhiteWins, "White mates", GE_FILE);
11031             }
11032             break;
11033           case MT_STALEMATE:
11034             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11035             break;
11036         }
11037         done = TRUE;
11038         break;
11039
11040       case PositionDiagram:     /* should not happen; ignore */
11041       case ElapsedTime:         /* ignore */
11042       case NAG:                 /* ignore */
11043         if (appData.debugMode)
11044           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11045                   yy_text, (int) moveType);
11046         return LoadGameOneMove(EndOfFile); /* tail recursion */
11047
11048       case IllegalMove:
11049         if (appData.testLegality) {
11050             if (appData.debugMode)
11051               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11052             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11053                     (forwardMostMove / 2) + 1,
11054                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11055             DisplayError(move, 0);
11056             done = TRUE;
11057         } else {
11058             if (appData.debugMode)
11059               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11060                       yy_text, currentMoveString);
11061             fromX = currentMoveString[0] - AAA;
11062             fromY = currentMoveString[1] - ONE;
11063             toX = currentMoveString[2] - AAA;
11064             toY = currentMoveString[3] - ONE;
11065             promoChar = currentMoveString[4];
11066         }
11067         break;
11068
11069       case AmbiguousMove:
11070         if (appData.debugMode)
11071           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11072         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11073                 (forwardMostMove / 2) + 1,
11074                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11075         DisplayError(move, 0);
11076         done = TRUE;
11077         break;
11078
11079       default:
11080       case ImpossibleMove:
11081         if (appData.debugMode)
11082           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11083         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11084                 (forwardMostMove / 2) + 1,
11085                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11086         DisplayError(move, 0);
11087         done = TRUE;
11088         break;
11089     }
11090
11091     if (done) {
11092         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11093             DrawPosition(FALSE, boards[currentMove]);
11094             DisplayBothClocks();
11095             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11096               DisplayComment(currentMove - 1, commentList[currentMove]);
11097         }
11098         (void) StopLoadGameTimer();
11099         gameFileFP = NULL;
11100         cmailOldMove = forwardMostMove;
11101         return FALSE;
11102     } else {
11103         /* currentMoveString is set as a side-effect of yylex */
11104
11105         thinkOutput[0] = NULLCHAR;
11106         MakeMove(fromX, fromY, toX, toY, promoChar);
11107         currentMove = forwardMostMove;
11108         return TRUE;
11109     }
11110 }
11111
11112 /* Load the nth game from the given file */
11113 int
11114 LoadGameFromFile (char *filename, int n, char *title, int useList)
11115 {
11116     FILE *f;
11117     char buf[MSG_SIZ];
11118
11119     if (strcmp(filename, "-") == 0) {
11120         f = stdin;
11121         title = "stdin";
11122     } else {
11123         f = fopen(filename, "rb");
11124         if (f == NULL) {
11125           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11126             DisplayError(buf, errno);
11127             return FALSE;
11128         }
11129     }
11130     if (fseek(f, 0, 0) == -1) {
11131         /* f is not seekable; probably a pipe */
11132         useList = FALSE;
11133     }
11134     if (useList && n == 0) {
11135         int error = GameListBuild(f);
11136         if (error) {
11137             DisplayError(_("Cannot build game list"), error);
11138         } else if (!ListEmpty(&gameList) &&
11139                    ((ListGame *) gameList.tailPred)->number > 1) {
11140             GameListPopUp(f, title);
11141             return TRUE;
11142         }
11143         GameListDestroy();
11144         n = 1;
11145     }
11146     if (n == 0) n = 1;
11147     return LoadGame(f, n, title, FALSE);
11148 }
11149
11150
11151 void
11152 MakeRegisteredMove ()
11153 {
11154     int fromX, fromY, toX, toY;
11155     char promoChar;
11156     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11157         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11158           case CMAIL_MOVE:
11159           case CMAIL_DRAW:
11160             if (appData.debugMode)
11161               fprintf(debugFP, "Restoring %s for game %d\n",
11162                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11163
11164             thinkOutput[0] = NULLCHAR;
11165             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11166             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11167             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11168             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11169             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11170             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11171             MakeMove(fromX, fromY, toX, toY, promoChar);
11172             ShowMove(fromX, fromY, toX, toY);
11173
11174             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11175               case MT_NONE:
11176               case MT_CHECK:
11177                 break;
11178
11179               case MT_CHECKMATE:
11180               case MT_STAINMATE:
11181                 if (WhiteOnMove(currentMove)) {
11182                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11183                 } else {
11184                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11185                 }
11186                 break;
11187
11188               case MT_STALEMATE:
11189                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11190                 break;
11191             }
11192
11193             break;
11194
11195           case CMAIL_RESIGN:
11196             if (WhiteOnMove(currentMove)) {
11197                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11198             } else {
11199                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11200             }
11201             break;
11202
11203           case CMAIL_ACCEPT:
11204             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11205             break;
11206
11207           default:
11208             break;
11209         }
11210     }
11211
11212     return;
11213 }
11214
11215 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11216 int
11217 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11218 {
11219     int retVal;
11220
11221     if (gameNumber > nCmailGames) {
11222         DisplayError(_("No more games in this message"), 0);
11223         return FALSE;
11224     }
11225     if (f == lastLoadGameFP) {
11226         int offset = gameNumber - lastLoadGameNumber;
11227         if (offset == 0) {
11228             cmailMsg[0] = NULLCHAR;
11229             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11230                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11231                 nCmailMovesRegistered--;
11232             }
11233             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11234             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11235                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11236             }
11237         } else {
11238             if (! RegisterMove()) return FALSE;
11239         }
11240     }
11241
11242     retVal = LoadGame(f, gameNumber, title, useList);
11243
11244     /* Make move registered during previous look at this game, if any */
11245     MakeRegisteredMove();
11246
11247     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11248         commentList[currentMove]
11249           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11250         DisplayComment(currentMove - 1, commentList[currentMove]);
11251     }
11252
11253     return retVal;
11254 }
11255
11256 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11257 int
11258 ReloadGame (int offset)
11259 {
11260     int gameNumber = lastLoadGameNumber + offset;
11261     if (lastLoadGameFP == NULL) {
11262         DisplayError(_("No game has been loaded yet"), 0);
11263         return FALSE;
11264     }
11265     if (gameNumber <= 0) {
11266         DisplayError(_("Can't back up any further"), 0);
11267         return FALSE;
11268     }
11269     if (cmailMsgLoaded) {
11270         return CmailLoadGame(lastLoadGameFP, gameNumber,
11271                              lastLoadGameTitle, lastLoadGameUseList);
11272     } else {
11273         return LoadGame(lastLoadGameFP, gameNumber,
11274                         lastLoadGameTitle, lastLoadGameUseList);
11275     }
11276 }
11277
11278 int keys[EmptySquare+1];
11279
11280 int
11281 PositionMatches (Board b1, Board b2)
11282 {
11283     int r, f, sum=0;
11284     switch(appData.searchMode) {
11285         case 1: return CompareWithRights(b1, b2);
11286         case 2:
11287             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11288                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11289             }
11290             return TRUE;
11291         case 3:
11292             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11293               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11294                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11295             }
11296             return sum==0;
11297         case 4:
11298             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11299                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11300             }
11301             return sum==0;
11302     }
11303     return TRUE;
11304 }
11305
11306 #define Q_PROMO  4
11307 #define Q_EP     3
11308 #define Q_BCASTL 2
11309 #define Q_WCASTL 1
11310
11311 int pieceList[256], quickBoard[256];
11312 ChessSquare pieceType[256] = { EmptySquare };
11313 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11314 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11315 int soughtTotal, turn;
11316 Boolean epOK, flipSearch;
11317
11318 typedef struct {
11319     unsigned char piece, to;
11320 } Move;
11321
11322 #define DSIZE (250000)
11323
11324 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11325 Move *moveDatabase = initialSpace;
11326 unsigned int movePtr, dataSize = DSIZE;
11327
11328 int
11329 MakePieceList (Board board, int *counts)
11330 {
11331     int r, f, n=Q_PROMO, total=0;
11332     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11333     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11334         int sq = f + (r<<4);
11335         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11336             quickBoard[sq] = ++n;
11337             pieceList[n] = sq;
11338             pieceType[n] = board[r][f];
11339             counts[board[r][f]]++;
11340             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11341             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11342             total++;
11343         }
11344     }
11345     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11346     return total;
11347 }
11348
11349 void
11350 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11351 {
11352     int sq = fromX + (fromY<<4);
11353     int piece = quickBoard[sq];
11354     quickBoard[sq] = 0;
11355     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11356     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11357         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11358         moveDatabase[movePtr++].piece = Q_WCASTL;
11359         quickBoard[sq] = piece;
11360         piece = quickBoard[from]; quickBoard[from] = 0;
11361         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11362     } else
11363     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11364         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11365         moveDatabase[movePtr++].piece = Q_BCASTL;
11366         quickBoard[sq] = piece;
11367         piece = quickBoard[from]; quickBoard[from] = 0;
11368         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11369     } else
11370     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11371         quickBoard[(fromY<<4)+toX] = 0;
11372         moveDatabase[movePtr].piece = Q_EP;
11373         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11374         moveDatabase[movePtr].to = sq;
11375     } else
11376     if(promoPiece != pieceType[piece]) {
11377         moveDatabase[movePtr++].piece = Q_PROMO;
11378         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11379     }
11380     moveDatabase[movePtr].piece = piece;
11381     quickBoard[sq] = piece;
11382     movePtr++;
11383 }
11384
11385 int
11386 PackGame (Board board)
11387 {
11388     Move *newSpace = NULL;
11389     moveDatabase[movePtr].piece = 0; // terminate previous game
11390     if(movePtr > dataSize) {
11391         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11392         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11393         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11394         if(newSpace) {
11395             int i;
11396             Move *p = moveDatabase, *q = newSpace;
11397             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11398             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11399             moveDatabase = newSpace;
11400         } else { // calloc failed, we must be out of memory. Too bad...
11401             dataSize = 0; // prevent calloc events for all subsequent games
11402             return 0;     // and signal this one isn't cached
11403         }
11404     }
11405     movePtr++;
11406     MakePieceList(board, counts);
11407     return movePtr;
11408 }
11409
11410 int
11411 QuickCompare (Board board, int *minCounts, int *maxCounts)
11412 {   // compare according to search mode
11413     int r, f;
11414     switch(appData.searchMode)
11415     {
11416       case 1: // exact position match
11417         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11418         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11419             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11420         }
11421         break;
11422       case 2: // can have extra material on empty squares
11423         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11424             if(board[r][f] == EmptySquare) continue;
11425             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11426         }
11427         break;
11428       case 3: // material with exact Pawn structure
11429         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11430             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11431             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11432         } // fall through to material comparison
11433       case 4: // exact material
11434         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11435         break;
11436       case 6: // material range with given imbalance
11437         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11438         // fall through to range comparison
11439       case 5: // material range
11440         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11441     }
11442     return TRUE;
11443 }
11444
11445 int
11446 QuickScan (Board board, Move *move)
11447 {   // reconstruct game,and compare all positions in it
11448     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11449     do {
11450         int piece = move->piece;
11451         int to = move->to, from = pieceList[piece];
11452         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11453           if(!piece) return -1;
11454           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11455             piece = (++move)->piece;
11456             from = pieceList[piece];
11457             counts[pieceType[piece]]--;
11458             pieceType[piece] = (ChessSquare) move->to;
11459             counts[move->to]++;
11460           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11461             counts[pieceType[quickBoard[to]]]--;
11462             quickBoard[to] = 0; total--;
11463             move++;
11464             continue;
11465           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11466             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11467             from  = pieceList[piece]; // so this must be King
11468             quickBoard[from] = 0;
11469             quickBoard[to] = piece;
11470             pieceList[piece] = to;
11471             move++;
11472             continue;
11473           }
11474         }
11475         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11476         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11477         quickBoard[from] = 0;
11478         quickBoard[to] = piece;
11479         pieceList[piece] = to;
11480         cnt++; turn ^= 3;
11481         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11482            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11483            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11484                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11485           ) {
11486             static int lastCounts[EmptySquare+1];
11487             int i;
11488             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11489             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11490         } else stretch = 0;
11491         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11492         move++;
11493     } while(1);
11494 }
11495
11496 void
11497 InitSearch ()
11498 {
11499     int r, f;
11500     flipSearch = FALSE;
11501     CopyBoard(soughtBoard, boards[currentMove]);
11502     soughtTotal = MakePieceList(soughtBoard, maxSought);
11503     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11504     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11505     CopyBoard(reverseBoard, boards[currentMove]);
11506     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11507         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11508         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11509         reverseBoard[r][f] = piece;
11510     }
11511     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11512     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11513     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11514                  || (boards[currentMove][CASTLING][2] == NoRights || 
11515                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11516                  && (boards[currentMove][CASTLING][5] == NoRights || 
11517                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11518       ) {
11519         flipSearch = TRUE;
11520         CopyBoard(flipBoard, soughtBoard);
11521         CopyBoard(rotateBoard, reverseBoard);
11522         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11523             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11524             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11525         }
11526     }
11527     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11528     if(appData.searchMode >= 5) {
11529         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11530         MakePieceList(soughtBoard, minSought);
11531         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11532     }
11533     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11534         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11535 }
11536
11537 GameInfo dummyInfo;
11538
11539 int
11540 GameContainsPosition (FILE *f, ListGame *lg)
11541 {
11542     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11543     int fromX, fromY, toX, toY;
11544     char promoChar;
11545     static int initDone=FALSE;
11546
11547     // weed out games based on numerical tag comparison
11548     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11549     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11550     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11551     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11552     if(!initDone) {
11553         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11554         initDone = TRUE;
11555     }
11556     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11557     else CopyBoard(boards[scratch], initialPosition); // default start position
11558     if(lg->moves) {
11559         turn = btm + 1;
11560         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11561         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11562     }
11563     if(btm) plyNr++;
11564     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11565     fseek(f, lg->offset, 0);
11566     yynewfile(f);
11567     while(1) {
11568         yyboardindex = scratch;
11569         quickFlag = plyNr+1;
11570         next = Myylex();
11571         quickFlag = 0;
11572         switch(next) {
11573             case PGNTag:
11574                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11575             default:
11576                 continue;
11577
11578             case XBoardGame:
11579             case GNUChessGame:
11580                 if(plyNr) return -1; // after we have seen moves, this is for new game
11581               continue;
11582
11583             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11584             case ImpossibleMove:
11585             case WhiteWins: // game ends here with these four
11586             case BlackWins:
11587             case GameIsDrawn:
11588             case GameUnfinished:
11589                 return -1;
11590
11591             case IllegalMove:
11592                 if(appData.testLegality) return -1;
11593             case WhiteCapturesEnPassant:
11594             case BlackCapturesEnPassant:
11595             case WhitePromotion:
11596             case BlackPromotion:
11597             case WhiteNonPromotion:
11598             case BlackNonPromotion:
11599             case NormalMove:
11600             case WhiteKingSideCastle:
11601             case WhiteQueenSideCastle:
11602             case BlackKingSideCastle:
11603             case BlackQueenSideCastle:
11604             case WhiteKingSideCastleWild:
11605             case WhiteQueenSideCastleWild:
11606             case BlackKingSideCastleWild:
11607             case BlackQueenSideCastleWild:
11608             case WhiteHSideCastleFR:
11609             case WhiteASideCastleFR:
11610             case BlackHSideCastleFR:
11611             case BlackASideCastleFR:
11612                 fromX = currentMoveString[0] - AAA;
11613                 fromY = currentMoveString[1] - ONE;
11614                 toX = currentMoveString[2] - AAA;
11615                 toY = currentMoveString[3] - ONE;
11616                 promoChar = currentMoveString[4];
11617                 break;
11618             case WhiteDrop:
11619             case BlackDrop:
11620                 fromX = next == WhiteDrop ?
11621                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11622                   (int) CharToPiece(ToLower(currentMoveString[0]));
11623                 fromY = DROP_RANK;
11624                 toX = currentMoveString[2] - AAA;
11625                 toY = currentMoveString[3] - ONE;
11626                 promoChar = 0;
11627                 break;
11628         }
11629         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11630         plyNr++;
11631         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11632         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11633         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11634         if(appData.findMirror) {
11635             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11636             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11637         }
11638     }
11639 }
11640
11641 /* Load the nth game from open file f */
11642 int
11643 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11644 {
11645     ChessMove cm;
11646     char buf[MSG_SIZ];
11647     int gn = gameNumber;
11648     ListGame *lg = NULL;
11649     int numPGNTags = 0;
11650     int err, pos = -1;
11651     GameMode oldGameMode;
11652     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11653
11654     if (appData.debugMode)
11655         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11656
11657     if (gameMode == Training )
11658         SetTrainingModeOff();
11659
11660     oldGameMode = gameMode;
11661     if (gameMode != BeginningOfGame) {
11662       Reset(FALSE, TRUE);
11663     }
11664
11665     gameFileFP = f;
11666     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11667         fclose(lastLoadGameFP);
11668     }
11669
11670     if (useList) {
11671         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11672
11673         if (lg) {
11674             fseek(f, lg->offset, 0);
11675             GameListHighlight(gameNumber);
11676             pos = lg->position;
11677             gn = 1;
11678         }
11679         else {
11680             DisplayError(_("Game number out of range"), 0);
11681             return FALSE;
11682         }
11683     } else {
11684         GameListDestroy();
11685         if (fseek(f, 0, 0) == -1) {
11686             if (f == lastLoadGameFP ?
11687                 gameNumber == lastLoadGameNumber + 1 :
11688                 gameNumber == 1) {
11689                 gn = 1;
11690             } else {
11691                 DisplayError(_("Can't seek on game file"), 0);
11692                 return FALSE;
11693             }
11694         }
11695     }
11696     lastLoadGameFP = f;
11697     lastLoadGameNumber = gameNumber;
11698     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11699     lastLoadGameUseList = useList;
11700
11701     yynewfile(f);
11702
11703     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11704       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11705                 lg->gameInfo.black);
11706             DisplayTitle(buf);
11707     } else if (*title != NULLCHAR) {
11708         if (gameNumber > 1) {
11709           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11710             DisplayTitle(buf);
11711         } else {
11712             DisplayTitle(title);
11713         }
11714     }
11715
11716     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11717         gameMode = PlayFromGameFile;
11718         ModeHighlight();
11719     }
11720
11721     currentMove = forwardMostMove = backwardMostMove = 0;
11722     CopyBoard(boards[0], initialPosition);
11723     StopClocks();
11724
11725     /*
11726      * Skip the first gn-1 games in the file.
11727      * Also skip over anything that precedes an identifiable
11728      * start of game marker, to avoid being confused by
11729      * garbage at the start of the file.  Currently
11730      * recognized start of game markers are the move number "1",
11731      * the pattern "gnuchess .* game", the pattern
11732      * "^[#;%] [^ ]* game file", and a PGN tag block.
11733      * A game that starts with one of the latter two patterns
11734      * will also have a move number 1, possibly
11735      * following a position diagram.
11736      * 5-4-02: Let's try being more lenient and allowing a game to
11737      * start with an unnumbered move.  Does that break anything?
11738      */
11739     cm = lastLoadGameStart = EndOfFile;
11740     while (gn > 0) {
11741         yyboardindex = forwardMostMove;
11742         cm = (ChessMove) Myylex();
11743         switch (cm) {
11744           case EndOfFile:
11745             if (cmailMsgLoaded) {
11746                 nCmailGames = CMAIL_MAX_GAMES - gn;
11747             } else {
11748                 Reset(TRUE, TRUE);
11749                 DisplayError(_("Game not found in file"), 0);
11750             }
11751             return FALSE;
11752
11753           case GNUChessGame:
11754           case XBoardGame:
11755             gn--;
11756             lastLoadGameStart = cm;
11757             break;
11758
11759           case MoveNumberOne:
11760             switch (lastLoadGameStart) {
11761               case GNUChessGame:
11762               case XBoardGame:
11763               case PGNTag:
11764                 break;
11765               case MoveNumberOne:
11766               case EndOfFile:
11767                 gn--;           /* count this game */
11768                 lastLoadGameStart = cm;
11769                 break;
11770               default:
11771                 /* impossible */
11772                 break;
11773             }
11774             break;
11775
11776           case PGNTag:
11777             switch (lastLoadGameStart) {
11778               case GNUChessGame:
11779               case PGNTag:
11780               case MoveNumberOne:
11781               case EndOfFile:
11782                 gn--;           /* count this game */
11783                 lastLoadGameStart = cm;
11784                 break;
11785               case XBoardGame:
11786                 lastLoadGameStart = cm; /* game counted already */
11787                 break;
11788               default:
11789                 /* impossible */
11790                 break;
11791             }
11792             if (gn > 0) {
11793                 do {
11794                     yyboardindex = forwardMostMove;
11795                     cm = (ChessMove) Myylex();
11796                 } while (cm == PGNTag || cm == Comment);
11797             }
11798             break;
11799
11800           case WhiteWins:
11801           case BlackWins:
11802           case GameIsDrawn:
11803             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11804                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11805                     != CMAIL_OLD_RESULT) {
11806                     nCmailResults ++ ;
11807                     cmailResult[  CMAIL_MAX_GAMES
11808                                 - gn - 1] = CMAIL_OLD_RESULT;
11809                 }
11810             }
11811             break;
11812
11813           case NormalMove:
11814             /* Only a NormalMove can be at the start of a game
11815              * without a position diagram. */
11816             if (lastLoadGameStart == EndOfFile ) {
11817               gn--;
11818               lastLoadGameStart = MoveNumberOne;
11819             }
11820             break;
11821
11822           default:
11823             break;
11824         }
11825     }
11826
11827     if (appData.debugMode)
11828       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11829
11830     if (cm == XBoardGame) {
11831         /* Skip any header junk before position diagram and/or move 1 */
11832         for (;;) {
11833             yyboardindex = forwardMostMove;
11834             cm = (ChessMove) Myylex();
11835
11836             if (cm == EndOfFile ||
11837                 cm == GNUChessGame || cm == XBoardGame) {
11838                 /* Empty game; pretend end-of-file and handle later */
11839                 cm = EndOfFile;
11840                 break;
11841             }
11842
11843             if (cm == MoveNumberOne || cm == PositionDiagram ||
11844                 cm == PGNTag || cm == Comment)
11845               break;
11846         }
11847     } else if (cm == GNUChessGame) {
11848         if (gameInfo.event != NULL) {
11849             free(gameInfo.event);
11850         }
11851         gameInfo.event = StrSave(yy_text);
11852     }
11853
11854     startedFromSetupPosition = FALSE;
11855     while (cm == PGNTag) {
11856         if (appData.debugMode)
11857           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11858         err = ParsePGNTag(yy_text, &gameInfo);
11859         if (!err) numPGNTags++;
11860
11861         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11862         if(gameInfo.variant != oldVariant) {
11863             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11864             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11865             InitPosition(TRUE);
11866             oldVariant = gameInfo.variant;
11867             if (appData.debugMode)
11868               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11869         }
11870
11871
11872         if (gameInfo.fen != NULL) {
11873           Board initial_position;
11874           startedFromSetupPosition = TRUE;
11875           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11876             Reset(TRUE, TRUE);
11877             DisplayError(_("Bad FEN position in file"), 0);
11878             return FALSE;
11879           }
11880           CopyBoard(boards[0], initial_position);
11881           if (blackPlaysFirst) {
11882             currentMove = forwardMostMove = backwardMostMove = 1;
11883             CopyBoard(boards[1], initial_position);
11884             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11885             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11886             timeRemaining[0][1] = whiteTimeRemaining;
11887             timeRemaining[1][1] = blackTimeRemaining;
11888             if (commentList[0] != NULL) {
11889               commentList[1] = commentList[0];
11890               commentList[0] = NULL;
11891             }
11892           } else {
11893             currentMove = forwardMostMove = backwardMostMove = 0;
11894           }
11895           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11896           {   int i;
11897               initialRulePlies = FENrulePlies;
11898               for( i=0; i< nrCastlingRights; i++ )
11899                   initialRights[i] = initial_position[CASTLING][i];
11900           }
11901           yyboardindex = forwardMostMove;
11902           free(gameInfo.fen);
11903           gameInfo.fen = NULL;
11904         }
11905
11906         yyboardindex = forwardMostMove;
11907         cm = (ChessMove) Myylex();
11908
11909         /* Handle comments interspersed among the tags */
11910         while (cm == Comment) {
11911             char *p;
11912             if (appData.debugMode)
11913               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11914             p = yy_text;
11915             AppendComment(currentMove, p, FALSE);
11916             yyboardindex = forwardMostMove;
11917             cm = (ChessMove) Myylex();
11918         }
11919     }
11920
11921     /* don't rely on existence of Event tag since if game was
11922      * pasted from clipboard the Event tag may not exist
11923      */
11924     if (numPGNTags > 0){
11925         char *tags;
11926         if (gameInfo.variant == VariantNormal) {
11927           VariantClass v = StringToVariant(gameInfo.event);
11928           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11929           if(v < VariantShogi) gameInfo.variant = v;
11930         }
11931         if (!matchMode) {
11932           if( appData.autoDisplayTags ) {
11933             tags = PGNTags(&gameInfo);
11934             TagsPopUp(tags, CmailMsg());
11935             free(tags);
11936           }
11937         }
11938     } else {
11939         /* Make something up, but don't display it now */
11940         SetGameInfo();
11941         TagsPopDown();
11942     }
11943
11944     if (cm == PositionDiagram) {
11945         int i, j;
11946         char *p;
11947         Board initial_position;
11948
11949         if (appData.debugMode)
11950           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11951
11952         if (!startedFromSetupPosition) {
11953             p = yy_text;
11954             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11955               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11956                 switch (*p) {
11957                   case '{':
11958                   case '[':
11959                   case '-':
11960                   case ' ':
11961                   case '\t':
11962                   case '\n':
11963                   case '\r':
11964                     break;
11965                   default:
11966                     initial_position[i][j++] = CharToPiece(*p);
11967                     break;
11968                 }
11969             while (*p == ' ' || *p == '\t' ||
11970                    *p == '\n' || *p == '\r') p++;
11971
11972             if (strncmp(p, "black", strlen("black"))==0)
11973               blackPlaysFirst = TRUE;
11974             else
11975               blackPlaysFirst = FALSE;
11976             startedFromSetupPosition = TRUE;
11977
11978             CopyBoard(boards[0], initial_position);
11979             if (blackPlaysFirst) {
11980                 currentMove = forwardMostMove = backwardMostMove = 1;
11981                 CopyBoard(boards[1], initial_position);
11982                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11983                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11984                 timeRemaining[0][1] = whiteTimeRemaining;
11985                 timeRemaining[1][1] = blackTimeRemaining;
11986                 if (commentList[0] != NULL) {
11987                     commentList[1] = commentList[0];
11988                     commentList[0] = NULL;
11989                 }
11990             } else {
11991                 currentMove = forwardMostMove = backwardMostMove = 0;
11992             }
11993         }
11994         yyboardindex = forwardMostMove;
11995         cm = (ChessMove) Myylex();
11996     }
11997
11998     if (first.pr == NoProc) {
11999         StartChessProgram(&first);
12000     }
12001     InitChessProgram(&first, FALSE);
12002     SendToProgram("force\n", &first);
12003     if (startedFromSetupPosition) {
12004         SendBoard(&first, forwardMostMove);
12005     if (appData.debugMode) {
12006         fprintf(debugFP, "Load Game\n");
12007     }
12008         DisplayBothClocks();
12009     }
12010
12011     /* [HGM] server: flag to write setup moves in broadcast file as one */
12012     loadFlag = appData.suppressLoadMoves;
12013
12014     while (cm == Comment) {
12015         char *p;
12016         if (appData.debugMode)
12017           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12018         p = yy_text;
12019         AppendComment(currentMove, p, FALSE);
12020         yyboardindex = forwardMostMove;
12021         cm = (ChessMove) Myylex();
12022     }
12023
12024     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12025         cm == WhiteWins || cm == BlackWins ||
12026         cm == GameIsDrawn || cm == GameUnfinished) {
12027         DisplayMessage("", _("No moves in game"));
12028         if (cmailMsgLoaded) {
12029             if (appData.debugMode)
12030               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12031             ClearHighlights();
12032             flipView = FALSE;
12033         }
12034         DrawPosition(FALSE, boards[currentMove]);
12035         DisplayBothClocks();
12036         gameMode = EditGame;
12037         ModeHighlight();
12038         gameFileFP = NULL;
12039         cmailOldMove = 0;
12040         return TRUE;
12041     }
12042
12043     // [HGM] PV info: routine tests if comment empty
12044     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12045         DisplayComment(currentMove - 1, commentList[currentMove]);
12046     }
12047     if (!matchMode && appData.timeDelay != 0)
12048       DrawPosition(FALSE, boards[currentMove]);
12049
12050     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12051       programStats.ok_to_send = 1;
12052     }
12053
12054     /* if the first token after the PGN tags is a move
12055      * and not move number 1, retrieve it from the parser
12056      */
12057     if (cm != MoveNumberOne)
12058         LoadGameOneMove(cm);
12059
12060     /* load the remaining moves from the file */
12061     while (LoadGameOneMove(EndOfFile)) {
12062       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12063       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12064     }
12065
12066     /* rewind to the start of the game */
12067     currentMove = backwardMostMove;
12068
12069     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12070
12071     if (oldGameMode == AnalyzeFile ||
12072         oldGameMode == AnalyzeMode) {
12073       AnalyzeFileEvent();
12074     }
12075
12076     if (!matchMode && pos >= 0) {
12077         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12078     } else
12079     if (matchMode || appData.timeDelay == 0) {
12080       ToEndEvent();
12081     } else if (appData.timeDelay > 0) {
12082       AutoPlayGameLoop();
12083     }
12084
12085     if (appData.debugMode)
12086         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12087
12088     loadFlag = 0; /* [HGM] true game starts */
12089     return TRUE;
12090 }
12091
12092 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12093 int
12094 ReloadPosition (int offset)
12095 {
12096     int positionNumber = lastLoadPositionNumber + offset;
12097     if (lastLoadPositionFP == NULL) {
12098         DisplayError(_("No position has been loaded yet"), 0);
12099         return FALSE;
12100     }
12101     if (positionNumber <= 0) {
12102         DisplayError(_("Can't back up any further"), 0);
12103         return FALSE;
12104     }
12105     return LoadPosition(lastLoadPositionFP, positionNumber,
12106                         lastLoadPositionTitle);
12107 }
12108
12109 /* Load the nth position from the given file */
12110 int
12111 LoadPositionFromFile (char *filename, int n, char *title)
12112 {
12113     FILE *f;
12114     char buf[MSG_SIZ];
12115
12116     if (strcmp(filename, "-") == 0) {
12117         return LoadPosition(stdin, n, "stdin");
12118     } else {
12119         f = fopen(filename, "rb");
12120         if (f == NULL) {
12121             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12122             DisplayError(buf, errno);
12123             return FALSE;
12124         } else {
12125             return LoadPosition(f, n, title);
12126         }
12127     }
12128 }
12129
12130 /* Load the nth position from the given open file, and close it */
12131 int
12132 LoadPosition (FILE *f, int positionNumber, char *title)
12133 {
12134     char *p, line[MSG_SIZ];
12135     Board initial_position;
12136     int i, j, fenMode, pn;
12137
12138     if (gameMode == Training )
12139         SetTrainingModeOff();
12140
12141     if (gameMode != BeginningOfGame) {
12142         Reset(FALSE, TRUE);
12143     }
12144     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12145         fclose(lastLoadPositionFP);
12146     }
12147     if (positionNumber == 0) positionNumber = 1;
12148     lastLoadPositionFP = f;
12149     lastLoadPositionNumber = positionNumber;
12150     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12151     if (first.pr == NoProc && !appData.noChessProgram) {
12152       StartChessProgram(&first);
12153       InitChessProgram(&first, FALSE);
12154     }
12155     pn = positionNumber;
12156     if (positionNumber < 0) {
12157         /* Negative position number means to seek to that byte offset */
12158         if (fseek(f, -positionNumber, 0) == -1) {
12159             DisplayError(_("Can't seek on position file"), 0);
12160             return FALSE;
12161         };
12162         pn = 1;
12163     } else {
12164         if (fseek(f, 0, 0) == -1) {
12165             if (f == lastLoadPositionFP ?
12166                 positionNumber == lastLoadPositionNumber + 1 :
12167                 positionNumber == 1) {
12168                 pn = 1;
12169             } else {
12170                 DisplayError(_("Can't seek on position file"), 0);
12171                 return FALSE;
12172             }
12173         }
12174     }
12175     /* See if this file is FEN or old-style xboard */
12176     if (fgets(line, MSG_SIZ, f) == NULL) {
12177         DisplayError(_("Position not found in file"), 0);
12178         return FALSE;
12179     }
12180     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12181     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12182
12183     if (pn >= 2) {
12184         if (fenMode || line[0] == '#') pn--;
12185         while (pn > 0) {
12186             /* skip positions before number pn */
12187             if (fgets(line, MSG_SIZ, f) == NULL) {
12188                 Reset(TRUE, TRUE);
12189                 DisplayError(_("Position not found in file"), 0);
12190                 return FALSE;
12191             }
12192             if (fenMode || line[0] == '#') pn--;
12193         }
12194     }
12195
12196     if (fenMode) {
12197         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12198             DisplayError(_("Bad FEN position in file"), 0);
12199             return FALSE;
12200         }
12201     } else {
12202         (void) fgets(line, MSG_SIZ, f);
12203         (void) fgets(line, MSG_SIZ, f);
12204
12205         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12206             (void) fgets(line, MSG_SIZ, f);
12207             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12208                 if (*p == ' ')
12209                   continue;
12210                 initial_position[i][j++] = CharToPiece(*p);
12211             }
12212         }
12213
12214         blackPlaysFirst = FALSE;
12215         if (!feof(f)) {
12216             (void) fgets(line, MSG_SIZ, f);
12217             if (strncmp(line, "black", strlen("black"))==0)
12218               blackPlaysFirst = TRUE;
12219         }
12220     }
12221     startedFromSetupPosition = TRUE;
12222
12223     CopyBoard(boards[0], initial_position);
12224     if (blackPlaysFirst) {
12225         currentMove = forwardMostMove = backwardMostMove = 1;
12226         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12227         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12228         CopyBoard(boards[1], initial_position);
12229         DisplayMessage("", _("Black to play"));
12230     } else {
12231         currentMove = forwardMostMove = backwardMostMove = 0;
12232         DisplayMessage("", _("White to play"));
12233     }
12234     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12235     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12236         SendToProgram("force\n", &first);
12237         SendBoard(&first, forwardMostMove);
12238     }
12239     if (appData.debugMode) {
12240 int i, j;
12241   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12242   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12243         fprintf(debugFP, "Load Position\n");
12244     }
12245
12246     if (positionNumber > 1) {
12247       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12248         DisplayTitle(line);
12249     } else {
12250         DisplayTitle(title);
12251     }
12252     gameMode = EditGame;
12253     ModeHighlight();
12254     ResetClocks();
12255     timeRemaining[0][1] = whiteTimeRemaining;
12256     timeRemaining[1][1] = blackTimeRemaining;
12257     DrawPosition(FALSE, boards[currentMove]);
12258
12259     return TRUE;
12260 }
12261
12262
12263 void
12264 CopyPlayerNameIntoFileName (char **dest, char *src)
12265 {
12266     while (*src != NULLCHAR && *src != ',') {
12267         if (*src == ' ') {
12268             *(*dest)++ = '_';
12269             src++;
12270         } else {
12271             *(*dest)++ = *src++;
12272         }
12273     }
12274 }
12275
12276 char *
12277 DefaultFileName (char *ext)
12278 {
12279     static char def[MSG_SIZ];
12280     char *p;
12281
12282     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12283         p = def;
12284         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12285         *p++ = '-';
12286         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12287         *p++ = '.';
12288         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12289     } else {
12290         def[0] = NULLCHAR;
12291     }
12292     return def;
12293 }
12294
12295 /* Save the current game to the given file */
12296 int
12297 SaveGameToFile (char *filename, int append)
12298 {
12299     FILE *f;
12300     char buf[MSG_SIZ];
12301     int result, i, t,tot=0;
12302
12303     if (strcmp(filename, "-") == 0) {
12304         return SaveGame(stdout, 0, NULL);
12305     } else {
12306         for(i=0; i<10; i++) { // upto 10 tries
12307              f = fopen(filename, append ? "a" : "w");
12308              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12309              if(f || errno != 13) break;
12310              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12311              tot += t;
12312         }
12313         if (f == NULL) {
12314             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12315             DisplayError(buf, errno);
12316             return FALSE;
12317         } else {
12318             safeStrCpy(buf, lastMsg, MSG_SIZ);
12319             DisplayMessage(_("Waiting for access to save file"), "");
12320             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12321             DisplayMessage(_("Saving game"), "");
12322             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12323             result = SaveGame(f, 0, NULL);
12324             DisplayMessage(buf, "");
12325             return result;
12326         }
12327     }
12328 }
12329
12330 char *
12331 SavePart (char *str)
12332 {
12333     static char buf[MSG_SIZ];
12334     char *p;
12335
12336     p = strchr(str, ' ');
12337     if (p == NULL) return str;
12338     strncpy(buf, str, p - str);
12339     buf[p - str] = NULLCHAR;
12340     return buf;
12341 }
12342
12343 #define PGN_MAX_LINE 75
12344
12345 #define PGN_SIDE_WHITE  0
12346 #define PGN_SIDE_BLACK  1
12347
12348 static int
12349 FindFirstMoveOutOfBook (int side)
12350 {
12351     int result = -1;
12352
12353     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12354         int index = backwardMostMove;
12355         int has_book_hit = 0;
12356
12357         if( (index % 2) != side ) {
12358             index++;
12359         }
12360
12361         while( index < forwardMostMove ) {
12362             /* Check to see if engine is in book */
12363             int depth = pvInfoList[index].depth;
12364             int score = pvInfoList[index].score;
12365             int in_book = 0;
12366
12367             if( depth <= 2 ) {
12368                 in_book = 1;
12369             }
12370             else if( score == 0 && depth == 63 ) {
12371                 in_book = 1; /* Zappa */
12372             }
12373             else if( score == 2 && depth == 99 ) {
12374                 in_book = 1; /* Abrok */
12375             }
12376
12377             has_book_hit += in_book;
12378
12379             if( ! in_book ) {
12380                 result = index;
12381
12382                 break;
12383             }
12384
12385             index += 2;
12386         }
12387     }
12388
12389     return result;
12390 }
12391
12392 void
12393 GetOutOfBookInfo (char * buf)
12394 {
12395     int oob[2];
12396     int i;
12397     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12398
12399     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12400     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12401
12402     *buf = '\0';
12403
12404     if( oob[0] >= 0 || oob[1] >= 0 ) {
12405         for( i=0; i<2; i++ ) {
12406             int idx = oob[i];
12407
12408             if( idx >= 0 ) {
12409                 if( i > 0 && oob[0] >= 0 ) {
12410                     strcat( buf, "   " );
12411                 }
12412
12413                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12414                 sprintf( buf+strlen(buf), "%s%.2f",
12415                     pvInfoList[idx].score >= 0 ? "+" : "",
12416                     pvInfoList[idx].score / 100.0 );
12417             }
12418         }
12419     }
12420 }
12421
12422 /* Save game in PGN style and close the file */
12423 int
12424 SaveGamePGN (FILE *f)
12425 {
12426     int i, offset, linelen, newblock;
12427     time_t tm;
12428 //    char *movetext;
12429     char numtext[32];
12430     int movelen, numlen, blank;
12431     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12432
12433     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12434
12435     tm = time((time_t *) NULL);
12436
12437     PrintPGNTags(f, &gameInfo);
12438
12439     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12440
12441     if (backwardMostMove > 0 || startedFromSetupPosition) {
12442         char *fen = PositionToFEN(backwardMostMove, NULL);
12443         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12444         fprintf(f, "\n{--------------\n");
12445         PrintPosition(f, backwardMostMove);
12446         fprintf(f, "--------------}\n");
12447         free(fen);
12448     }
12449     else {
12450         /* [AS] Out of book annotation */
12451         if( appData.saveOutOfBookInfo ) {
12452             char buf[64];
12453
12454             GetOutOfBookInfo( buf );
12455
12456             if( buf[0] != '\0' ) {
12457                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12458             }
12459         }
12460
12461         fprintf(f, "\n");
12462     }
12463
12464     i = backwardMostMove;
12465     linelen = 0;
12466     newblock = TRUE;
12467
12468     while (i < forwardMostMove) {
12469         /* Print comments preceding this move */
12470         if (commentList[i] != NULL) {
12471             if (linelen > 0) fprintf(f, "\n");
12472             fprintf(f, "%s", commentList[i]);
12473             linelen = 0;
12474             newblock = TRUE;
12475         }
12476
12477         /* Format move number */
12478         if ((i % 2) == 0)
12479           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12480         else
12481           if (newblock)
12482             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12483           else
12484             numtext[0] = NULLCHAR;
12485
12486         numlen = strlen(numtext);
12487         newblock = FALSE;
12488
12489         /* Print move number */
12490         blank = linelen > 0 && numlen > 0;
12491         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12492             fprintf(f, "\n");
12493             linelen = 0;
12494             blank = 0;
12495         }
12496         if (blank) {
12497             fprintf(f, " ");
12498             linelen++;
12499         }
12500         fprintf(f, "%s", numtext);
12501         linelen += numlen;
12502
12503         /* Get move */
12504         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12505         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12506
12507         /* Print move */
12508         blank = linelen > 0 && movelen > 0;
12509         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12510             fprintf(f, "\n");
12511             linelen = 0;
12512             blank = 0;
12513         }
12514         if (blank) {
12515             fprintf(f, " ");
12516             linelen++;
12517         }
12518         fprintf(f, "%s", move_buffer);
12519         linelen += movelen;
12520
12521         /* [AS] Add PV info if present */
12522         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12523             /* [HGM] add time */
12524             char buf[MSG_SIZ]; int seconds;
12525
12526             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12527
12528             if( seconds <= 0)
12529               buf[0] = 0;
12530             else
12531               if( seconds < 30 )
12532                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12533               else
12534                 {
12535                   seconds = (seconds + 4)/10; // round to full seconds
12536                   if( seconds < 60 )
12537                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12538                   else
12539                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12540                 }
12541
12542             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12543                       pvInfoList[i].score >= 0 ? "+" : "",
12544                       pvInfoList[i].score / 100.0,
12545                       pvInfoList[i].depth,
12546                       buf );
12547
12548             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12549
12550             /* Print score/depth */
12551             blank = linelen > 0 && movelen > 0;
12552             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12553                 fprintf(f, "\n");
12554                 linelen = 0;
12555                 blank = 0;
12556             }
12557             if (blank) {
12558                 fprintf(f, " ");
12559                 linelen++;
12560             }
12561             fprintf(f, "%s", move_buffer);
12562             linelen += movelen;
12563         }
12564
12565         i++;
12566     }
12567
12568     /* Start a new line */
12569     if (linelen > 0) fprintf(f, "\n");
12570
12571     /* Print comments after last move */
12572     if (commentList[i] != NULL) {
12573         fprintf(f, "%s\n", commentList[i]);
12574     }
12575
12576     /* Print result */
12577     if (gameInfo.resultDetails != NULL &&
12578         gameInfo.resultDetails[0] != NULLCHAR) {
12579         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12580                 PGNResult(gameInfo.result));
12581     } else {
12582         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12583     }
12584
12585     fclose(f);
12586     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12587     return TRUE;
12588 }
12589
12590 /* Save game in old style and close the file */
12591 int
12592 SaveGameOldStyle (FILE *f)
12593 {
12594     int i, offset;
12595     time_t tm;
12596
12597     tm = time((time_t *) NULL);
12598
12599     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12600     PrintOpponents(f);
12601
12602     if (backwardMostMove > 0 || startedFromSetupPosition) {
12603         fprintf(f, "\n[--------------\n");
12604         PrintPosition(f, backwardMostMove);
12605         fprintf(f, "--------------]\n");
12606     } else {
12607         fprintf(f, "\n");
12608     }
12609
12610     i = backwardMostMove;
12611     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12612
12613     while (i < forwardMostMove) {
12614         if (commentList[i] != NULL) {
12615             fprintf(f, "[%s]\n", commentList[i]);
12616         }
12617
12618         if ((i % 2) == 1) {
12619             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12620             i++;
12621         } else {
12622             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12623             i++;
12624             if (commentList[i] != NULL) {
12625                 fprintf(f, "\n");
12626                 continue;
12627             }
12628             if (i >= forwardMostMove) {
12629                 fprintf(f, "\n");
12630                 break;
12631             }
12632             fprintf(f, "%s\n", parseList[i]);
12633             i++;
12634         }
12635     }
12636
12637     if (commentList[i] != NULL) {
12638         fprintf(f, "[%s]\n", commentList[i]);
12639     }
12640
12641     /* This isn't really the old style, but it's close enough */
12642     if (gameInfo.resultDetails != NULL &&
12643         gameInfo.resultDetails[0] != NULLCHAR) {
12644         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12645                 gameInfo.resultDetails);
12646     } else {
12647         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12648     }
12649
12650     fclose(f);
12651     return TRUE;
12652 }
12653
12654 /* Save the current game to open file f and close the file */
12655 int
12656 SaveGame (FILE *f, int dummy, char *dummy2)
12657 {
12658     if (gameMode == EditPosition) EditPositionDone(TRUE);
12659     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12660     if (appData.oldSaveStyle)
12661       return SaveGameOldStyle(f);
12662     else
12663       return SaveGamePGN(f);
12664 }
12665
12666 /* Save the current position to the given file */
12667 int
12668 SavePositionToFile (char *filename)
12669 {
12670     FILE *f;
12671     char buf[MSG_SIZ];
12672
12673     if (strcmp(filename, "-") == 0) {
12674         return SavePosition(stdout, 0, NULL);
12675     } else {
12676         f = fopen(filename, "a");
12677         if (f == NULL) {
12678             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12679             DisplayError(buf, errno);
12680             return FALSE;
12681         } else {
12682             safeStrCpy(buf, lastMsg, MSG_SIZ);
12683             DisplayMessage(_("Waiting for access to save file"), "");
12684             flock(fileno(f), LOCK_EX); // [HGM] lock
12685             DisplayMessage(_("Saving position"), "");
12686             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12687             SavePosition(f, 0, NULL);
12688             DisplayMessage(buf, "");
12689             return TRUE;
12690         }
12691     }
12692 }
12693
12694 /* Save the current position to the given open file and close the file */
12695 int
12696 SavePosition (FILE *f, int dummy, char *dummy2)
12697 {
12698     time_t tm;
12699     char *fen;
12700
12701     if (gameMode == EditPosition) EditPositionDone(TRUE);
12702     if (appData.oldSaveStyle) {
12703         tm = time((time_t *) NULL);
12704
12705         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12706         PrintOpponents(f);
12707         fprintf(f, "[--------------\n");
12708         PrintPosition(f, currentMove);
12709         fprintf(f, "--------------]\n");
12710     } else {
12711         fen = PositionToFEN(currentMove, NULL);
12712         fprintf(f, "%s\n", fen);
12713         free(fen);
12714     }
12715     fclose(f);
12716     return TRUE;
12717 }
12718
12719 void
12720 ReloadCmailMsgEvent (int unregister)
12721 {
12722 #if !WIN32
12723     static char *inFilename = NULL;
12724     static char *outFilename;
12725     int i;
12726     struct stat inbuf, outbuf;
12727     int status;
12728
12729     /* Any registered moves are unregistered if unregister is set, */
12730     /* i.e. invoked by the signal handler */
12731     if (unregister) {
12732         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12733             cmailMoveRegistered[i] = FALSE;
12734             if (cmailCommentList[i] != NULL) {
12735                 free(cmailCommentList[i]);
12736                 cmailCommentList[i] = NULL;
12737             }
12738         }
12739         nCmailMovesRegistered = 0;
12740     }
12741
12742     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12743         cmailResult[i] = CMAIL_NOT_RESULT;
12744     }
12745     nCmailResults = 0;
12746
12747     if (inFilename == NULL) {
12748         /* Because the filenames are static they only get malloced once  */
12749         /* and they never get freed                                      */
12750         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12751         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12752
12753         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12754         sprintf(outFilename, "%s.out", appData.cmailGameName);
12755     }
12756
12757     status = stat(outFilename, &outbuf);
12758     if (status < 0) {
12759         cmailMailedMove = FALSE;
12760     } else {
12761         status = stat(inFilename, &inbuf);
12762         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12763     }
12764
12765     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12766        counts the games, notes how each one terminated, etc.
12767
12768        It would be nice to remove this kludge and instead gather all
12769        the information while building the game list.  (And to keep it
12770        in the game list nodes instead of having a bunch of fixed-size
12771        parallel arrays.)  Note this will require getting each game's
12772        termination from the PGN tags, as the game list builder does
12773        not process the game moves.  --mann
12774        */
12775     cmailMsgLoaded = TRUE;
12776     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12777
12778     /* Load first game in the file or popup game menu */
12779     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12780
12781 #endif /* !WIN32 */
12782     return;
12783 }
12784
12785 int
12786 RegisterMove ()
12787 {
12788     FILE *f;
12789     char string[MSG_SIZ];
12790
12791     if (   cmailMailedMove
12792         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12793         return TRUE;            /* Allow free viewing  */
12794     }
12795
12796     /* Unregister move to ensure that we don't leave RegisterMove        */
12797     /* with the move registered when the conditions for registering no   */
12798     /* longer hold                                                       */
12799     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12800         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12801         nCmailMovesRegistered --;
12802
12803         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12804           {
12805               free(cmailCommentList[lastLoadGameNumber - 1]);
12806               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12807           }
12808     }
12809
12810     if (cmailOldMove == -1) {
12811         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12812         return FALSE;
12813     }
12814
12815     if (currentMove > cmailOldMove + 1) {
12816         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12817         return FALSE;
12818     }
12819
12820     if (currentMove < cmailOldMove) {
12821         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12822         return FALSE;
12823     }
12824
12825     if (forwardMostMove > currentMove) {
12826         /* Silently truncate extra moves */
12827         TruncateGame();
12828     }
12829
12830     if (   (currentMove == cmailOldMove + 1)
12831         || (   (currentMove == cmailOldMove)
12832             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12833                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12834         if (gameInfo.result != GameUnfinished) {
12835             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12836         }
12837
12838         if (commentList[currentMove] != NULL) {
12839             cmailCommentList[lastLoadGameNumber - 1]
12840               = StrSave(commentList[currentMove]);
12841         }
12842         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12843
12844         if (appData.debugMode)
12845           fprintf(debugFP, "Saving %s for game %d\n",
12846                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12847
12848         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12849
12850         f = fopen(string, "w");
12851         if (appData.oldSaveStyle) {
12852             SaveGameOldStyle(f); /* also closes the file */
12853
12854             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12855             f = fopen(string, "w");
12856             SavePosition(f, 0, NULL); /* also closes the file */
12857         } else {
12858             fprintf(f, "{--------------\n");
12859             PrintPosition(f, currentMove);
12860             fprintf(f, "--------------}\n\n");
12861
12862             SaveGame(f, 0, NULL); /* also closes the file*/
12863         }
12864
12865         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12866         nCmailMovesRegistered ++;
12867     } else if (nCmailGames == 1) {
12868         DisplayError(_("You have not made a move yet"), 0);
12869         return FALSE;
12870     }
12871
12872     return TRUE;
12873 }
12874
12875 void
12876 MailMoveEvent ()
12877 {
12878 #if !WIN32
12879     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12880     FILE *commandOutput;
12881     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12882     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12883     int nBuffers;
12884     int i;
12885     int archived;
12886     char *arcDir;
12887
12888     if (! cmailMsgLoaded) {
12889         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12890         return;
12891     }
12892
12893     if (nCmailGames == nCmailResults) {
12894         DisplayError(_("No unfinished games"), 0);
12895         return;
12896     }
12897
12898 #if CMAIL_PROHIBIT_REMAIL
12899     if (cmailMailedMove) {
12900       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);
12901         DisplayError(msg, 0);
12902         return;
12903     }
12904 #endif
12905
12906     if (! (cmailMailedMove || RegisterMove())) return;
12907
12908     if (   cmailMailedMove
12909         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12910       snprintf(string, MSG_SIZ, partCommandString,
12911                appData.debugMode ? " -v" : "", appData.cmailGameName);
12912         commandOutput = popen(string, "r");
12913
12914         if (commandOutput == NULL) {
12915             DisplayError(_("Failed to invoke cmail"), 0);
12916         } else {
12917             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12918                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12919             }
12920             if (nBuffers > 1) {
12921                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12922                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12923                 nBytes = MSG_SIZ - 1;
12924             } else {
12925                 (void) memcpy(msg, buffer, nBytes);
12926             }
12927             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12928
12929             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12930                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12931
12932                 archived = TRUE;
12933                 for (i = 0; i < nCmailGames; i ++) {
12934                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12935                         archived = FALSE;
12936                     }
12937                 }
12938                 if (   archived
12939                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12940                         != NULL)) {
12941                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12942                            arcDir,
12943                            appData.cmailGameName,
12944                            gameInfo.date);
12945                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12946                     cmailMsgLoaded = FALSE;
12947                 }
12948             }
12949
12950             DisplayInformation(msg);
12951             pclose(commandOutput);
12952         }
12953     } else {
12954         if ((*cmailMsg) != '\0') {
12955             DisplayInformation(cmailMsg);
12956         }
12957     }
12958
12959     return;
12960 #endif /* !WIN32 */
12961 }
12962
12963 char *
12964 CmailMsg ()
12965 {
12966 #if WIN32
12967     return NULL;
12968 #else
12969     int  prependComma = 0;
12970     char number[5];
12971     char string[MSG_SIZ];       /* Space for game-list */
12972     int  i;
12973
12974     if (!cmailMsgLoaded) return "";
12975
12976     if (cmailMailedMove) {
12977       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12978     } else {
12979         /* Create a list of games left */
12980       snprintf(string, MSG_SIZ, "[");
12981         for (i = 0; i < nCmailGames; i ++) {
12982             if (! (   cmailMoveRegistered[i]
12983                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12984                 if (prependComma) {
12985                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12986                 } else {
12987                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12988                     prependComma = 1;
12989                 }
12990
12991                 strcat(string, number);
12992             }
12993         }
12994         strcat(string, "]");
12995
12996         if (nCmailMovesRegistered + nCmailResults == 0) {
12997             switch (nCmailGames) {
12998               case 1:
12999                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13000                 break;
13001
13002               case 2:
13003                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13004                 break;
13005
13006               default:
13007                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13008                          nCmailGames);
13009                 break;
13010             }
13011         } else {
13012             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13013               case 1:
13014                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13015                          string);
13016                 break;
13017
13018               case 0:
13019                 if (nCmailResults == nCmailGames) {
13020                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13021                 } else {
13022                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13023                 }
13024                 break;
13025
13026               default:
13027                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13028                          string);
13029             }
13030         }
13031     }
13032     return cmailMsg;
13033 #endif /* WIN32 */
13034 }
13035
13036 void
13037 ResetGameEvent ()
13038 {
13039     if (gameMode == Training)
13040       SetTrainingModeOff();
13041
13042     Reset(TRUE, TRUE);
13043     cmailMsgLoaded = FALSE;
13044     if (appData.icsActive) {
13045       SendToICS(ics_prefix);
13046       SendToICS("refresh\n");
13047     }
13048 }
13049
13050 void
13051 ExitEvent (int status)
13052 {
13053     exiting++;
13054     if (exiting > 2) {
13055       /* Give up on clean exit */
13056       exit(status);
13057     }
13058     if (exiting > 1) {
13059       /* Keep trying for clean exit */
13060       return;
13061     }
13062
13063     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13064
13065     if (telnetISR != NULL) {
13066       RemoveInputSource(telnetISR);
13067     }
13068     if (icsPR != NoProc) {
13069       DestroyChildProcess(icsPR, TRUE);
13070     }
13071
13072     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13073     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13074
13075     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13076     /* make sure this other one finishes before killing it!                  */
13077     if(endingGame) { int count = 0;
13078         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13079         while(endingGame && count++ < 10) DoSleep(1);
13080         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13081     }
13082
13083     /* Kill off chess programs */
13084     if (first.pr != NoProc) {
13085         ExitAnalyzeMode();
13086
13087         DoSleep( appData.delayBeforeQuit );
13088         SendToProgram("quit\n", &first);
13089         DoSleep( appData.delayAfterQuit );
13090         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13091     }
13092     if (second.pr != NoProc) {
13093         DoSleep( appData.delayBeforeQuit );
13094         SendToProgram("quit\n", &second);
13095         DoSleep( appData.delayAfterQuit );
13096         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13097     }
13098     if (first.isr != NULL) {
13099         RemoveInputSource(first.isr);
13100     }
13101     if (second.isr != NULL) {
13102         RemoveInputSource(second.isr);
13103     }
13104
13105     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13106     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13107
13108     ShutDownFrontEnd();
13109     exit(status);
13110 }
13111
13112 void
13113 PauseEvent ()
13114 {
13115     if (appData.debugMode)
13116         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13117     if (pausing) {
13118         pausing = FALSE;
13119         ModeHighlight();
13120         if (gameMode == MachinePlaysWhite ||
13121             gameMode == MachinePlaysBlack) {
13122             StartClocks();
13123         } else {
13124             DisplayBothClocks();
13125         }
13126         if (gameMode == PlayFromGameFile) {
13127             if (appData.timeDelay >= 0)
13128                 AutoPlayGameLoop();
13129         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13130             Reset(FALSE, TRUE);
13131             SendToICS(ics_prefix);
13132             SendToICS("refresh\n");
13133         } else if (currentMove < forwardMostMove) {
13134             ForwardInner(forwardMostMove);
13135         }
13136         pauseExamInvalid = FALSE;
13137     } else {
13138         switch (gameMode) {
13139           default:
13140             return;
13141           case IcsExamining:
13142             pauseExamForwardMostMove = forwardMostMove;
13143             pauseExamInvalid = FALSE;
13144             /* fall through */
13145           case IcsObserving:
13146           case IcsPlayingWhite:
13147           case IcsPlayingBlack:
13148             pausing = TRUE;
13149             ModeHighlight();
13150             return;
13151           case PlayFromGameFile:
13152             (void) StopLoadGameTimer();
13153             pausing = TRUE;
13154             ModeHighlight();
13155             break;
13156           case BeginningOfGame:
13157             if (appData.icsActive) return;
13158             /* else fall through */
13159           case MachinePlaysWhite:
13160           case MachinePlaysBlack:
13161           case TwoMachinesPlay:
13162             if (forwardMostMove == 0)
13163               return;           /* don't pause if no one has moved */
13164             if ((gameMode == MachinePlaysWhite &&
13165                  !WhiteOnMove(forwardMostMove)) ||
13166                 (gameMode == MachinePlaysBlack &&
13167                  WhiteOnMove(forwardMostMove))) {
13168                 StopClocks();
13169             }
13170             pausing = TRUE;
13171             ModeHighlight();
13172             break;
13173         }
13174     }
13175 }
13176
13177 void
13178 EditCommentEvent ()
13179 {
13180     char title[MSG_SIZ];
13181
13182     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13183       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13184     } else {
13185       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13186                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13187                parseList[currentMove - 1]);
13188     }
13189
13190     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13191 }
13192
13193
13194 void
13195 EditTagsEvent ()
13196 {
13197     char *tags = PGNTags(&gameInfo);
13198     bookUp = FALSE;
13199     EditTagsPopUp(tags, NULL);
13200     free(tags);
13201 }
13202
13203 void
13204 AnalyzeModeEvent ()
13205 {
13206     if (appData.noChessProgram || gameMode == AnalyzeMode)
13207       return;
13208
13209     if (gameMode != AnalyzeFile) {
13210         if (!appData.icsEngineAnalyze) {
13211                EditGameEvent();
13212                if (gameMode != EditGame) return;
13213         }
13214         ResurrectChessProgram();
13215         SendToProgram("analyze\n", &first);
13216         first.analyzing = TRUE;
13217         /*first.maybeThinking = TRUE;*/
13218         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13219         EngineOutputPopUp();
13220     }
13221     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13222     pausing = FALSE;
13223     ModeHighlight();
13224     SetGameInfo();
13225
13226     StartAnalysisClock();
13227     GetTimeMark(&lastNodeCountTime);
13228     lastNodeCount = 0;
13229 }
13230
13231 void
13232 AnalyzeFileEvent ()
13233 {
13234     if (appData.noChessProgram || gameMode == AnalyzeFile)
13235       return;
13236
13237     if (gameMode != AnalyzeMode) {
13238         EditGameEvent();
13239         if (gameMode != EditGame) return;
13240         ResurrectChessProgram();
13241         SendToProgram("analyze\n", &first);
13242         first.analyzing = TRUE;
13243         /*first.maybeThinking = TRUE;*/
13244         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13245         EngineOutputPopUp();
13246     }
13247     gameMode = AnalyzeFile;
13248     pausing = FALSE;
13249     ModeHighlight();
13250     SetGameInfo();
13251
13252     StartAnalysisClock();
13253     GetTimeMark(&lastNodeCountTime);
13254     lastNodeCount = 0;
13255     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13256 }
13257
13258 void
13259 MachineWhiteEvent ()
13260 {
13261     char buf[MSG_SIZ];
13262     char *bookHit = NULL;
13263
13264     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13265       return;
13266
13267
13268     if (gameMode == PlayFromGameFile ||
13269         gameMode == TwoMachinesPlay  ||
13270         gameMode == Training         ||
13271         gameMode == AnalyzeMode      ||
13272         gameMode == EndOfGame)
13273         EditGameEvent();
13274
13275     if (gameMode == EditPosition)
13276         EditPositionDone(TRUE);
13277
13278     if (!WhiteOnMove(currentMove)) {
13279         DisplayError(_("It is not White's turn"), 0);
13280         return;
13281     }
13282
13283     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13284       ExitAnalyzeMode();
13285
13286     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13287         gameMode == AnalyzeFile)
13288         TruncateGame();
13289
13290     ResurrectChessProgram();    /* in case it isn't running */
13291     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13292         gameMode = MachinePlaysWhite;
13293         ResetClocks();
13294     } else
13295     gameMode = MachinePlaysWhite;
13296     pausing = FALSE;
13297     ModeHighlight();
13298     SetGameInfo();
13299     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13300     DisplayTitle(buf);
13301     if (first.sendName) {
13302       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13303       SendToProgram(buf, &first);
13304     }
13305     if (first.sendTime) {
13306       if (first.useColors) {
13307         SendToProgram("black\n", &first); /*gnu kludge*/
13308       }
13309       SendTimeRemaining(&first, TRUE);
13310     }
13311     if (first.useColors) {
13312       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13313     }
13314     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13315     SetMachineThinkingEnables();
13316     first.maybeThinking = TRUE;
13317     StartClocks();
13318     firstMove = FALSE;
13319
13320     if (appData.autoFlipView && !flipView) {
13321       flipView = !flipView;
13322       DrawPosition(FALSE, NULL);
13323       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13324     }
13325
13326     if(bookHit) { // [HGM] book: simulate book reply
13327         static char bookMove[MSG_SIZ]; // a bit generous?
13328
13329         programStats.nodes = programStats.depth = programStats.time =
13330         programStats.score = programStats.got_only_move = 0;
13331         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13332
13333         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13334         strcat(bookMove, bookHit);
13335         HandleMachineMove(bookMove, &first);
13336     }
13337 }
13338
13339 void
13340 MachineBlackEvent ()
13341 {
13342   char buf[MSG_SIZ];
13343   char *bookHit = NULL;
13344
13345     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13346         return;
13347
13348
13349     if (gameMode == PlayFromGameFile ||
13350         gameMode == TwoMachinesPlay  ||
13351         gameMode == Training         ||
13352         gameMode == AnalyzeMode      ||
13353         gameMode == EndOfGame)
13354         EditGameEvent();
13355
13356     if (gameMode == EditPosition)
13357         EditPositionDone(TRUE);
13358
13359     if (WhiteOnMove(currentMove)) {
13360         DisplayError(_("It is not Black's turn"), 0);
13361         return;
13362     }
13363
13364     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13365       ExitAnalyzeMode();
13366
13367     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13368         gameMode == AnalyzeFile)
13369         TruncateGame();
13370
13371     ResurrectChessProgram();    /* in case it isn't running */
13372     gameMode = MachinePlaysBlack;
13373     pausing = FALSE;
13374     ModeHighlight();
13375     SetGameInfo();
13376     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13377     DisplayTitle(buf);
13378     if (first.sendName) {
13379       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13380       SendToProgram(buf, &first);
13381     }
13382     if (first.sendTime) {
13383       if (first.useColors) {
13384         SendToProgram("white\n", &first); /*gnu kludge*/
13385       }
13386       SendTimeRemaining(&first, FALSE);
13387     }
13388     if (first.useColors) {
13389       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13390     }
13391     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13392     SetMachineThinkingEnables();
13393     first.maybeThinking = TRUE;
13394     StartClocks();
13395
13396     if (appData.autoFlipView && flipView) {
13397       flipView = !flipView;
13398       DrawPosition(FALSE, NULL);
13399       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13400     }
13401     if(bookHit) { // [HGM] book: simulate book reply
13402         static char bookMove[MSG_SIZ]; // a bit generous?
13403
13404         programStats.nodes = programStats.depth = programStats.time =
13405         programStats.score = programStats.got_only_move = 0;
13406         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13407
13408         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13409         strcat(bookMove, bookHit);
13410         HandleMachineMove(bookMove, &first);
13411     }
13412 }
13413
13414
13415 void
13416 DisplayTwoMachinesTitle ()
13417 {
13418     char buf[MSG_SIZ];
13419     if (appData.matchGames > 0) {
13420         if(appData.tourneyFile[0]) {
13421           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13422                    gameInfo.white, _("vs."), gameInfo.black,
13423                    nextGame+1, appData.matchGames+1,
13424                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13425         } else 
13426         if (first.twoMachinesColor[0] == 'w') {
13427           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13428                    gameInfo.white, _("vs."),  gameInfo.black,
13429                    first.matchWins, second.matchWins,
13430                    matchGame - 1 - (first.matchWins + second.matchWins));
13431         } else {
13432           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13433                    gameInfo.white, _("vs."), gameInfo.black,
13434                    second.matchWins, first.matchWins,
13435                    matchGame - 1 - (first.matchWins + second.matchWins));
13436         }
13437     } else {
13438       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13439     }
13440     DisplayTitle(buf);
13441 }
13442
13443 void
13444 SettingsMenuIfReady ()
13445 {
13446   if (second.lastPing != second.lastPong) {
13447     DisplayMessage("", _("Waiting for second chess program"));
13448     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13449     return;
13450   }
13451   ThawUI();
13452   DisplayMessage("", "");
13453   SettingsPopUp(&second);
13454 }
13455
13456 int
13457 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13458 {
13459     char buf[MSG_SIZ];
13460     if (cps->pr == NoProc) {
13461         StartChessProgram(cps);
13462         if (cps->protocolVersion == 1) {
13463           retry();
13464         } else {
13465           /* kludge: allow timeout for initial "feature" command */
13466           FreezeUI();
13467           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13468           DisplayMessage("", buf);
13469           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13470         }
13471         return 1;
13472     }
13473     return 0;
13474 }
13475
13476 void
13477 TwoMachinesEvent P((void))
13478 {
13479     int i;
13480     char buf[MSG_SIZ];
13481     ChessProgramState *onmove;
13482     char *bookHit = NULL;
13483     static int stalling = 0;
13484     TimeMark now;
13485     long wait;
13486
13487     if (appData.noChessProgram) return;
13488
13489     switch (gameMode) {
13490       case TwoMachinesPlay:
13491         return;
13492       case MachinePlaysWhite:
13493       case MachinePlaysBlack:
13494         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13495             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13496             return;
13497         }
13498         /* fall through */
13499       case BeginningOfGame:
13500       case PlayFromGameFile:
13501       case EndOfGame:
13502         EditGameEvent();
13503         if (gameMode != EditGame) return;
13504         break;
13505       case EditPosition:
13506         EditPositionDone(TRUE);
13507         break;
13508       case AnalyzeMode:
13509       case AnalyzeFile:
13510         ExitAnalyzeMode();
13511         break;
13512       case EditGame:
13513       default:
13514         break;
13515     }
13516
13517 //    forwardMostMove = currentMove;
13518     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13519
13520     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13521
13522     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13523     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13524       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13525       return;
13526     }
13527     if(!stalling) {
13528       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13529       SendToProgram("force\n", &second);
13530       stalling = 1;
13531       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13532       return;
13533     }
13534     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13535     if(appData.matchPause>10000 || appData.matchPause<10)
13536                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13537     wait = SubtractTimeMarks(&now, &pauseStart);
13538     if(wait < appData.matchPause) {
13539         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13540         return;
13541     }
13542     // we are now committed to starting the game
13543     stalling = 0;
13544     DisplayMessage("", "");
13545     if (startedFromSetupPosition) {
13546         SendBoard(&second, backwardMostMove);
13547     if (appData.debugMode) {
13548         fprintf(debugFP, "Two Machines\n");
13549     }
13550     }
13551     for (i = backwardMostMove; i < forwardMostMove; i++) {
13552         SendMoveToProgram(i, &second);
13553     }
13554
13555     gameMode = TwoMachinesPlay;
13556     pausing = FALSE;
13557     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13558     SetGameInfo();
13559     DisplayTwoMachinesTitle();
13560     firstMove = TRUE;
13561     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13562         onmove = &first;
13563     } else {
13564         onmove = &second;
13565     }
13566     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13567     SendToProgram(first.computerString, &first);
13568     if (first.sendName) {
13569       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13570       SendToProgram(buf, &first);
13571     }
13572     SendToProgram(second.computerString, &second);
13573     if (second.sendName) {
13574       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13575       SendToProgram(buf, &second);
13576     }
13577
13578     ResetClocks();
13579     if (!first.sendTime || !second.sendTime) {
13580         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13581         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13582     }
13583     if (onmove->sendTime) {
13584       if (onmove->useColors) {
13585         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13586       }
13587       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13588     }
13589     if (onmove->useColors) {
13590       SendToProgram(onmove->twoMachinesColor, onmove);
13591     }
13592     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13593 //    SendToProgram("go\n", onmove);
13594     onmove->maybeThinking = TRUE;
13595     SetMachineThinkingEnables();
13596
13597     StartClocks();
13598
13599     if(bookHit) { // [HGM] book: simulate book reply
13600         static char bookMove[MSG_SIZ]; // a bit generous?
13601
13602         programStats.nodes = programStats.depth = programStats.time =
13603         programStats.score = programStats.got_only_move = 0;
13604         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13605
13606         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13607         strcat(bookMove, bookHit);
13608         savedMessage = bookMove; // args for deferred call
13609         savedState = onmove;
13610         ScheduleDelayedEvent(DeferredBookMove, 1);
13611     }
13612 }
13613
13614 void
13615 TrainingEvent ()
13616 {
13617     if (gameMode == Training) {
13618       SetTrainingModeOff();
13619       gameMode = PlayFromGameFile;
13620       DisplayMessage("", _("Training mode off"));
13621     } else {
13622       gameMode = Training;
13623       animateTraining = appData.animate;
13624
13625       /* make sure we are not already at the end of the game */
13626       if (currentMove < forwardMostMove) {
13627         SetTrainingModeOn();
13628         DisplayMessage("", _("Training mode on"));
13629       } else {
13630         gameMode = PlayFromGameFile;
13631         DisplayError(_("Already at end of game"), 0);
13632       }
13633     }
13634     ModeHighlight();
13635 }
13636
13637 void
13638 IcsClientEvent ()
13639 {
13640     if (!appData.icsActive) return;
13641     switch (gameMode) {
13642       case IcsPlayingWhite:
13643       case IcsPlayingBlack:
13644       case IcsObserving:
13645       case IcsIdle:
13646       case BeginningOfGame:
13647       case IcsExamining:
13648         return;
13649
13650       case EditGame:
13651         break;
13652
13653       case EditPosition:
13654         EditPositionDone(TRUE);
13655         break;
13656
13657       case AnalyzeMode:
13658       case AnalyzeFile:
13659         ExitAnalyzeMode();
13660         break;
13661
13662       default:
13663         EditGameEvent();
13664         break;
13665     }
13666
13667     gameMode = IcsIdle;
13668     ModeHighlight();
13669     return;
13670 }
13671
13672 void
13673 EditGameEvent ()
13674 {
13675     int i;
13676
13677     switch (gameMode) {
13678       case Training:
13679         SetTrainingModeOff();
13680         break;
13681       case MachinePlaysWhite:
13682       case MachinePlaysBlack:
13683       case BeginningOfGame:
13684         SendToProgram("force\n", &first);
13685         SetUserThinkingEnables();
13686         break;
13687       case PlayFromGameFile:
13688         (void) StopLoadGameTimer();
13689         if (gameFileFP != NULL) {
13690             gameFileFP = NULL;
13691         }
13692         break;
13693       case EditPosition:
13694         EditPositionDone(TRUE);
13695         break;
13696       case AnalyzeMode:
13697       case AnalyzeFile:
13698         ExitAnalyzeMode();
13699         SendToProgram("force\n", &first);
13700         break;
13701       case TwoMachinesPlay:
13702         GameEnds(EndOfFile, NULL, GE_PLAYER);
13703         ResurrectChessProgram();
13704         SetUserThinkingEnables();
13705         break;
13706       case EndOfGame:
13707         ResurrectChessProgram();
13708         break;
13709       case IcsPlayingBlack:
13710       case IcsPlayingWhite:
13711         DisplayError(_("Warning: You are still playing a game"), 0);
13712         break;
13713       case IcsObserving:
13714         DisplayError(_("Warning: You are still observing a game"), 0);
13715         break;
13716       case IcsExamining:
13717         DisplayError(_("Warning: You are still examining a game"), 0);
13718         break;
13719       case IcsIdle:
13720         break;
13721       case EditGame:
13722       default:
13723         return;
13724     }
13725
13726     pausing = FALSE;
13727     StopClocks();
13728     first.offeredDraw = second.offeredDraw = 0;
13729
13730     if (gameMode == PlayFromGameFile) {
13731         whiteTimeRemaining = timeRemaining[0][currentMove];
13732         blackTimeRemaining = timeRemaining[1][currentMove];
13733         DisplayTitle("");
13734     }
13735
13736     if (gameMode == MachinePlaysWhite ||
13737         gameMode == MachinePlaysBlack ||
13738         gameMode == TwoMachinesPlay ||
13739         gameMode == EndOfGame) {
13740         i = forwardMostMove;
13741         while (i > currentMove) {
13742             SendToProgram("undo\n", &first);
13743             i--;
13744         }
13745         if(!adjustedClock) {
13746         whiteTimeRemaining = timeRemaining[0][currentMove];
13747         blackTimeRemaining = timeRemaining[1][currentMove];
13748         DisplayBothClocks();
13749         }
13750         if (whiteFlag || blackFlag) {
13751             whiteFlag = blackFlag = 0;
13752         }
13753         DisplayTitle("");
13754     }
13755
13756     gameMode = EditGame;
13757     ModeHighlight();
13758     SetGameInfo();
13759 }
13760
13761
13762 void
13763 EditPositionEvent ()
13764 {
13765     if (gameMode == EditPosition) {
13766         EditGameEvent();
13767         return;
13768     }
13769
13770     EditGameEvent();
13771     if (gameMode != EditGame) return;
13772
13773     gameMode = EditPosition;
13774     ModeHighlight();
13775     SetGameInfo();
13776     if (currentMove > 0)
13777       CopyBoard(boards[0], boards[currentMove]);
13778
13779     blackPlaysFirst = !WhiteOnMove(currentMove);
13780     ResetClocks();
13781     currentMove = forwardMostMove = backwardMostMove = 0;
13782     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13783     DisplayMove(-1);
13784     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13785 }
13786
13787 void
13788 ExitAnalyzeMode ()
13789 {
13790     /* [DM] icsEngineAnalyze - possible call from other functions */
13791     if (appData.icsEngineAnalyze) {
13792         appData.icsEngineAnalyze = FALSE;
13793
13794         DisplayMessage("",_("Close ICS engine analyze..."));
13795     }
13796     if (first.analysisSupport && first.analyzing) {
13797       SendToProgram("exit\n", &first);
13798       first.analyzing = FALSE;
13799     }
13800     thinkOutput[0] = NULLCHAR;
13801 }
13802
13803 void
13804 EditPositionDone (Boolean fakeRights)
13805 {
13806     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13807
13808     startedFromSetupPosition = TRUE;
13809     InitChessProgram(&first, FALSE);
13810     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13811       boards[0][EP_STATUS] = EP_NONE;
13812       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13813     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13814         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13815         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13816       } else boards[0][CASTLING][2] = NoRights;
13817     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13818         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13819         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13820       } else boards[0][CASTLING][5] = NoRights;
13821     }
13822     SendToProgram("force\n", &first);
13823     if (blackPlaysFirst) {
13824         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13825         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13826         currentMove = forwardMostMove = backwardMostMove = 1;
13827         CopyBoard(boards[1], boards[0]);
13828     } else {
13829         currentMove = forwardMostMove = backwardMostMove = 0;
13830     }
13831     SendBoard(&first, forwardMostMove);
13832     if (appData.debugMode) {
13833         fprintf(debugFP, "EditPosDone\n");
13834     }
13835     DisplayTitle("");
13836     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13837     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13838     gameMode = EditGame;
13839     ModeHighlight();
13840     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13841     ClearHighlights(); /* [AS] */
13842 }
13843
13844 /* Pause for `ms' milliseconds */
13845 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13846 void
13847 TimeDelay (long ms)
13848 {
13849     TimeMark m1, m2;
13850
13851     GetTimeMark(&m1);
13852     do {
13853         GetTimeMark(&m2);
13854     } while (SubtractTimeMarks(&m2, &m1) < ms);
13855 }
13856
13857 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13858 void
13859 SendMultiLineToICS (char *buf)
13860 {
13861     char temp[MSG_SIZ+1], *p;
13862     int len;
13863
13864     len = strlen(buf);
13865     if (len > MSG_SIZ)
13866       len = MSG_SIZ;
13867
13868     strncpy(temp, buf, len);
13869     temp[len] = 0;
13870
13871     p = temp;
13872     while (*p) {
13873         if (*p == '\n' || *p == '\r')
13874           *p = ' ';
13875         ++p;
13876     }
13877
13878     strcat(temp, "\n");
13879     SendToICS(temp);
13880     SendToPlayer(temp, strlen(temp));
13881 }
13882
13883 void
13884 SetWhiteToPlayEvent ()
13885 {
13886     if (gameMode == EditPosition) {
13887         blackPlaysFirst = FALSE;
13888         DisplayBothClocks();    /* works because currentMove is 0 */
13889     } else if (gameMode == IcsExamining) {
13890         SendToICS(ics_prefix);
13891         SendToICS("tomove white\n");
13892     }
13893 }
13894
13895 void
13896 SetBlackToPlayEvent ()
13897 {
13898     if (gameMode == EditPosition) {
13899         blackPlaysFirst = TRUE;
13900         currentMove = 1;        /* kludge */
13901         DisplayBothClocks();
13902         currentMove = 0;
13903     } else if (gameMode == IcsExamining) {
13904         SendToICS(ics_prefix);
13905         SendToICS("tomove black\n");
13906     }
13907 }
13908
13909 void
13910 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13911 {
13912     char buf[MSG_SIZ];
13913     ChessSquare piece = boards[0][y][x];
13914
13915     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13916
13917     switch (selection) {
13918       case ClearBoard:
13919         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13920             SendToICS(ics_prefix);
13921             SendToICS("bsetup clear\n");
13922         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13923             SendToICS(ics_prefix);
13924             SendToICS("clearboard\n");
13925         } else {
13926             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13927                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13928                 for (y = 0; y < BOARD_HEIGHT; y++) {
13929                     if (gameMode == IcsExamining) {
13930                         if (boards[currentMove][y][x] != EmptySquare) {
13931                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13932                                     AAA + x, ONE + y);
13933                             SendToICS(buf);
13934                         }
13935                     } else {
13936                         boards[0][y][x] = p;
13937                     }
13938                 }
13939             }
13940         }
13941         if (gameMode == EditPosition) {
13942             DrawPosition(FALSE, boards[0]);
13943         }
13944         break;
13945
13946       case WhitePlay:
13947         SetWhiteToPlayEvent();
13948         break;
13949
13950       case BlackPlay:
13951         SetBlackToPlayEvent();
13952         break;
13953
13954       case EmptySquare:
13955         if (gameMode == IcsExamining) {
13956             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13957             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13958             SendToICS(buf);
13959         } else {
13960             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13961                 if(x == BOARD_LEFT-2) {
13962                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13963                     boards[0][y][1] = 0;
13964                 } else
13965                 if(x == BOARD_RGHT+1) {
13966                     if(y >= gameInfo.holdingsSize) break;
13967                     boards[0][y][BOARD_WIDTH-2] = 0;
13968                 } else break;
13969             }
13970             boards[0][y][x] = EmptySquare;
13971             DrawPosition(FALSE, boards[0]);
13972         }
13973         break;
13974
13975       case PromotePiece:
13976         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13977            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13978             selection = (ChessSquare) (PROMOTED piece);
13979         } else if(piece == EmptySquare) selection = WhiteSilver;
13980         else selection = (ChessSquare)((int)piece - 1);
13981         goto defaultlabel;
13982
13983       case DemotePiece:
13984         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13985            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13986             selection = (ChessSquare) (DEMOTED piece);
13987         } else if(piece == EmptySquare) selection = BlackSilver;
13988         else selection = (ChessSquare)((int)piece + 1);
13989         goto defaultlabel;
13990
13991       case WhiteQueen:
13992       case BlackQueen:
13993         if(gameInfo.variant == VariantShatranj ||
13994            gameInfo.variant == VariantXiangqi  ||
13995            gameInfo.variant == VariantCourier  ||
13996            gameInfo.variant == VariantMakruk     )
13997             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13998         goto defaultlabel;
13999
14000       case WhiteKing:
14001       case BlackKing:
14002         if(gameInfo.variant == VariantXiangqi)
14003             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14004         if(gameInfo.variant == VariantKnightmate)
14005             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14006       default:
14007         defaultlabel:
14008         if (gameMode == IcsExamining) {
14009             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14010             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14011                      PieceToChar(selection), AAA + x, ONE + y);
14012             SendToICS(buf);
14013         } else {
14014             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14015                 int n;
14016                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14017                     n = PieceToNumber(selection - BlackPawn);
14018                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14019                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14020                     boards[0][BOARD_HEIGHT-1-n][1]++;
14021                 } else
14022                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14023                     n = PieceToNumber(selection);
14024                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14025                     boards[0][n][BOARD_WIDTH-1] = selection;
14026                     boards[0][n][BOARD_WIDTH-2]++;
14027                 }
14028             } else
14029             boards[0][y][x] = selection;
14030             DrawPosition(TRUE, boards[0]);
14031             ClearHighlights();
14032             fromX = fromY = -1;
14033         }
14034         break;
14035     }
14036 }
14037
14038
14039 void
14040 DropMenuEvent (ChessSquare selection, int x, int y)
14041 {
14042     ChessMove moveType;
14043
14044     switch (gameMode) {
14045       case IcsPlayingWhite:
14046       case MachinePlaysBlack:
14047         if (!WhiteOnMove(currentMove)) {
14048             DisplayMoveError(_("It is Black's turn"));
14049             return;
14050         }
14051         moveType = WhiteDrop;
14052         break;
14053       case IcsPlayingBlack:
14054       case MachinePlaysWhite:
14055         if (WhiteOnMove(currentMove)) {
14056             DisplayMoveError(_("It is White's turn"));
14057             return;
14058         }
14059         moveType = BlackDrop;
14060         break;
14061       case EditGame:
14062         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14063         break;
14064       default:
14065         return;
14066     }
14067
14068     if (moveType == BlackDrop && selection < BlackPawn) {
14069       selection = (ChessSquare) ((int) selection
14070                                  + (int) BlackPawn - (int) WhitePawn);
14071     }
14072     if (boards[currentMove][y][x] != EmptySquare) {
14073         DisplayMoveError(_("That square is occupied"));
14074         return;
14075     }
14076
14077     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14078 }
14079
14080 void
14081 AcceptEvent ()
14082 {
14083     /* Accept a pending offer of any kind from opponent */
14084
14085     if (appData.icsActive) {
14086         SendToICS(ics_prefix);
14087         SendToICS("accept\n");
14088     } else if (cmailMsgLoaded) {
14089         if (currentMove == cmailOldMove &&
14090             commentList[cmailOldMove] != NULL &&
14091             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14092                    "Black offers a draw" : "White offers a draw")) {
14093             TruncateGame();
14094             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14095             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14096         } else {
14097             DisplayError(_("There is no pending offer on this move"), 0);
14098             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14099         }
14100     } else {
14101         /* Not used for offers from chess program */
14102     }
14103 }
14104
14105 void
14106 DeclineEvent ()
14107 {
14108     /* Decline a pending offer of any kind from opponent */
14109
14110     if (appData.icsActive) {
14111         SendToICS(ics_prefix);
14112         SendToICS("decline\n");
14113     } else if (cmailMsgLoaded) {
14114         if (currentMove == cmailOldMove &&
14115             commentList[cmailOldMove] != NULL &&
14116             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14117                    "Black offers a draw" : "White offers a draw")) {
14118 #ifdef NOTDEF
14119             AppendComment(cmailOldMove, "Draw declined", TRUE);
14120             DisplayComment(cmailOldMove - 1, "Draw declined");
14121 #endif /*NOTDEF*/
14122         } else {
14123             DisplayError(_("There is no pending offer on this move"), 0);
14124         }
14125     } else {
14126         /* Not used for offers from chess program */
14127     }
14128 }
14129
14130 void
14131 RematchEvent ()
14132 {
14133     /* Issue ICS rematch command */
14134     if (appData.icsActive) {
14135         SendToICS(ics_prefix);
14136         SendToICS("rematch\n");
14137     }
14138 }
14139
14140 void
14141 CallFlagEvent ()
14142 {
14143     /* Call your opponent's flag (claim a win on time) */
14144     if (appData.icsActive) {
14145         SendToICS(ics_prefix);
14146         SendToICS("flag\n");
14147     } else {
14148         switch (gameMode) {
14149           default:
14150             return;
14151           case MachinePlaysWhite:
14152             if (whiteFlag) {
14153                 if (blackFlag)
14154                   GameEnds(GameIsDrawn, "Both players ran out of time",
14155                            GE_PLAYER);
14156                 else
14157                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14158             } else {
14159                 DisplayError(_("Your opponent is not out of time"), 0);
14160             }
14161             break;
14162           case MachinePlaysBlack:
14163             if (blackFlag) {
14164                 if (whiteFlag)
14165                   GameEnds(GameIsDrawn, "Both players ran out of time",
14166                            GE_PLAYER);
14167                 else
14168                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14169             } else {
14170                 DisplayError(_("Your opponent is not out of time"), 0);
14171             }
14172             break;
14173         }
14174     }
14175 }
14176
14177 void
14178 ClockClick (int which)
14179 {       // [HGM] code moved to back-end from winboard.c
14180         if(which) { // black clock
14181           if (gameMode == EditPosition || gameMode == IcsExamining) {
14182             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14183             SetBlackToPlayEvent();
14184           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14185           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14186           } else if (shiftKey) {
14187             AdjustClock(which, -1);
14188           } else if (gameMode == IcsPlayingWhite ||
14189                      gameMode == MachinePlaysBlack) {
14190             CallFlagEvent();
14191           }
14192         } else { // white clock
14193           if (gameMode == EditPosition || gameMode == IcsExamining) {
14194             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14195             SetWhiteToPlayEvent();
14196           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14197           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14198           } else if (shiftKey) {
14199             AdjustClock(which, -1);
14200           } else if (gameMode == IcsPlayingBlack ||
14201                    gameMode == MachinePlaysWhite) {
14202             CallFlagEvent();
14203           }
14204         }
14205 }
14206
14207 void
14208 DrawEvent ()
14209 {
14210     /* Offer draw or accept pending draw offer from opponent */
14211
14212     if (appData.icsActive) {
14213         /* Note: tournament rules require draw offers to be
14214            made after you make your move but before you punch
14215            your clock.  Currently ICS doesn't let you do that;
14216            instead, you immediately punch your clock after making
14217            a move, but you can offer a draw at any time. */
14218
14219         SendToICS(ics_prefix);
14220         SendToICS("draw\n");
14221         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14222     } else if (cmailMsgLoaded) {
14223         if (currentMove == cmailOldMove &&
14224             commentList[cmailOldMove] != NULL &&
14225             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14226                    "Black offers a draw" : "White offers a draw")) {
14227             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14228             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14229         } else if (currentMove == cmailOldMove + 1) {
14230             char *offer = WhiteOnMove(cmailOldMove) ?
14231               "White offers a draw" : "Black offers a draw";
14232             AppendComment(currentMove, offer, TRUE);
14233             DisplayComment(currentMove - 1, offer);
14234             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14235         } else {
14236             DisplayError(_("You must make your move before offering a draw"), 0);
14237             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14238         }
14239     } else if (first.offeredDraw) {
14240         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14241     } else {
14242         if (first.sendDrawOffers) {
14243             SendToProgram("draw\n", &first);
14244             userOfferedDraw = TRUE;
14245         }
14246     }
14247 }
14248
14249 void
14250 AdjournEvent ()
14251 {
14252     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14253
14254     if (appData.icsActive) {
14255         SendToICS(ics_prefix);
14256         SendToICS("adjourn\n");
14257     } else {
14258         /* Currently GNU Chess doesn't offer or accept Adjourns */
14259     }
14260 }
14261
14262
14263 void
14264 AbortEvent ()
14265 {
14266     /* Offer Abort or accept pending Abort offer from opponent */
14267
14268     if (appData.icsActive) {
14269         SendToICS(ics_prefix);
14270         SendToICS("abort\n");
14271     } else {
14272         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14273     }
14274 }
14275
14276 void
14277 ResignEvent ()
14278 {
14279     /* Resign.  You can do this even if it's not your turn. */
14280
14281     if (appData.icsActive) {
14282         SendToICS(ics_prefix);
14283         SendToICS("resign\n");
14284     } else {
14285         switch (gameMode) {
14286           case MachinePlaysWhite:
14287             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14288             break;
14289           case MachinePlaysBlack:
14290             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14291             break;
14292           case EditGame:
14293             if (cmailMsgLoaded) {
14294                 TruncateGame();
14295                 if (WhiteOnMove(cmailOldMove)) {
14296                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14297                 } else {
14298                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14299                 }
14300                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14301             }
14302             break;
14303           default:
14304             break;
14305         }
14306     }
14307 }
14308
14309
14310 void
14311 StopObservingEvent ()
14312 {
14313     /* Stop observing current games */
14314     SendToICS(ics_prefix);
14315     SendToICS("unobserve\n");
14316 }
14317
14318 void
14319 StopExaminingEvent ()
14320 {
14321     /* Stop observing current game */
14322     SendToICS(ics_prefix);
14323     SendToICS("unexamine\n");
14324 }
14325
14326 void
14327 ForwardInner (int target)
14328 {
14329     int limit; int oldSeekGraphUp = seekGraphUp;
14330
14331     if (appData.debugMode)
14332         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14333                 target, currentMove, forwardMostMove);
14334
14335     if (gameMode == EditPosition)
14336       return;
14337
14338     seekGraphUp = FALSE;
14339     MarkTargetSquares(1);
14340
14341     if (gameMode == PlayFromGameFile && !pausing)
14342       PauseEvent();
14343
14344     if (gameMode == IcsExamining && pausing)
14345       limit = pauseExamForwardMostMove;
14346     else
14347       limit = forwardMostMove;
14348
14349     if (target > limit) target = limit;
14350
14351     if (target > 0 && moveList[target - 1][0]) {
14352         int fromX, fromY, toX, toY;
14353         toX = moveList[target - 1][2] - AAA;
14354         toY = moveList[target - 1][3] - ONE;
14355         if (moveList[target - 1][1] == '@') {
14356             if (appData.highlightLastMove) {
14357                 SetHighlights(-1, -1, toX, toY);
14358             }
14359         } else {
14360             fromX = moveList[target - 1][0] - AAA;
14361             fromY = moveList[target - 1][1] - ONE;
14362             if (target == currentMove + 1) {
14363                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14364             }
14365             if (appData.highlightLastMove) {
14366                 SetHighlights(fromX, fromY, toX, toY);
14367             }
14368         }
14369     }
14370     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14371         gameMode == Training || gameMode == PlayFromGameFile ||
14372         gameMode == AnalyzeFile) {
14373         while (currentMove < target) {
14374             SendMoveToProgram(currentMove++, &first);
14375         }
14376     } else {
14377         currentMove = target;
14378     }
14379
14380     if (gameMode == EditGame || gameMode == EndOfGame) {
14381         whiteTimeRemaining = timeRemaining[0][currentMove];
14382         blackTimeRemaining = timeRemaining[1][currentMove];
14383     }
14384     DisplayBothClocks();
14385     DisplayMove(currentMove - 1);
14386     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14387     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14388     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14389         DisplayComment(currentMove - 1, commentList[currentMove]);
14390     }
14391     ClearMap(); // [HGM] exclude: invalidate map
14392 }
14393
14394
14395 void
14396 ForwardEvent ()
14397 {
14398     if (gameMode == IcsExamining && !pausing) {
14399         SendToICS(ics_prefix);
14400         SendToICS("forward\n");
14401     } else {
14402         ForwardInner(currentMove + 1);
14403     }
14404 }
14405
14406 void
14407 ToEndEvent ()
14408 {
14409     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14410         /* to optimze, we temporarily turn off analysis mode while we feed
14411          * the remaining moves to the engine. Otherwise we get analysis output
14412          * after each move.
14413          */
14414         if (first.analysisSupport) {
14415           SendToProgram("exit\nforce\n", &first);
14416           first.analyzing = FALSE;
14417         }
14418     }
14419
14420     if (gameMode == IcsExamining && !pausing) {
14421         SendToICS(ics_prefix);
14422         SendToICS("forward 999999\n");
14423     } else {
14424         ForwardInner(forwardMostMove);
14425     }
14426
14427     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14428         /* we have fed all the moves, so reactivate analysis mode */
14429         SendToProgram("analyze\n", &first);
14430         first.analyzing = TRUE;
14431         /*first.maybeThinking = TRUE;*/
14432         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14433     }
14434 }
14435
14436 void
14437 BackwardInner (int target)
14438 {
14439     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14440
14441     if (appData.debugMode)
14442         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14443                 target, currentMove, forwardMostMove);
14444
14445     if (gameMode == EditPosition) return;
14446     seekGraphUp = FALSE;
14447     MarkTargetSquares(1);
14448     if (currentMove <= backwardMostMove) {
14449         ClearHighlights();
14450         DrawPosition(full_redraw, boards[currentMove]);
14451         return;
14452     }
14453     if (gameMode == PlayFromGameFile && !pausing)
14454       PauseEvent();
14455
14456     if (moveList[target][0]) {
14457         int fromX, fromY, toX, toY;
14458         toX = moveList[target][2] - AAA;
14459         toY = moveList[target][3] - ONE;
14460         if (moveList[target][1] == '@') {
14461             if (appData.highlightLastMove) {
14462                 SetHighlights(-1, -1, toX, toY);
14463             }
14464         } else {
14465             fromX = moveList[target][0] - AAA;
14466             fromY = moveList[target][1] - ONE;
14467             if (target == currentMove - 1) {
14468                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14469             }
14470             if (appData.highlightLastMove) {
14471                 SetHighlights(fromX, fromY, toX, toY);
14472             }
14473         }
14474     }
14475     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14476         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14477         while (currentMove > target) {
14478             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14479                 // null move cannot be undone. Reload program with move history before it.
14480                 int i;
14481                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14482                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14483                 }
14484                 SendBoard(&first, i); 
14485                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14486                 break;
14487             }
14488             SendToProgram("undo\n", &first);
14489             currentMove--;
14490         }
14491     } else {
14492         currentMove = target;
14493     }
14494
14495     if (gameMode == EditGame || gameMode == EndOfGame) {
14496         whiteTimeRemaining = timeRemaining[0][currentMove];
14497         blackTimeRemaining = timeRemaining[1][currentMove];
14498     }
14499     DisplayBothClocks();
14500     DisplayMove(currentMove - 1);
14501     DrawPosition(full_redraw, boards[currentMove]);
14502     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14503     // [HGM] PV info: routine tests if comment empty
14504     DisplayComment(currentMove - 1, commentList[currentMove]);
14505     ClearMap(); // [HGM] exclude: invalidate map
14506 }
14507
14508 void
14509 BackwardEvent ()
14510 {
14511     if (gameMode == IcsExamining && !pausing) {
14512         SendToICS(ics_prefix);
14513         SendToICS("backward\n");
14514     } else {
14515         BackwardInner(currentMove - 1);
14516     }
14517 }
14518
14519 void
14520 ToStartEvent ()
14521 {
14522     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14523         /* to optimize, we temporarily turn off analysis mode while we undo
14524          * all the moves. Otherwise we get analysis output after each undo.
14525          */
14526         if (first.analysisSupport) {
14527           SendToProgram("exit\nforce\n", &first);
14528           first.analyzing = FALSE;
14529         }
14530     }
14531
14532     if (gameMode == IcsExamining && !pausing) {
14533         SendToICS(ics_prefix);
14534         SendToICS("backward 999999\n");
14535     } else {
14536         BackwardInner(backwardMostMove);
14537     }
14538
14539     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14540         /* we have fed all the moves, so reactivate analysis mode */
14541         SendToProgram("analyze\n", &first);
14542         first.analyzing = TRUE;
14543         /*first.maybeThinking = TRUE;*/
14544         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14545     }
14546 }
14547
14548 void
14549 ToNrEvent (int to)
14550 {
14551   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14552   if (to >= forwardMostMove) to = forwardMostMove;
14553   if (to <= backwardMostMove) to = backwardMostMove;
14554   if (to < currentMove) {
14555     BackwardInner(to);
14556   } else {
14557     ForwardInner(to);
14558   }
14559 }
14560
14561 void
14562 RevertEvent (Boolean annotate)
14563 {
14564     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14565         return;
14566     }
14567     if (gameMode != IcsExamining) {
14568         DisplayError(_("You are not examining a game"), 0);
14569         return;
14570     }
14571     if (pausing) {
14572         DisplayError(_("You can't revert while pausing"), 0);
14573         return;
14574     }
14575     SendToICS(ics_prefix);
14576     SendToICS("revert\n");
14577 }
14578
14579 void
14580 RetractMoveEvent ()
14581 {
14582     switch (gameMode) {
14583       case MachinePlaysWhite:
14584       case MachinePlaysBlack:
14585         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14586             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14587             return;
14588         }
14589         if (forwardMostMove < 2) return;
14590         currentMove = forwardMostMove = forwardMostMove - 2;
14591         whiteTimeRemaining = timeRemaining[0][currentMove];
14592         blackTimeRemaining = timeRemaining[1][currentMove];
14593         DisplayBothClocks();
14594         DisplayMove(currentMove - 1);
14595         ClearHighlights();/*!! could figure this out*/
14596         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14597         SendToProgram("remove\n", &first);
14598         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14599         break;
14600
14601       case BeginningOfGame:
14602       default:
14603         break;
14604
14605       case IcsPlayingWhite:
14606       case IcsPlayingBlack:
14607         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14608             SendToICS(ics_prefix);
14609             SendToICS("takeback 2\n");
14610         } else {
14611             SendToICS(ics_prefix);
14612             SendToICS("takeback 1\n");
14613         }
14614         break;
14615     }
14616 }
14617
14618 void
14619 MoveNowEvent ()
14620 {
14621     ChessProgramState *cps;
14622
14623     switch (gameMode) {
14624       case MachinePlaysWhite:
14625         if (!WhiteOnMove(forwardMostMove)) {
14626             DisplayError(_("It is your turn"), 0);
14627             return;
14628         }
14629         cps = &first;
14630         break;
14631       case MachinePlaysBlack:
14632         if (WhiteOnMove(forwardMostMove)) {
14633             DisplayError(_("It is your turn"), 0);
14634             return;
14635         }
14636         cps = &first;
14637         break;
14638       case TwoMachinesPlay:
14639         if (WhiteOnMove(forwardMostMove) ==
14640             (first.twoMachinesColor[0] == 'w')) {
14641             cps = &first;
14642         } else {
14643             cps = &second;
14644         }
14645         break;
14646       case BeginningOfGame:
14647       default:
14648         return;
14649     }
14650     SendToProgram("?\n", cps);
14651 }
14652
14653 void
14654 TruncateGameEvent ()
14655 {
14656     EditGameEvent();
14657     if (gameMode != EditGame) return;
14658     TruncateGame();
14659 }
14660
14661 void
14662 TruncateGame ()
14663 {
14664     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14665     if (forwardMostMove > currentMove) {
14666         if (gameInfo.resultDetails != NULL) {
14667             free(gameInfo.resultDetails);
14668             gameInfo.resultDetails = NULL;
14669             gameInfo.result = GameUnfinished;
14670         }
14671         forwardMostMove = currentMove;
14672         HistorySet(parseList, backwardMostMove, forwardMostMove,
14673                    currentMove-1);
14674     }
14675 }
14676
14677 void
14678 HintEvent ()
14679 {
14680     if (appData.noChessProgram) return;
14681     switch (gameMode) {
14682       case MachinePlaysWhite:
14683         if (WhiteOnMove(forwardMostMove)) {
14684             DisplayError(_("Wait until your turn"), 0);
14685             return;
14686         }
14687         break;
14688       case BeginningOfGame:
14689       case MachinePlaysBlack:
14690         if (!WhiteOnMove(forwardMostMove)) {
14691             DisplayError(_("Wait until your turn"), 0);
14692             return;
14693         }
14694         break;
14695       default:
14696         DisplayError(_("No hint available"), 0);
14697         return;
14698     }
14699     SendToProgram("hint\n", &first);
14700     hintRequested = TRUE;
14701 }
14702
14703 void
14704 BookEvent ()
14705 {
14706     if (appData.noChessProgram) return;
14707     switch (gameMode) {
14708       case MachinePlaysWhite:
14709         if (WhiteOnMove(forwardMostMove)) {
14710             DisplayError(_("Wait until your turn"), 0);
14711             return;
14712         }
14713         break;
14714       case BeginningOfGame:
14715       case MachinePlaysBlack:
14716         if (!WhiteOnMove(forwardMostMove)) {
14717             DisplayError(_("Wait until your turn"), 0);
14718             return;
14719         }
14720         break;
14721       case EditPosition:
14722         EditPositionDone(TRUE);
14723         break;
14724       case TwoMachinesPlay:
14725         return;
14726       default:
14727         break;
14728     }
14729     SendToProgram("bk\n", &first);
14730     bookOutput[0] = NULLCHAR;
14731     bookRequested = TRUE;
14732 }
14733
14734 void
14735 AboutGameEvent ()
14736 {
14737     char *tags = PGNTags(&gameInfo);
14738     TagsPopUp(tags, CmailMsg());
14739     free(tags);
14740 }
14741
14742 /* end button procedures */
14743
14744 void
14745 PrintPosition (FILE *fp, int move)
14746 {
14747     int i, j;
14748
14749     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14750         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14751             char c = PieceToChar(boards[move][i][j]);
14752             fputc(c == 'x' ? '.' : c, fp);
14753             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14754         }
14755     }
14756     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14757       fprintf(fp, "white to play\n");
14758     else
14759       fprintf(fp, "black to play\n");
14760 }
14761
14762 void
14763 PrintOpponents (FILE *fp)
14764 {
14765     if (gameInfo.white != NULL) {
14766         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14767     } else {
14768         fprintf(fp, "\n");
14769     }
14770 }
14771
14772 /* Find last component of program's own name, using some heuristics */
14773 void
14774 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14775 {
14776     char *p, *q, c;
14777     int local = (strcmp(host, "localhost") == 0);
14778     while (!local && (p = strchr(prog, ';')) != NULL) {
14779         p++;
14780         while (*p == ' ') p++;
14781         prog = p;
14782     }
14783     if (*prog == '"' || *prog == '\'') {
14784         q = strchr(prog + 1, *prog);
14785     } else {
14786         q = strchr(prog, ' ');
14787     }
14788     if (q == NULL) q = prog + strlen(prog);
14789     p = q;
14790     while (p >= prog && *p != '/' && *p != '\\') p--;
14791     p++;
14792     if(p == prog && *p == '"') p++;
14793     c = *q; *q = 0;
14794     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14795     memcpy(buf, p, q - p);
14796     buf[q - p] = NULLCHAR;
14797     if (!local) {
14798         strcat(buf, "@");
14799         strcat(buf, host);
14800     }
14801 }
14802
14803 char *
14804 TimeControlTagValue ()
14805 {
14806     char buf[MSG_SIZ];
14807     if (!appData.clockMode) {
14808       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14809     } else if (movesPerSession > 0) {
14810       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14811     } else if (timeIncrement == 0) {
14812       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14813     } else {
14814       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14815     }
14816     return StrSave(buf);
14817 }
14818
14819 void
14820 SetGameInfo ()
14821 {
14822     /* This routine is used only for certain modes */
14823     VariantClass v = gameInfo.variant;
14824     ChessMove r = GameUnfinished;
14825     char *p = NULL;
14826
14827     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14828         r = gameInfo.result;
14829         p = gameInfo.resultDetails;
14830         gameInfo.resultDetails = NULL;
14831     }
14832     ClearGameInfo(&gameInfo);
14833     gameInfo.variant = v;
14834
14835     switch (gameMode) {
14836       case MachinePlaysWhite:
14837         gameInfo.event = StrSave( appData.pgnEventHeader );
14838         gameInfo.site = StrSave(HostName());
14839         gameInfo.date = PGNDate();
14840         gameInfo.round = StrSave("-");
14841         gameInfo.white = StrSave(first.tidy);
14842         gameInfo.black = StrSave(UserName());
14843         gameInfo.timeControl = TimeControlTagValue();
14844         break;
14845
14846       case MachinePlaysBlack:
14847         gameInfo.event = StrSave( appData.pgnEventHeader );
14848         gameInfo.site = StrSave(HostName());
14849         gameInfo.date = PGNDate();
14850         gameInfo.round = StrSave("-");
14851         gameInfo.white = StrSave(UserName());
14852         gameInfo.black = StrSave(first.tidy);
14853         gameInfo.timeControl = TimeControlTagValue();
14854         break;
14855
14856       case TwoMachinesPlay:
14857         gameInfo.event = StrSave( appData.pgnEventHeader );
14858         gameInfo.site = StrSave(HostName());
14859         gameInfo.date = PGNDate();
14860         if (roundNr > 0) {
14861             char buf[MSG_SIZ];
14862             snprintf(buf, MSG_SIZ, "%d", roundNr);
14863             gameInfo.round = StrSave(buf);
14864         } else {
14865             gameInfo.round = StrSave("-");
14866         }
14867         if (first.twoMachinesColor[0] == 'w') {
14868             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14869             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14870         } else {
14871             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14872             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14873         }
14874         gameInfo.timeControl = TimeControlTagValue();
14875         break;
14876
14877       case EditGame:
14878         gameInfo.event = StrSave("Edited game");
14879         gameInfo.site = StrSave(HostName());
14880         gameInfo.date = PGNDate();
14881         gameInfo.round = StrSave("-");
14882         gameInfo.white = StrSave("-");
14883         gameInfo.black = StrSave("-");
14884         gameInfo.result = r;
14885         gameInfo.resultDetails = p;
14886         break;
14887
14888       case EditPosition:
14889         gameInfo.event = StrSave("Edited position");
14890         gameInfo.site = StrSave(HostName());
14891         gameInfo.date = PGNDate();
14892         gameInfo.round = StrSave("-");
14893         gameInfo.white = StrSave("-");
14894         gameInfo.black = StrSave("-");
14895         break;
14896
14897       case IcsPlayingWhite:
14898       case IcsPlayingBlack:
14899       case IcsObserving:
14900       case IcsExamining:
14901         break;
14902
14903       case PlayFromGameFile:
14904         gameInfo.event = StrSave("Game from non-PGN file");
14905         gameInfo.site = StrSave(HostName());
14906         gameInfo.date = PGNDate();
14907         gameInfo.round = StrSave("-");
14908         gameInfo.white = StrSave("?");
14909         gameInfo.black = StrSave("?");
14910         break;
14911
14912       default:
14913         break;
14914     }
14915 }
14916
14917 void
14918 ReplaceComment (int index, char *text)
14919 {
14920     int len;
14921     char *p;
14922     float score;
14923
14924     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14925        pvInfoList[index-1].depth == len &&
14926        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14927        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14928     while (*text == '\n') text++;
14929     len = strlen(text);
14930     while (len > 0 && text[len - 1] == '\n') len--;
14931
14932     if (commentList[index] != NULL)
14933       free(commentList[index]);
14934
14935     if (len == 0) {
14936         commentList[index] = NULL;
14937         return;
14938     }
14939   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14940       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14941       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14942     commentList[index] = (char *) malloc(len + 2);
14943     strncpy(commentList[index], text, len);
14944     commentList[index][len] = '\n';
14945     commentList[index][len + 1] = NULLCHAR;
14946   } else {
14947     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14948     char *p;
14949     commentList[index] = (char *) malloc(len + 7);
14950     safeStrCpy(commentList[index], "{\n", 3);
14951     safeStrCpy(commentList[index]+2, text, len+1);
14952     commentList[index][len+2] = NULLCHAR;
14953     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14954     strcat(commentList[index], "\n}\n");
14955   }
14956 }
14957
14958 void
14959 CrushCRs (char *text)
14960 {
14961   char *p = text;
14962   char *q = text;
14963   char ch;
14964
14965   do {
14966     ch = *p++;
14967     if (ch == '\r') continue;
14968     *q++ = ch;
14969   } while (ch != '\0');
14970 }
14971
14972 void
14973 AppendComment (int index, char *text, Boolean addBraces)
14974 /* addBraces  tells if we should add {} */
14975 {
14976     int oldlen, len;
14977     char *old;
14978
14979 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14980     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14981
14982     CrushCRs(text);
14983     while (*text == '\n') text++;
14984     len = strlen(text);
14985     while (len > 0 && text[len - 1] == '\n') len--;
14986     text[len] = NULLCHAR;
14987
14988     if (len == 0) return;
14989
14990     if (commentList[index] != NULL) {
14991       Boolean addClosingBrace = addBraces;
14992         old = commentList[index];
14993         oldlen = strlen(old);
14994         while(commentList[index][oldlen-1] ==  '\n')
14995           commentList[index][--oldlen] = NULLCHAR;
14996         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14997         safeStrCpy(commentList[index], old, oldlen + len + 6);
14998         free(old);
14999         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15000         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15001           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15002           while (*text == '\n') { text++; len--; }
15003           commentList[index][--oldlen] = NULLCHAR;
15004       }
15005         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15006         else          strcat(commentList[index], "\n");
15007         strcat(commentList[index], text);
15008         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15009         else          strcat(commentList[index], "\n");
15010     } else {
15011         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15012         if(addBraces)
15013           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15014         else commentList[index][0] = NULLCHAR;
15015         strcat(commentList[index], text);
15016         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15017         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15018     }
15019 }
15020
15021 static char *
15022 FindStr (char * text, char * sub_text)
15023 {
15024     char * result = strstr( text, sub_text );
15025
15026     if( result != NULL ) {
15027         result += strlen( sub_text );
15028     }
15029
15030     return result;
15031 }
15032
15033 /* [AS] Try to extract PV info from PGN comment */
15034 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15035 char *
15036 GetInfoFromComment (int index, char * text)
15037 {
15038     char * sep = text, *p;
15039
15040     if( text != NULL && index > 0 ) {
15041         int score = 0;
15042         int depth = 0;
15043         int time = -1, sec = 0, deci;
15044         char * s_eval = FindStr( text, "[%eval " );
15045         char * s_emt = FindStr( text, "[%emt " );
15046
15047         if( s_eval != NULL || s_emt != NULL ) {
15048             /* New style */
15049             char delim;
15050
15051             if( s_eval != NULL ) {
15052                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15053                     return text;
15054                 }
15055
15056                 if( delim != ']' ) {
15057                     return text;
15058                 }
15059             }
15060
15061             if( s_emt != NULL ) {
15062             }
15063                 return text;
15064         }
15065         else {
15066             /* We expect something like: [+|-]nnn.nn/dd */
15067             int score_lo = 0;
15068
15069             if(*text != '{') return text; // [HGM] braces: must be normal comment
15070
15071             sep = strchr( text, '/' );
15072             if( sep == NULL || sep < (text+4) ) {
15073                 return text;
15074             }
15075
15076             p = text;
15077             if(p[1] == '(') { // comment starts with PV
15078                p = strchr(p, ')'); // locate end of PV
15079                if(p == NULL || sep < p+5) return text;
15080                // at this point we have something like "{(.*) +0.23/6 ..."
15081                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15082                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15083                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15084             }
15085             time = -1; sec = -1; deci = -1;
15086             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15087                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15088                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15089                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15090                 return text;
15091             }
15092
15093             if( score_lo < 0 || score_lo >= 100 ) {
15094                 return text;
15095             }
15096
15097             if(sec >= 0) time = 600*time + 10*sec; else
15098             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15099
15100             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15101
15102             /* [HGM] PV time: now locate end of PV info */
15103             while( *++sep >= '0' && *sep <= '9'); // strip depth
15104             if(time >= 0)
15105             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15106             if(sec >= 0)
15107             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15108             if(deci >= 0)
15109             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15110             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15111         }
15112
15113         if( depth <= 0 ) {
15114             return text;
15115         }
15116
15117         if( time < 0 ) {
15118             time = -1;
15119         }
15120
15121         pvInfoList[index-1].depth = depth;
15122         pvInfoList[index-1].score = score;
15123         pvInfoList[index-1].time  = 10*time; // centi-sec
15124         if(*sep == '}') *sep = 0; else *--sep = '{';
15125         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15126     }
15127     return sep;
15128 }
15129
15130 void
15131 SendToProgram (char *message, ChessProgramState *cps)
15132 {
15133     int count, outCount, error;
15134     char buf[MSG_SIZ];
15135
15136     if (cps->pr == NoProc) return;
15137     Attention(cps);
15138
15139     if (appData.debugMode) {
15140         TimeMark now;
15141         GetTimeMark(&now);
15142         fprintf(debugFP, "%ld >%-6s: %s",
15143                 SubtractTimeMarks(&now, &programStartTime),
15144                 cps->which, message);
15145         if(serverFP)
15146             fprintf(serverFP, "%ld >%-6s: %s",
15147                 SubtractTimeMarks(&now, &programStartTime),
15148                 cps->which, message), fflush(serverFP);
15149     }
15150
15151     count = strlen(message);
15152     outCount = OutputToProcess(cps->pr, message, count, &error);
15153     if (outCount < count && !exiting
15154                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15155       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15156       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15157         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15158             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15159                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15160                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15161                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15162             } else {
15163                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15164                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15165                 gameInfo.result = res;
15166             }
15167             gameInfo.resultDetails = StrSave(buf);
15168         }
15169         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15170         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15171     }
15172 }
15173
15174 void
15175 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15176 {
15177     char *end_str;
15178     char buf[MSG_SIZ];
15179     ChessProgramState *cps = (ChessProgramState *)closure;
15180
15181     if (isr != cps->isr) return; /* Killed intentionally */
15182     if (count <= 0) {
15183         if (count == 0) {
15184             RemoveInputSource(cps->isr);
15185             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15186             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15187                     _(cps->which), cps->program);
15188         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15189                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15190                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15191                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15192                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15193                 } else {
15194                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15195                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15196                     gameInfo.result = res;
15197                 }
15198                 gameInfo.resultDetails = StrSave(buf);
15199             }
15200             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15201             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15202         } else {
15203             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15204                     _(cps->which), cps->program);
15205             RemoveInputSource(cps->isr);
15206
15207             /* [AS] Program is misbehaving badly... kill it */
15208             if( count == -2 ) {
15209                 DestroyChildProcess( cps->pr, 9 );
15210                 cps->pr = NoProc;
15211             }
15212
15213             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15214         }
15215         return;
15216     }
15217
15218     if ((end_str = strchr(message, '\r')) != NULL)
15219       *end_str = NULLCHAR;
15220     if ((end_str = strchr(message, '\n')) != NULL)
15221       *end_str = NULLCHAR;
15222
15223     if (appData.debugMode) {
15224         TimeMark now; int print = 1;
15225         char *quote = ""; char c; int i;
15226
15227         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15228                 char start = message[0];
15229                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15230                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15231                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15232                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15233                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15234                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15235                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15236                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15237                    sscanf(message, "hint: %c", &c)!=1 && 
15238                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15239                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15240                     print = (appData.engineComments >= 2);
15241                 }
15242                 message[0] = start; // restore original message
15243         }
15244         if(print) {
15245                 GetTimeMark(&now);
15246                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15247                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15248                         quote,
15249                         message);
15250                 if(serverFP)
15251                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15252                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15253                         quote,
15254                         message), fflush(serverFP);
15255         }
15256     }
15257
15258     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15259     if (appData.icsEngineAnalyze) {
15260         if (strstr(message, "whisper") != NULL ||
15261              strstr(message, "kibitz") != NULL ||
15262             strstr(message, "tellics") != NULL) return;
15263     }
15264
15265     HandleMachineMove(message, cps);
15266 }
15267
15268
15269 void
15270 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15271 {
15272     char buf[MSG_SIZ];
15273     int seconds;
15274
15275     if( timeControl_2 > 0 ) {
15276         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15277             tc = timeControl_2;
15278         }
15279     }
15280     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15281     inc /= cps->timeOdds;
15282     st  /= cps->timeOdds;
15283
15284     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15285
15286     if (st > 0) {
15287       /* Set exact time per move, normally using st command */
15288       if (cps->stKludge) {
15289         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15290         seconds = st % 60;
15291         if (seconds == 0) {
15292           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15293         } else {
15294           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15295         }
15296       } else {
15297         snprintf(buf, MSG_SIZ, "st %d\n", st);
15298       }
15299     } else {
15300       /* Set conventional or incremental time control, using level command */
15301       if (seconds == 0) {
15302         /* Note old gnuchess bug -- minutes:seconds used to not work.
15303            Fixed in later versions, but still avoid :seconds
15304            when seconds is 0. */
15305         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15306       } else {
15307         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15308                  seconds, inc/1000.);
15309       }
15310     }
15311     SendToProgram(buf, cps);
15312
15313     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15314     /* Orthogonally, limit search to given depth */
15315     if (sd > 0) {
15316       if (cps->sdKludge) {
15317         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15318       } else {
15319         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15320       }
15321       SendToProgram(buf, cps);
15322     }
15323
15324     if(cps->nps >= 0) { /* [HGM] nps */
15325         if(cps->supportsNPS == FALSE)
15326           cps->nps = -1; // don't use if engine explicitly says not supported!
15327         else {
15328           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15329           SendToProgram(buf, cps);
15330         }
15331     }
15332 }
15333
15334 ChessProgramState *
15335 WhitePlayer ()
15336 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15337 {
15338     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15339        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15340         return &second;
15341     return &first;
15342 }
15343
15344 void
15345 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15346 {
15347     char message[MSG_SIZ];
15348     long time, otime;
15349
15350     /* Note: this routine must be called when the clocks are stopped
15351        or when they have *just* been set or switched; otherwise
15352        it will be off by the time since the current tick started.
15353     */
15354     if (machineWhite) {
15355         time = whiteTimeRemaining / 10;
15356         otime = blackTimeRemaining / 10;
15357     } else {
15358         time = blackTimeRemaining / 10;
15359         otime = whiteTimeRemaining / 10;
15360     }
15361     /* [HGM] translate opponent's time by time-odds factor */
15362     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15363
15364     if (time <= 0) time = 1;
15365     if (otime <= 0) otime = 1;
15366
15367     snprintf(message, MSG_SIZ, "time %ld\n", time);
15368     SendToProgram(message, cps);
15369
15370     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15371     SendToProgram(message, cps);
15372 }
15373
15374 int
15375 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15376 {
15377   char buf[MSG_SIZ];
15378   int len = strlen(name);
15379   int val;
15380
15381   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15382     (*p) += len + 1;
15383     sscanf(*p, "%d", &val);
15384     *loc = (val != 0);
15385     while (**p && **p != ' ')
15386       (*p)++;
15387     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15388     SendToProgram(buf, cps);
15389     return TRUE;
15390   }
15391   return FALSE;
15392 }
15393
15394 int
15395 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15396 {
15397   char buf[MSG_SIZ];
15398   int len = strlen(name);
15399   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15400     (*p) += len + 1;
15401     sscanf(*p, "%d", loc);
15402     while (**p && **p != ' ') (*p)++;
15403     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15404     SendToProgram(buf, cps);
15405     return TRUE;
15406   }
15407   return FALSE;
15408 }
15409
15410 int
15411 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15412 {
15413   char buf[MSG_SIZ];
15414   int len = strlen(name);
15415   if (strncmp((*p), name, len) == 0
15416       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15417     (*p) += len + 2;
15418     sscanf(*p, "%[^\"]", loc);
15419     while (**p && **p != '\"') (*p)++;
15420     if (**p == '\"') (*p)++;
15421     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15422     SendToProgram(buf, cps);
15423     return TRUE;
15424   }
15425   return FALSE;
15426 }
15427
15428 int
15429 ParseOption (Option *opt, ChessProgramState *cps)
15430 // [HGM] options: process the string that defines an engine option, and determine
15431 // name, type, default value, and allowed value range
15432 {
15433         char *p, *q, buf[MSG_SIZ];
15434         int n, min = (-1)<<31, max = 1<<31, def;
15435
15436         if(p = strstr(opt->name, " -spin ")) {
15437             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15438             if(max < min) max = min; // enforce consistency
15439             if(def < min) def = min;
15440             if(def > max) def = max;
15441             opt->value = def;
15442             opt->min = min;
15443             opt->max = max;
15444             opt->type = Spin;
15445         } else if((p = strstr(opt->name, " -slider "))) {
15446             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15447             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15448             if(max < min) max = min; // enforce consistency
15449             if(def < min) def = min;
15450             if(def > max) def = max;
15451             opt->value = def;
15452             opt->min = min;
15453             opt->max = max;
15454             opt->type = Spin; // Slider;
15455         } else if((p = strstr(opt->name, " -string "))) {
15456             opt->textValue = p+9;
15457             opt->type = TextBox;
15458         } else if((p = strstr(opt->name, " -file "))) {
15459             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15460             opt->textValue = p+7;
15461             opt->type = FileName; // FileName;
15462         } else if((p = strstr(opt->name, " -path "))) {
15463             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15464             opt->textValue = p+7;
15465             opt->type = PathName; // PathName;
15466         } else if(p = strstr(opt->name, " -check ")) {
15467             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15468             opt->value = (def != 0);
15469             opt->type = CheckBox;
15470         } else if(p = strstr(opt->name, " -combo ")) {
15471             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15472             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15473             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15474             opt->value = n = 0;
15475             while(q = StrStr(q, " /// ")) {
15476                 n++; *q = 0;    // count choices, and null-terminate each of them
15477                 q += 5;
15478                 if(*q == '*') { // remember default, which is marked with * prefix
15479                     q++;
15480                     opt->value = n;
15481                 }
15482                 cps->comboList[cps->comboCnt++] = q;
15483             }
15484             cps->comboList[cps->comboCnt++] = NULL;
15485             opt->max = n + 1;
15486             opt->type = ComboBox;
15487         } else if(p = strstr(opt->name, " -button")) {
15488             opt->type = Button;
15489         } else if(p = strstr(opt->name, " -save")) {
15490             opt->type = SaveButton;
15491         } else return FALSE;
15492         *p = 0; // terminate option name
15493         // now look if the command-line options define a setting for this engine option.
15494         if(cps->optionSettings && cps->optionSettings[0])
15495             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15496         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15497           snprintf(buf, MSG_SIZ, "option %s", p);
15498                 if(p = strstr(buf, ",")) *p = 0;
15499                 if(q = strchr(buf, '=')) switch(opt->type) {
15500                     case ComboBox:
15501                         for(n=0; n<opt->max; n++)
15502                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15503                         break;
15504                     case TextBox:
15505                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15506                         break;
15507                     case Spin:
15508                     case CheckBox:
15509                         opt->value = atoi(q+1);
15510                     default:
15511                         break;
15512                 }
15513                 strcat(buf, "\n");
15514                 SendToProgram(buf, cps);
15515         }
15516         return TRUE;
15517 }
15518
15519 void
15520 FeatureDone (ChessProgramState *cps, int val)
15521 {
15522   DelayedEventCallback cb = GetDelayedEvent();
15523   if ((cb == InitBackEnd3 && cps == &first) ||
15524       (cb == SettingsMenuIfReady && cps == &second) ||
15525       (cb == LoadEngine) ||
15526       (cb == TwoMachinesEventIfReady)) {
15527     CancelDelayedEvent();
15528     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15529   }
15530   cps->initDone = val;
15531 }
15532
15533 /* Parse feature command from engine */
15534 void
15535 ParseFeatures (char *args, ChessProgramState *cps)
15536 {
15537   char *p = args;
15538   char *q;
15539   int val;
15540   char buf[MSG_SIZ];
15541
15542   for (;;) {
15543     while (*p == ' ') p++;
15544     if (*p == NULLCHAR) return;
15545
15546     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15547     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15548     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15549     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15550     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15551     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15552     if (BoolFeature(&p, "reuse", &val, cps)) {
15553       /* Engine can disable reuse, but can't enable it if user said no */
15554       if (!val) cps->reuse = FALSE;
15555       continue;
15556     }
15557     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15558     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15559       if (gameMode == TwoMachinesPlay) {
15560         DisplayTwoMachinesTitle();
15561       } else {
15562         DisplayTitle("");
15563       }
15564       continue;
15565     }
15566     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15567     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15568     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15569     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15570     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15571     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15572     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15573     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15574     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15575     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15576     if (IntFeature(&p, "done", &val, cps)) {
15577       FeatureDone(cps, val);
15578       continue;
15579     }
15580     /* Added by Tord: */
15581     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15582     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15583     /* End of additions by Tord */
15584
15585     /* [HGM] added features: */
15586     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15587     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15588     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15589     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15590     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15591     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15592     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15593         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15594           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15595             SendToProgram(buf, cps);
15596             continue;
15597         }
15598         if(cps->nrOptions >= MAX_OPTIONS) {
15599             cps->nrOptions--;
15600             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15601             DisplayError(buf, 0);
15602         }
15603         continue;
15604     }
15605     /* End of additions by HGM */
15606
15607     /* unknown feature: complain and skip */
15608     q = p;
15609     while (*q && *q != '=') q++;
15610     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15611     SendToProgram(buf, cps);
15612     p = q;
15613     if (*p == '=') {
15614       p++;
15615       if (*p == '\"') {
15616         p++;
15617         while (*p && *p != '\"') p++;
15618         if (*p == '\"') p++;
15619       } else {
15620         while (*p && *p != ' ') p++;
15621       }
15622     }
15623   }
15624
15625 }
15626
15627 void
15628 PeriodicUpdatesEvent (int newState)
15629 {
15630     if (newState == appData.periodicUpdates)
15631       return;
15632
15633     appData.periodicUpdates=newState;
15634
15635     /* Display type changes, so update it now */
15636 //    DisplayAnalysis();
15637
15638     /* Get the ball rolling again... */
15639     if (newState) {
15640         AnalysisPeriodicEvent(1);
15641         StartAnalysisClock();
15642     }
15643 }
15644
15645 void
15646 PonderNextMoveEvent (int newState)
15647 {
15648     if (newState == appData.ponderNextMove) return;
15649     if (gameMode == EditPosition) EditPositionDone(TRUE);
15650     if (newState) {
15651         SendToProgram("hard\n", &first);
15652         if (gameMode == TwoMachinesPlay) {
15653             SendToProgram("hard\n", &second);
15654         }
15655     } else {
15656         SendToProgram("easy\n", &first);
15657         thinkOutput[0] = NULLCHAR;
15658         if (gameMode == TwoMachinesPlay) {
15659             SendToProgram("easy\n", &second);
15660         }
15661     }
15662     appData.ponderNextMove = newState;
15663 }
15664
15665 void
15666 NewSettingEvent (int option, int *feature, char *command, int value)
15667 {
15668     char buf[MSG_SIZ];
15669
15670     if (gameMode == EditPosition) EditPositionDone(TRUE);
15671     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15672     if(feature == NULL || *feature) SendToProgram(buf, &first);
15673     if (gameMode == TwoMachinesPlay) {
15674         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15675     }
15676 }
15677
15678 void
15679 ShowThinkingEvent ()
15680 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15681 {
15682     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15683     int newState = appData.showThinking
15684         // [HGM] thinking: other features now need thinking output as well
15685         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15686
15687     if (oldState == newState) return;
15688     oldState = newState;
15689     if (gameMode == EditPosition) EditPositionDone(TRUE);
15690     if (oldState) {
15691         SendToProgram("post\n", &first);
15692         if (gameMode == TwoMachinesPlay) {
15693             SendToProgram("post\n", &second);
15694         }
15695     } else {
15696         SendToProgram("nopost\n", &first);
15697         thinkOutput[0] = NULLCHAR;
15698         if (gameMode == TwoMachinesPlay) {
15699             SendToProgram("nopost\n", &second);
15700         }
15701     }
15702 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15703 }
15704
15705 void
15706 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15707 {
15708   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15709   if (pr == NoProc) return;
15710   AskQuestion(title, question, replyPrefix, pr);
15711 }
15712
15713 void
15714 TypeInEvent (char firstChar)
15715 {
15716     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15717         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15718         gameMode == AnalyzeMode || gameMode == EditGame || 
15719         gameMode == EditPosition || gameMode == IcsExamining ||
15720         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15721         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15722                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15723                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15724         gameMode == Training) PopUpMoveDialog(firstChar);
15725 }
15726
15727 void
15728 TypeInDoneEvent (char *move)
15729 {
15730         Board board;
15731         int n, fromX, fromY, toX, toY;
15732         char promoChar;
15733         ChessMove moveType;
15734
15735         // [HGM] FENedit
15736         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15737                 EditPositionPasteFEN(move);
15738                 return;
15739         }
15740         // [HGM] movenum: allow move number to be typed in any mode
15741         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15742           ToNrEvent(2*n-1);
15743           return;
15744         }
15745         // undocumented kludge: allow command-line option to be typed in!
15746         // (potentially fatal, and does not implement the effect of the option.)
15747         // should only be used for options that are values on which future decisions will be made,
15748         // and definitely not on options that would be used during initialization.
15749         if(strstr(move, "!!! -") == move) {
15750             ParseArgsFromString(move+4);
15751             return;
15752         }
15753
15754       if (gameMode != EditGame && currentMove != forwardMostMove && 
15755         gameMode != Training) {
15756         DisplayMoveError(_("Displayed move is not current"));
15757       } else {
15758         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15759           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15760         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15761         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15762           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15763           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15764         } else {
15765           DisplayMoveError(_("Could not parse move"));
15766         }
15767       }
15768 }
15769
15770 void
15771 DisplayMove (int moveNumber)
15772 {
15773     char message[MSG_SIZ];
15774     char res[MSG_SIZ];
15775     char cpThinkOutput[MSG_SIZ];
15776
15777     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15778
15779     if (moveNumber == forwardMostMove - 1 ||
15780         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15781
15782         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15783
15784         if (strchr(cpThinkOutput, '\n')) {
15785             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15786         }
15787     } else {
15788         *cpThinkOutput = NULLCHAR;
15789     }
15790
15791     /* [AS] Hide thinking from human user */
15792     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15793         *cpThinkOutput = NULLCHAR;
15794         if( thinkOutput[0] != NULLCHAR ) {
15795             int i;
15796
15797             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15798                 cpThinkOutput[i] = '.';
15799             }
15800             cpThinkOutput[i] = NULLCHAR;
15801             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15802         }
15803     }
15804
15805     if (moveNumber == forwardMostMove - 1 &&
15806         gameInfo.resultDetails != NULL) {
15807         if (gameInfo.resultDetails[0] == NULLCHAR) {
15808           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15809         } else {
15810           snprintf(res, MSG_SIZ, " {%s} %s",
15811                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15812         }
15813     } else {
15814         res[0] = NULLCHAR;
15815     }
15816
15817     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15818         DisplayMessage(res, cpThinkOutput);
15819     } else {
15820       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15821                 WhiteOnMove(moveNumber) ? " " : ".. ",
15822                 parseList[moveNumber], res);
15823         DisplayMessage(message, cpThinkOutput);
15824     }
15825 }
15826
15827 void
15828 DisplayComment (int moveNumber, char *text)
15829 {
15830     char title[MSG_SIZ];
15831
15832     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15833       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15834     } else {
15835       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15836               WhiteOnMove(moveNumber) ? " " : ".. ",
15837               parseList[moveNumber]);
15838     }
15839     if (text != NULL && (appData.autoDisplayComment || commentUp))
15840         CommentPopUp(title, text);
15841 }
15842
15843 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15844  * might be busy thinking or pondering.  It can be omitted if your
15845  * gnuchess is configured to stop thinking immediately on any user
15846  * input.  However, that gnuchess feature depends on the FIONREAD
15847  * ioctl, which does not work properly on some flavors of Unix.
15848  */
15849 void
15850 Attention (ChessProgramState *cps)
15851 {
15852 #if ATTENTION
15853     if (!cps->useSigint) return;
15854     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15855     switch (gameMode) {
15856       case MachinePlaysWhite:
15857       case MachinePlaysBlack:
15858       case TwoMachinesPlay:
15859       case IcsPlayingWhite:
15860       case IcsPlayingBlack:
15861       case AnalyzeMode:
15862       case AnalyzeFile:
15863         /* Skip if we know it isn't thinking */
15864         if (!cps->maybeThinking) return;
15865         if (appData.debugMode)
15866           fprintf(debugFP, "Interrupting %s\n", cps->which);
15867         InterruptChildProcess(cps->pr);
15868         cps->maybeThinking = FALSE;
15869         break;
15870       default:
15871         break;
15872     }
15873 #endif /*ATTENTION*/
15874 }
15875
15876 int
15877 CheckFlags ()
15878 {
15879     if (whiteTimeRemaining <= 0) {
15880         if (!whiteFlag) {
15881             whiteFlag = TRUE;
15882             if (appData.icsActive) {
15883                 if (appData.autoCallFlag &&
15884                     gameMode == IcsPlayingBlack && !blackFlag) {
15885                   SendToICS(ics_prefix);
15886                   SendToICS("flag\n");
15887                 }
15888             } else {
15889                 if (blackFlag) {
15890                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15891                 } else {
15892                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15893                     if (appData.autoCallFlag) {
15894                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15895                         return TRUE;
15896                     }
15897                 }
15898             }
15899         }
15900     }
15901     if (blackTimeRemaining <= 0) {
15902         if (!blackFlag) {
15903             blackFlag = TRUE;
15904             if (appData.icsActive) {
15905                 if (appData.autoCallFlag &&
15906                     gameMode == IcsPlayingWhite && !whiteFlag) {
15907                   SendToICS(ics_prefix);
15908                   SendToICS("flag\n");
15909                 }
15910             } else {
15911                 if (whiteFlag) {
15912                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15913                 } else {
15914                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15915                     if (appData.autoCallFlag) {
15916                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15917                         return TRUE;
15918                     }
15919                 }
15920             }
15921         }
15922     }
15923     return FALSE;
15924 }
15925
15926 void
15927 CheckTimeControl ()
15928 {
15929     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15930         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15931
15932     /*
15933      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15934      */
15935     if ( !WhiteOnMove(forwardMostMove) ) {
15936         /* White made time control */
15937         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15938         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15939         /* [HGM] time odds: correct new time quota for time odds! */
15940                                             / WhitePlayer()->timeOdds;
15941         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15942     } else {
15943         lastBlack -= blackTimeRemaining;
15944         /* Black made time control */
15945         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15946                                             / WhitePlayer()->other->timeOdds;
15947         lastWhite = whiteTimeRemaining;
15948     }
15949 }
15950
15951 void
15952 DisplayBothClocks ()
15953 {
15954     int wom = gameMode == EditPosition ?
15955       !blackPlaysFirst : WhiteOnMove(currentMove);
15956     DisplayWhiteClock(whiteTimeRemaining, wom);
15957     DisplayBlackClock(blackTimeRemaining, !wom);
15958 }
15959
15960
15961 /* Timekeeping seems to be a portability nightmare.  I think everyone
15962    has ftime(), but I'm really not sure, so I'm including some ifdefs
15963    to use other calls if you don't.  Clocks will be less accurate if
15964    you have neither ftime nor gettimeofday.
15965 */
15966
15967 /* VS 2008 requires the #include outside of the function */
15968 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15969 #include <sys/timeb.h>
15970 #endif
15971
15972 /* Get the current time as a TimeMark */
15973 void
15974 GetTimeMark (TimeMark *tm)
15975 {
15976 #if HAVE_GETTIMEOFDAY
15977
15978     struct timeval timeVal;
15979     struct timezone timeZone;
15980
15981     gettimeofday(&timeVal, &timeZone);
15982     tm->sec = (long) timeVal.tv_sec;
15983     tm->ms = (int) (timeVal.tv_usec / 1000L);
15984
15985 #else /*!HAVE_GETTIMEOFDAY*/
15986 #if HAVE_FTIME
15987
15988 // include <sys/timeb.h> / moved to just above start of function
15989     struct timeb timeB;
15990
15991     ftime(&timeB);
15992     tm->sec = (long) timeB.time;
15993     tm->ms = (int) timeB.millitm;
15994
15995 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15996     tm->sec = (long) time(NULL);
15997     tm->ms = 0;
15998 #endif
15999 #endif
16000 }
16001
16002 /* Return the difference in milliseconds between two
16003    time marks.  We assume the difference will fit in a long!
16004 */
16005 long
16006 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16007 {
16008     return 1000L*(tm2->sec - tm1->sec) +
16009            (long) (tm2->ms - tm1->ms);
16010 }
16011
16012
16013 /*
16014  * Code to manage the game clocks.
16015  *
16016  * In tournament play, black starts the clock and then white makes a move.
16017  * We give the human user a slight advantage if he is playing white---the
16018  * clocks don't run until he makes his first move, so it takes zero time.
16019  * Also, we don't account for network lag, so we could get out of sync
16020  * with GNU Chess's clock -- but then, referees are always right.
16021  */
16022
16023 static TimeMark tickStartTM;
16024 static long intendedTickLength;
16025
16026 long
16027 NextTickLength (long timeRemaining)
16028 {
16029     long nominalTickLength, nextTickLength;
16030
16031     if (timeRemaining > 0L && timeRemaining <= 10000L)
16032       nominalTickLength = 100L;
16033     else
16034       nominalTickLength = 1000L;
16035     nextTickLength = timeRemaining % nominalTickLength;
16036     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16037
16038     return nextTickLength;
16039 }
16040
16041 /* Adjust clock one minute up or down */
16042 void
16043 AdjustClock (Boolean which, int dir)
16044 {
16045     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16046     if(which) blackTimeRemaining += 60000*dir;
16047     else      whiteTimeRemaining += 60000*dir;
16048     DisplayBothClocks();
16049     adjustedClock = TRUE;
16050 }
16051
16052 /* Stop clocks and reset to a fresh time control */
16053 void
16054 ResetClocks ()
16055 {
16056     (void) StopClockTimer();
16057     if (appData.icsActive) {
16058         whiteTimeRemaining = blackTimeRemaining = 0;
16059     } else if (searchTime) {
16060         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16061         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16062     } else { /* [HGM] correct new time quote for time odds */
16063         whiteTC = blackTC = fullTimeControlString;
16064         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16065         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16066     }
16067     if (whiteFlag || blackFlag) {
16068         DisplayTitle("");
16069         whiteFlag = blackFlag = FALSE;
16070     }
16071     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16072     DisplayBothClocks();
16073     adjustedClock = FALSE;
16074 }
16075
16076 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16077
16078 /* Decrement running clock by amount of time that has passed */
16079 void
16080 DecrementClocks ()
16081 {
16082     long timeRemaining;
16083     long lastTickLength, fudge;
16084     TimeMark now;
16085
16086     if (!appData.clockMode) return;
16087     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16088
16089     GetTimeMark(&now);
16090
16091     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16092
16093     /* Fudge if we woke up a little too soon */
16094     fudge = intendedTickLength - lastTickLength;
16095     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16096
16097     if (WhiteOnMove(forwardMostMove)) {
16098         if(whiteNPS >= 0) lastTickLength = 0;
16099         timeRemaining = whiteTimeRemaining -= lastTickLength;
16100         if(timeRemaining < 0 && !appData.icsActive) {
16101             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16102             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16103                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16104                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16105             }
16106         }
16107         DisplayWhiteClock(whiteTimeRemaining - fudge,
16108                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16109     } else {
16110         if(blackNPS >= 0) lastTickLength = 0;
16111         timeRemaining = blackTimeRemaining -= lastTickLength;
16112         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16113             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16114             if(suddenDeath) {
16115                 blackStartMove = forwardMostMove;
16116                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16117             }
16118         }
16119         DisplayBlackClock(blackTimeRemaining - fudge,
16120                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16121     }
16122     if (CheckFlags()) return;
16123
16124     tickStartTM = now;
16125     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16126     StartClockTimer(intendedTickLength);
16127
16128     /* if the time remaining has fallen below the alarm threshold, sound the
16129      * alarm. if the alarm has sounded and (due to a takeback or time control
16130      * with increment) the time remaining has increased to a level above the
16131      * threshold, reset the alarm so it can sound again.
16132      */
16133
16134     if (appData.icsActive && appData.icsAlarm) {
16135
16136         /* make sure we are dealing with the user's clock */
16137         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16138                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16139            )) return;
16140
16141         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16142             alarmSounded = FALSE;
16143         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16144             PlayAlarmSound();
16145             alarmSounded = TRUE;
16146         }
16147     }
16148 }
16149
16150
16151 /* A player has just moved, so stop the previously running
16152    clock and (if in clock mode) start the other one.
16153    We redisplay both clocks in case we're in ICS mode, because
16154    ICS gives us an update to both clocks after every move.
16155    Note that this routine is called *after* forwardMostMove
16156    is updated, so the last fractional tick must be subtracted
16157    from the color that is *not* on move now.
16158 */
16159 void
16160 SwitchClocks (int newMoveNr)
16161 {
16162     long lastTickLength;
16163     TimeMark now;
16164     int flagged = FALSE;
16165
16166     GetTimeMark(&now);
16167
16168     if (StopClockTimer() && appData.clockMode) {
16169         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16170         if (!WhiteOnMove(forwardMostMove)) {
16171             if(blackNPS >= 0) lastTickLength = 0;
16172             blackTimeRemaining -= lastTickLength;
16173            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16174 //         if(pvInfoList[forwardMostMove].time == -1)
16175                  pvInfoList[forwardMostMove].time =               // use GUI time
16176                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16177         } else {
16178            if(whiteNPS >= 0) lastTickLength = 0;
16179            whiteTimeRemaining -= lastTickLength;
16180            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16181 //         if(pvInfoList[forwardMostMove].time == -1)
16182                  pvInfoList[forwardMostMove].time =
16183                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16184         }
16185         flagged = CheckFlags();
16186     }
16187     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16188     CheckTimeControl();
16189
16190     if (flagged || !appData.clockMode) return;
16191
16192     switch (gameMode) {
16193       case MachinePlaysBlack:
16194       case MachinePlaysWhite:
16195       case BeginningOfGame:
16196         if (pausing) return;
16197         break;
16198
16199       case EditGame:
16200       case PlayFromGameFile:
16201       case IcsExamining:
16202         return;
16203
16204       default:
16205         break;
16206     }
16207
16208     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16209         if(WhiteOnMove(forwardMostMove))
16210              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16211         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16212     }
16213
16214     tickStartTM = now;
16215     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16216       whiteTimeRemaining : blackTimeRemaining);
16217     StartClockTimer(intendedTickLength);
16218 }
16219
16220
16221 /* Stop both clocks */
16222 void
16223 StopClocks ()
16224 {
16225     long lastTickLength;
16226     TimeMark now;
16227
16228     if (!StopClockTimer()) return;
16229     if (!appData.clockMode) return;
16230
16231     GetTimeMark(&now);
16232
16233     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16234     if (WhiteOnMove(forwardMostMove)) {
16235         if(whiteNPS >= 0) lastTickLength = 0;
16236         whiteTimeRemaining -= lastTickLength;
16237         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16238     } else {
16239         if(blackNPS >= 0) lastTickLength = 0;
16240         blackTimeRemaining -= lastTickLength;
16241         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16242     }
16243     CheckFlags();
16244 }
16245
16246 /* Start clock of player on move.  Time may have been reset, so
16247    if clock is already running, stop and restart it. */
16248 void
16249 StartClocks ()
16250 {
16251     (void) StopClockTimer(); /* in case it was running already */
16252     DisplayBothClocks();
16253     if (CheckFlags()) return;
16254
16255     if (!appData.clockMode) return;
16256     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16257
16258     GetTimeMark(&tickStartTM);
16259     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16260       whiteTimeRemaining : blackTimeRemaining);
16261
16262    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16263     whiteNPS = blackNPS = -1;
16264     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16265        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16266         whiteNPS = first.nps;
16267     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16268        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16269         blackNPS = first.nps;
16270     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16271         whiteNPS = second.nps;
16272     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16273         blackNPS = second.nps;
16274     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16275
16276     StartClockTimer(intendedTickLength);
16277 }
16278
16279 char *
16280 TimeString (long ms)
16281 {
16282     long second, minute, hour, day;
16283     char *sign = "";
16284     static char buf[32];
16285
16286     if (ms > 0 && ms <= 9900) {
16287       /* convert milliseconds to tenths, rounding up */
16288       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16289
16290       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16291       return buf;
16292     }
16293
16294     /* convert milliseconds to seconds, rounding up */
16295     /* use floating point to avoid strangeness of integer division
16296        with negative dividends on many machines */
16297     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16298
16299     if (second < 0) {
16300         sign = "-";
16301         second = -second;
16302     }
16303
16304     day = second / (60 * 60 * 24);
16305     second = second % (60 * 60 * 24);
16306     hour = second / (60 * 60);
16307     second = second % (60 * 60);
16308     minute = second / 60;
16309     second = second % 60;
16310
16311     if (day > 0)
16312       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16313               sign, day, hour, minute, second);
16314     else if (hour > 0)
16315       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16316     else
16317       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16318
16319     return buf;
16320 }
16321
16322
16323 /*
16324  * This is necessary because some C libraries aren't ANSI C compliant yet.
16325  */
16326 char *
16327 StrStr (char *string, char *match)
16328 {
16329     int i, length;
16330
16331     length = strlen(match);
16332
16333     for (i = strlen(string) - length; i >= 0; i--, string++)
16334       if (!strncmp(match, string, length))
16335         return string;
16336
16337     return NULL;
16338 }
16339
16340 char *
16341 StrCaseStr (char *string, char *match)
16342 {
16343     int i, j, length;
16344
16345     length = strlen(match);
16346
16347     for (i = strlen(string) - length; i >= 0; i--, string++) {
16348         for (j = 0; j < length; j++) {
16349             if (ToLower(match[j]) != ToLower(string[j]))
16350               break;
16351         }
16352         if (j == length) return string;
16353     }
16354
16355     return NULL;
16356 }
16357
16358 #ifndef _amigados
16359 int
16360 StrCaseCmp (char *s1, char *s2)
16361 {
16362     char c1, c2;
16363
16364     for (;;) {
16365         c1 = ToLower(*s1++);
16366         c2 = ToLower(*s2++);
16367         if (c1 > c2) return 1;
16368         if (c1 < c2) return -1;
16369         if (c1 == NULLCHAR) return 0;
16370     }
16371 }
16372
16373
16374 int
16375 ToLower (int c)
16376 {
16377     return isupper(c) ? tolower(c) : c;
16378 }
16379
16380
16381 int
16382 ToUpper (int c)
16383 {
16384     return islower(c) ? toupper(c) : c;
16385 }
16386 #endif /* !_amigados    */
16387
16388 char *
16389 StrSave (char *s)
16390 {
16391   char *ret;
16392
16393   if ((ret = (char *) malloc(strlen(s) + 1)))
16394     {
16395       safeStrCpy(ret, s, strlen(s)+1);
16396     }
16397   return ret;
16398 }
16399
16400 char *
16401 StrSavePtr (char *s, char **savePtr)
16402 {
16403     if (*savePtr) {
16404         free(*savePtr);
16405     }
16406     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16407       safeStrCpy(*savePtr, s, strlen(s)+1);
16408     }
16409     return(*savePtr);
16410 }
16411
16412 char *
16413 PGNDate ()
16414 {
16415     time_t clock;
16416     struct tm *tm;
16417     char buf[MSG_SIZ];
16418
16419     clock = time((time_t *)NULL);
16420     tm = localtime(&clock);
16421     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16422             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16423     return StrSave(buf);
16424 }
16425
16426
16427 char *
16428 PositionToFEN (int move, char *overrideCastling)
16429 {
16430     int i, j, fromX, fromY, toX, toY;
16431     int whiteToPlay;
16432     char buf[MSG_SIZ];
16433     char *p, *q;
16434     int emptycount;
16435     ChessSquare piece;
16436
16437     whiteToPlay = (gameMode == EditPosition) ?
16438       !blackPlaysFirst : (move % 2 == 0);
16439     p = buf;
16440
16441     /* Piece placement data */
16442     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16443         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16444         emptycount = 0;
16445         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16446             if (boards[move][i][j] == EmptySquare) {
16447                 emptycount++;
16448             } else { ChessSquare piece = boards[move][i][j];
16449                 if (emptycount > 0) {
16450                     if(emptycount<10) /* [HGM] can be >= 10 */
16451                         *p++ = '0' + emptycount;
16452                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16453                     emptycount = 0;
16454                 }
16455                 if(PieceToChar(piece) == '+') {
16456                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16457                     *p++ = '+';
16458                     piece = (ChessSquare)(DEMOTED piece);
16459                 }
16460                 *p++ = PieceToChar(piece);
16461                 if(p[-1] == '~') {
16462                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16463                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16464                     *p++ = '~';
16465                 }
16466             }
16467         }
16468         if (emptycount > 0) {
16469             if(emptycount<10) /* [HGM] can be >= 10 */
16470                 *p++ = '0' + emptycount;
16471             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16472             emptycount = 0;
16473         }
16474         *p++ = '/';
16475     }
16476     *(p - 1) = ' ';
16477
16478     /* [HGM] print Crazyhouse or Shogi holdings */
16479     if( gameInfo.holdingsWidth ) {
16480         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16481         q = p;
16482         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16483             piece = boards[move][i][BOARD_WIDTH-1];
16484             if( piece != EmptySquare )
16485               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16486                   *p++ = PieceToChar(piece);
16487         }
16488         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16489             piece = boards[move][BOARD_HEIGHT-i-1][0];
16490             if( piece != EmptySquare )
16491               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16492                   *p++ = PieceToChar(piece);
16493         }
16494
16495         if( q == p ) *p++ = '-';
16496         *p++ = ']';
16497         *p++ = ' ';
16498     }
16499
16500     /* Active color */
16501     *p++ = whiteToPlay ? 'w' : 'b';
16502     *p++ = ' ';
16503
16504   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16505     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16506   } else {
16507   if(nrCastlingRights) {
16508      q = p;
16509      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16510        /* [HGM] write directly from rights */
16511            if(boards[move][CASTLING][2] != NoRights &&
16512               boards[move][CASTLING][0] != NoRights   )
16513                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16514            if(boards[move][CASTLING][2] != NoRights &&
16515               boards[move][CASTLING][1] != NoRights   )
16516                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16517            if(boards[move][CASTLING][5] != NoRights &&
16518               boards[move][CASTLING][3] != NoRights   )
16519                 *p++ = boards[move][CASTLING][3] + AAA;
16520            if(boards[move][CASTLING][5] != NoRights &&
16521               boards[move][CASTLING][4] != NoRights   )
16522                 *p++ = boards[move][CASTLING][4] + AAA;
16523      } else {
16524
16525         /* [HGM] write true castling rights */
16526         if( nrCastlingRights == 6 ) {
16527             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16528                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16529             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16530                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16531             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16532                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16533             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16534                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16535         }
16536      }
16537      if (q == p) *p++ = '-'; /* No castling rights */
16538      *p++ = ' ';
16539   }
16540
16541   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16542      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16543     /* En passant target square */
16544     if (move > backwardMostMove) {
16545         fromX = moveList[move - 1][0] - AAA;
16546         fromY = moveList[move - 1][1] - ONE;
16547         toX = moveList[move - 1][2] - AAA;
16548         toY = moveList[move - 1][3] - ONE;
16549         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16550             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16551             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16552             fromX == toX) {
16553             /* 2-square pawn move just happened */
16554             *p++ = toX + AAA;
16555             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16556         } else {
16557             *p++ = '-';
16558         }
16559     } else if(move == backwardMostMove) {
16560         // [HGM] perhaps we should always do it like this, and forget the above?
16561         if((signed char)boards[move][EP_STATUS] >= 0) {
16562             *p++ = boards[move][EP_STATUS] + AAA;
16563             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16564         } else {
16565             *p++ = '-';
16566         }
16567     } else {
16568         *p++ = '-';
16569     }
16570     *p++ = ' ';
16571   }
16572   }
16573
16574     /* [HGM] find reversible plies */
16575     {   int i = 0, j=move;
16576
16577         if (appData.debugMode) { int k;
16578             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16579             for(k=backwardMostMove; k<=forwardMostMove; k++)
16580                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16581
16582         }
16583
16584         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16585         if( j == backwardMostMove ) i += initialRulePlies;
16586         sprintf(p, "%d ", i);
16587         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16588     }
16589     /* Fullmove number */
16590     sprintf(p, "%d", (move / 2) + 1);
16591
16592     return StrSave(buf);
16593 }
16594
16595 Boolean
16596 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16597 {
16598     int i, j;
16599     char *p, c;
16600     int emptycount;
16601     ChessSquare piece;
16602
16603     p = fen;
16604
16605     /* [HGM] by default clear Crazyhouse holdings, if present */
16606     if(gameInfo.holdingsWidth) {
16607        for(i=0; i<BOARD_HEIGHT; i++) {
16608            board[i][0]             = EmptySquare; /* black holdings */
16609            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16610            board[i][1]             = (ChessSquare) 0; /* black counts */
16611            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16612        }
16613     }
16614
16615     /* Piece placement data */
16616     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16617         j = 0;
16618         for (;;) {
16619             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16620                 if (*p == '/') p++;
16621                 emptycount = gameInfo.boardWidth - j;
16622                 while (emptycount--)
16623                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16624                 break;
16625 #if(BOARD_FILES >= 10)
16626             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16627                 p++; emptycount=10;
16628                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16629                 while (emptycount--)
16630                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16631 #endif
16632             } else if (isdigit(*p)) {
16633                 emptycount = *p++ - '0';
16634                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16635                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16636                 while (emptycount--)
16637                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16638             } else if (*p == '+' || isalpha(*p)) {
16639                 if (j >= gameInfo.boardWidth) return FALSE;
16640                 if(*p=='+') {
16641                     piece = CharToPiece(*++p);
16642                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16643                     piece = (ChessSquare) (PROMOTED piece ); p++;
16644                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16645                 } else piece = CharToPiece(*p++);
16646
16647                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16648                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16649                     piece = (ChessSquare) (PROMOTED piece);
16650                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16651                     p++;
16652                 }
16653                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16654             } else {
16655                 return FALSE;
16656             }
16657         }
16658     }
16659     while (*p == '/' || *p == ' ') p++;
16660
16661     /* [HGM] look for Crazyhouse holdings here */
16662     while(*p==' ') p++;
16663     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16664         if(*p == '[') p++;
16665         if(*p == '-' ) p++; /* empty holdings */ else {
16666             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16667             /* if we would allow FEN reading to set board size, we would   */
16668             /* have to add holdings and shift the board read so far here   */
16669             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16670                 p++;
16671                 if((int) piece >= (int) BlackPawn ) {
16672                     i = (int)piece - (int)BlackPawn;
16673                     i = PieceToNumber((ChessSquare)i);
16674                     if( i >= gameInfo.holdingsSize ) return FALSE;
16675                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16676                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16677                 } else {
16678                     i = (int)piece - (int)WhitePawn;
16679                     i = PieceToNumber((ChessSquare)i);
16680                     if( i >= gameInfo.holdingsSize ) return FALSE;
16681                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16682                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16683                 }
16684             }
16685         }
16686         if(*p == ']') p++;
16687     }
16688
16689     while(*p == ' ') p++;
16690
16691     /* Active color */
16692     c = *p++;
16693     if(appData.colorNickNames) {
16694       if( c == appData.colorNickNames[0] ) c = 'w'; else
16695       if( c == appData.colorNickNames[1] ) c = 'b';
16696     }
16697     switch (c) {
16698       case 'w':
16699         *blackPlaysFirst = FALSE;
16700         break;
16701       case 'b':
16702         *blackPlaysFirst = TRUE;
16703         break;
16704       default:
16705         return FALSE;
16706     }
16707
16708     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16709     /* return the extra info in global variiables             */
16710
16711     /* set defaults in case FEN is incomplete */
16712     board[EP_STATUS] = EP_UNKNOWN;
16713     for(i=0; i<nrCastlingRights; i++ ) {
16714         board[CASTLING][i] =
16715             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16716     }   /* assume possible unless obviously impossible */
16717     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16718     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16719     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16720                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16721     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16722     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16723     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16724                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16725     FENrulePlies = 0;
16726
16727     while(*p==' ') p++;
16728     if(nrCastlingRights) {
16729       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16730           /* castling indicator present, so default becomes no castlings */
16731           for(i=0; i<nrCastlingRights; i++ ) {
16732                  board[CASTLING][i] = NoRights;
16733           }
16734       }
16735       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16736              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16737              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16738              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16739         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16740
16741         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16742             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16743             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16744         }
16745         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16746             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16747         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16748                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16749         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16750                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16751         switch(c) {
16752           case'K':
16753               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16754               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16755               board[CASTLING][2] = whiteKingFile;
16756               break;
16757           case'Q':
16758               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16759               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16760               board[CASTLING][2] = whiteKingFile;
16761               break;
16762           case'k':
16763               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16764               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16765               board[CASTLING][5] = blackKingFile;
16766               break;
16767           case'q':
16768               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16769               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16770               board[CASTLING][5] = blackKingFile;
16771           case '-':
16772               break;
16773           default: /* FRC castlings */
16774               if(c >= 'a') { /* black rights */
16775                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16776                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16777                   if(i == BOARD_RGHT) break;
16778                   board[CASTLING][5] = i;
16779                   c -= AAA;
16780                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16781                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16782                   if(c > i)
16783                       board[CASTLING][3] = c;
16784                   else
16785                       board[CASTLING][4] = c;
16786               } else { /* white rights */
16787                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16788                     if(board[0][i] == WhiteKing) break;
16789                   if(i == BOARD_RGHT) break;
16790                   board[CASTLING][2] = i;
16791                   c -= AAA - 'a' + 'A';
16792                   if(board[0][c] >= WhiteKing) break;
16793                   if(c > i)
16794                       board[CASTLING][0] = c;
16795                   else
16796                       board[CASTLING][1] = c;
16797               }
16798         }
16799       }
16800       for(i=0; i<nrCastlingRights; i++)
16801         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16802     if (appData.debugMode) {
16803         fprintf(debugFP, "FEN castling rights:");
16804         for(i=0; i<nrCastlingRights; i++)
16805         fprintf(debugFP, " %d", board[CASTLING][i]);
16806         fprintf(debugFP, "\n");
16807     }
16808
16809       while(*p==' ') p++;
16810     }
16811
16812     /* read e.p. field in games that know e.p. capture */
16813     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16814        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16815       if(*p=='-') {
16816         p++; board[EP_STATUS] = EP_NONE;
16817       } else {
16818          char c = *p++ - AAA;
16819
16820          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16821          if(*p >= '0' && *p <='9') p++;
16822          board[EP_STATUS] = c;
16823       }
16824     }
16825
16826
16827     if(sscanf(p, "%d", &i) == 1) {
16828         FENrulePlies = i; /* 50-move ply counter */
16829         /* (The move number is still ignored)    */
16830     }
16831
16832     return TRUE;
16833 }
16834
16835 void
16836 EditPositionPasteFEN (char *fen)
16837 {
16838   if (fen != NULL) {
16839     Board initial_position;
16840
16841     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16842       DisplayError(_("Bad FEN position in clipboard"), 0);
16843       return ;
16844     } else {
16845       int savedBlackPlaysFirst = blackPlaysFirst;
16846       EditPositionEvent();
16847       blackPlaysFirst = savedBlackPlaysFirst;
16848       CopyBoard(boards[0], initial_position);
16849       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16850       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16851       DisplayBothClocks();
16852       DrawPosition(FALSE, boards[currentMove]);
16853     }
16854   }
16855 }
16856
16857 static char cseq[12] = "\\   ";
16858
16859 Boolean
16860 set_cont_sequence (char *new_seq)
16861 {
16862     int len;
16863     Boolean ret;
16864
16865     // handle bad attempts to set the sequence
16866         if (!new_seq)
16867                 return 0; // acceptable error - no debug
16868
16869     len = strlen(new_seq);
16870     ret = (len > 0) && (len < sizeof(cseq));
16871     if (ret)
16872       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16873     else if (appData.debugMode)
16874       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16875     return ret;
16876 }
16877
16878 /*
16879     reformat a source message so words don't cross the width boundary.  internal
16880     newlines are not removed.  returns the wrapped size (no null character unless
16881     included in source message).  If dest is NULL, only calculate the size required
16882     for the dest buffer.  lp argument indicats line position upon entry, and it's
16883     passed back upon exit.
16884 */
16885 int
16886 wrap (char *dest, char *src, int count, int width, int *lp)
16887 {
16888     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16889
16890     cseq_len = strlen(cseq);
16891     old_line = line = *lp;
16892     ansi = len = clen = 0;
16893
16894     for (i=0; i < count; i++)
16895     {
16896         if (src[i] == '\033')
16897             ansi = 1;
16898
16899         // if we hit the width, back up
16900         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16901         {
16902             // store i & len in case the word is too long
16903             old_i = i, old_len = len;
16904
16905             // find the end of the last word
16906             while (i && src[i] != ' ' && src[i] != '\n')
16907             {
16908                 i--;
16909                 len--;
16910             }
16911
16912             // word too long?  restore i & len before splitting it
16913             if ((old_i-i+clen) >= width)
16914             {
16915                 i = old_i;
16916                 len = old_len;
16917             }
16918
16919             // extra space?
16920             if (i && src[i-1] == ' ')
16921                 len--;
16922
16923             if (src[i] != ' ' && src[i] != '\n')
16924             {
16925                 i--;
16926                 if (len)
16927                     len--;
16928             }
16929
16930             // now append the newline and continuation sequence
16931             if (dest)
16932                 dest[len] = '\n';
16933             len++;
16934             if (dest)
16935                 strncpy(dest+len, cseq, cseq_len);
16936             len += cseq_len;
16937             line = cseq_len;
16938             clen = cseq_len;
16939             continue;
16940         }
16941
16942         if (dest)
16943             dest[len] = src[i];
16944         len++;
16945         if (!ansi)
16946             line++;
16947         if (src[i] == '\n')
16948             line = 0;
16949         if (src[i] == 'm')
16950             ansi = 0;
16951     }
16952     if (dest && appData.debugMode)
16953     {
16954         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16955             count, width, line, len, *lp);
16956         show_bytes(debugFP, src, count);
16957         fprintf(debugFP, "\ndest: ");
16958         show_bytes(debugFP, dest, len);
16959         fprintf(debugFP, "\n");
16960     }
16961     *lp = dest ? line : old_line;
16962
16963     return len;
16964 }
16965
16966 // [HGM] vari: routines for shelving variations
16967 Boolean modeRestore = FALSE;
16968
16969 void
16970 PushInner (int firstMove, int lastMove)
16971 {
16972         int i, j, nrMoves = lastMove - firstMove;
16973
16974         // push current tail of game on stack
16975         savedResult[storedGames] = gameInfo.result;
16976         savedDetails[storedGames] = gameInfo.resultDetails;
16977         gameInfo.resultDetails = NULL;
16978         savedFirst[storedGames] = firstMove;
16979         savedLast [storedGames] = lastMove;
16980         savedFramePtr[storedGames] = framePtr;
16981         framePtr -= nrMoves; // reserve space for the boards
16982         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16983             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16984             for(j=0; j<MOVE_LEN; j++)
16985                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16986             for(j=0; j<2*MOVE_LEN; j++)
16987                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16988             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16989             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16990             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16991             pvInfoList[firstMove+i-1].depth = 0;
16992             commentList[framePtr+i] = commentList[firstMove+i];
16993             commentList[firstMove+i] = NULL;
16994         }
16995
16996         storedGames++;
16997         forwardMostMove = firstMove; // truncate game so we can start variation
16998 }
16999
17000 void
17001 PushTail (int firstMove, int lastMove)
17002 {
17003         if(appData.icsActive) { // only in local mode
17004                 forwardMostMove = currentMove; // mimic old ICS behavior
17005                 return;
17006         }
17007         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17008
17009         PushInner(firstMove, lastMove);
17010         if(storedGames == 1) GreyRevert(FALSE);
17011         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17012 }
17013
17014 void
17015 PopInner (Boolean annotate)
17016 {
17017         int i, j, nrMoves;
17018         char buf[8000], moveBuf[20];
17019
17020         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17021         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17022         nrMoves = savedLast[storedGames] - currentMove;
17023         if(annotate) {
17024                 int cnt = 10;
17025                 if(!WhiteOnMove(currentMove))
17026                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17027                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17028                 for(i=currentMove; i<forwardMostMove; i++) {
17029                         if(WhiteOnMove(i))
17030                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17031                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17032                         strcat(buf, moveBuf);
17033                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17034                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17035                 }
17036                 strcat(buf, ")");
17037         }
17038         for(i=1; i<=nrMoves; i++) { // copy last variation back
17039             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17040             for(j=0; j<MOVE_LEN; j++)
17041                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17042             for(j=0; j<2*MOVE_LEN; j++)
17043                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17044             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17045             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17046             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17047             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17048             commentList[currentMove+i] = commentList[framePtr+i];
17049             commentList[framePtr+i] = NULL;
17050         }
17051         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17052         framePtr = savedFramePtr[storedGames];
17053         gameInfo.result = savedResult[storedGames];
17054         if(gameInfo.resultDetails != NULL) {
17055             free(gameInfo.resultDetails);
17056       }
17057         gameInfo.resultDetails = savedDetails[storedGames];
17058         forwardMostMove = currentMove + nrMoves;
17059 }
17060
17061 Boolean
17062 PopTail (Boolean annotate)
17063 {
17064         if(appData.icsActive) return FALSE; // only in local mode
17065         if(!storedGames) return FALSE; // sanity
17066         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17067
17068         PopInner(annotate);
17069         if(currentMove < forwardMostMove) ForwardEvent(); else
17070         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17071
17072         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17073         return TRUE;
17074 }
17075
17076 void
17077 CleanupTail ()
17078 {       // remove all shelved variations
17079         int i;
17080         for(i=0; i<storedGames; i++) {
17081             if(savedDetails[i])
17082                 free(savedDetails[i]);
17083             savedDetails[i] = NULL;
17084         }
17085         for(i=framePtr; i<MAX_MOVES; i++) {
17086                 if(commentList[i]) free(commentList[i]);
17087                 commentList[i] = NULL;
17088         }
17089         framePtr = MAX_MOVES-1;
17090         storedGames = 0;
17091 }
17092
17093 void
17094 LoadVariation (int index, char *text)
17095 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17096         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17097         int level = 0, move;
17098
17099         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17100         // first find outermost bracketing variation
17101         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17102             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17103                 if(*p == '{') wait = '}'; else
17104                 if(*p == '[') wait = ']'; else
17105                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17106                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17107             }
17108             if(*p == wait) wait = NULLCHAR; // closing ]} found
17109             p++;
17110         }
17111         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17112         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17113         end[1] = NULLCHAR; // clip off comment beyond variation
17114         ToNrEvent(currentMove-1);
17115         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17116         // kludge: use ParsePV() to append variation to game
17117         move = currentMove;
17118         ParsePV(start, TRUE, TRUE);
17119         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17120         ClearPremoveHighlights();
17121         CommentPopDown();
17122         ToNrEvent(currentMove+1);
17123 }
17124