Give the dual-board option a separate board window
[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 static void ExcludeClick P((int index));
226
227 #ifdef WIN32
228        extern void ConsoleCreate();
229 #endif
230
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
234
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
242 Boolean abortMatch;
243
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 int endPV = -1;
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
251 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
255 Boolean partnerUp;
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
267 int chattingPartner;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273
274 /* States for ics_getting_history */
275 #define H_FALSE 0
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
281
282 /* whosays values for GameEnds */
283 #define GE_ICS 0
284 #define GE_ENGINE 1
285 #define GE_PLAYER 2
286 #define GE_FILE 3
287 #define GE_XBOARD 4
288 #define GE_ENGINE1 5
289 #define GE_ENGINE2 6
290
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
293
294 /* Different types of move when calling RegisterMove */
295 #define CMAIL_MOVE   0
296 #define CMAIL_RESIGN 1
297 #define CMAIL_DRAW   2
298 #define CMAIL_ACCEPT 3
299
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
304
305 /* Telnet protocol constants */
306 #define TN_WILL 0373
307 #define TN_WONT 0374
308 #define TN_DO   0375
309 #define TN_DONT 0376
310 #define TN_IAC  0377
311 #define TN_ECHO 0001
312 #define TN_SGA  0003
313 #define TN_PORT 23
314
315 char*
316 safeStrCpy (char *dst, const char *src, size_t count)
317 { // [HGM] made safe
318   int i;
319   assert( dst != NULL );
320   assert( src != NULL );
321   assert( count > 0 );
322
323   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324   if(  i == count && dst[count-1] != NULLCHAR)
325     {
326       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327       if(appData.debugMode)
328       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
329     }
330
331   return dst;
332 }
333
334 /* Some compiler can't cast u64 to double
335  * This function do the job for us:
336
337  * We use the highest bit for cast, this only
338  * works if the highest bit is not
339  * in use (This should not happen)
340  *
341  * We used this for all compiler
342  */
343 double
344 u64ToDouble (u64 value)
345 {
346   double r;
347   u64 tmp = value & u64Const(0x7fffffffffffffff);
348   r = (double)(s64)tmp;
349   if (value & u64Const(0x8000000000000000))
350        r +=  9.2233720368547758080e18; /* 2^63 */
351  return r;
352 }
353
354 /* Fake up flags for now, as we aren't keeping track of castling
355    availability yet. [HGM] Change of logic: the flag now only
356    indicates the type of castlings allowed by the rule of the game.
357    The actual rights themselves are maintained in the array
358    castlingRights, as part of the game history, and are not probed
359    by this function.
360  */
361 int
362 PosFlags (index)
363 {
364   int flags = F_ALL_CASTLE_OK;
365   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366   switch (gameInfo.variant) {
367   case VariantSuicide:
368     flags &= ~F_ALL_CASTLE_OK;
369   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370     flags |= F_IGNORE_CHECK;
371   case VariantLosers:
372     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
373     break;
374   case VariantAtomic:
375     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376     break;
377   case VariantKriegspiel:
378     flags |= F_KRIEGSPIEL_CAPTURE;
379     break;
380   case VariantCapaRandom:
381   case VariantFischeRandom:
382     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383   case VariantNoCastle:
384   case VariantShatranj:
385   case VariantCourier:
386   case VariantMakruk:
387   case VariantGrand:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
398
399 /*
400     [AS] Note: sometimes, the sscanf() function is used to parse the input
401     into a fixed-size buffer. Because of this, we must be prepared to
402     receive strings as long as the size of the input buffer, which is currently
403     set to 4K for Windows and 8K for the rest.
404     So, we must either allocate sufficiently large buffers here, or
405     reduce the size of the input buffer in the input reading part.
406 */
407
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
411
412 ChessProgramState first, second, pairing;
413
414 /* premove variables */
415 int premoveToX = 0;
416 int premoveToY = 0;
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
420 int gotPremove = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
423
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
426
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey; // [HGM] set by mouse handler
454
455 int have_sent_ICS_logon = 0;
456 int movesPerSession;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
506
507 ChessSquare  FIDEArray[2][BOARD_FILES] = {
508     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511         BlackKing, BlackBishop, BlackKnight, BlackRook }
512 };
513
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518         BlackKing, BlackKing, BlackKnight, BlackRook }
519 };
520
521 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
522     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524     { BlackRook, BlackMan, BlackBishop, BlackQueen,
525         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 };
527
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 };
534
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 };
541
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 };
548
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552     { BlackRook, BlackKnight, BlackMan, BlackFerz,
553         BlackKing, BlackMan, BlackKnight, BlackRook }
554 };
555
556
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 };
564
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 };
571
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 };
578
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 };
585
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 };
592
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
598 };
599
600 #ifdef GOTHIC
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 };
607 #else // !GOTHIC
608 #define GothicArray CapablancaArray
609 #endif // !GOTHIC
610
611 #ifdef FALCON
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 };
618 #else // !FALCON
619 #define FalconArray CapablancaArray
620 #endif // !FALCON
621
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
628
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 };
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
639
640
641 Board initialPosition;
642
643
644 /* Convert str to a rating. Checks for special cases of "----",
645
646    "++++", etc. Also strips ()'s */
647 int
648 string_to_rating (char *str)
649 {
650   while(*str && !isdigit(*str)) ++str;
651   if (!*str)
652     return 0;   /* One of the special "no rating" cases */
653   else
654     return atoi(str);
655 }
656
657 void
658 ClearProgramStats ()
659 {
660     /* Init programStats */
661     programStats.movelist[0] = 0;
662     programStats.depth = 0;
663     programStats.nr_moves = 0;
664     programStats.moves_left = 0;
665     programStats.nodes = 0;
666     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
667     programStats.score = 0;
668     programStats.got_only_move = 0;
669     programStats.got_fail = 0;
670     programStats.line_is_book = 0;
671 }
672
673 void
674 CommonEngineInit ()
675 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676     if (appData.firstPlaysBlack) {
677         first.twoMachinesColor = "black\n";
678         second.twoMachinesColor = "white\n";
679     } else {
680         first.twoMachinesColor = "white\n";
681         second.twoMachinesColor = "black\n";
682     }
683
684     first.other = &second;
685     second.other = &first;
686
687     { float norm = 1;
688         if(appData.timeOddsMode) {
689             norm = appData.timeOdds[0];
690             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691         }
692         first.timeOdds  = appData.timeOdds[0]/norm;
693         second.timeOdds = appData.timeOdds[1]/norm;
694     }
695
696     if(programVersion) free(programVersion);
697     if (appData.noChessProgram) {
698         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699         sprintf(programVersion, "%s", PACKAGE_STRING);
700     } else {
701       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
704     }
705 }
706
707 void
708 UnloadEngine (ChessProgramState *cps)
709 {
710         /* Kill off first chess program */
711         if (cps->isr != NULL)
712           RemoveInputSource(cps->isr);
713         cps->isr = NULL;
714
715         if (cps->pr != NoProc) {
716             ExitAnalyzeMode();
717             DoSleep( appData.delayBeforeQuit );
718             SendToProgram("quit\n", cps);
719             DoSleep( appData.delayAfterQuit );
720             DestroyChildProcess(cps->pr, cps->useSigterm);
721         }
722         cps->pr = NoProc;
723         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
724 }
725
726 void
727 ClearOptions (ChessProgramState *cps)
728 {
729     int i;
730     cps->nrOptions = cps->comboCnt = 0;
731     for(i=0; i<MAX_OPTIONS; i++) {
732         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733         cps->option[i].textValue = 0;
734     }
735 }
736
737 char *engineNames[] = {
738   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 N_("first"),
741   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 N_("second")
744 };
745
746 void
747 InitEngine (ChessProgramState *cps, int n)
748 {   // [HGM] all engine initialiation put in a function that does one engine
749
750     ClearOptions(cps);
751
752     cps->which = engineNames[n];
753     cps->maybeThinking = FALSE;
754     cps->pr = NoProc;
755     cps->isr = NULL;
756     cps->sendTime = 2;
757     cps->sendDrawOffers = 1;
758
759     cps->program = appData.chessProgram[n];
760     cps->host = appData.host[n];
761     cps->dir = appData.directory[n];
762     cps->initString = appData.engInitString[n];
763     cps->computerString = appData.computerString[n];
764     cps->useSigint  = TRUE;
765     cps->useSigterm = TRUE;
766     cps->reuse = appData.reuse[n];
767     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
768     cps->useSetboard = FALSE;
769     cps->useSAN = FALSE;
770     cps->usePing = FALSE;
771     cps->lastPing = 0;
772     cps->lastPong = 0;
773     cps->usePlayother = FALSE;
774     cps->useColors = TRUE;
775     cps->useUsermove = FALSE;
776     cps->sendICS = FALSE;
777     cps->sendName = appData.icsActive;
778     cps->sdKludge = FALSE;
779     cps->stKludge = FALSE;
780     TidyProgramName(cps->program, cps->host, cps->tidy);
781     cps->matchWins = 0;
782     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783     cps->analysisSupport = 2; /* detect */
784     cps->analyzing = FALSE;
785     cps->initDone = FALSE;
786
787     /* New features added by Tord: */
788     cps->useFEN960 = FALSE;
789     cps->useOOCastle = TRUE;
790     /* End of new features added by Tord. */
791     cps->fenOverride  = appData.fenOverride[n];
792
793     /* [HGM] time odds: set factor for each machine */
794     cps->timeOdds  = appData.timeOdds[n];
795
796     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797     cps->accumulateTC = appData.accumulateTC[n];
798     cps->maxNrOfSessions = 1;
799
800     /* [HGM] debug */
801     cps->debug = FALSE;
802
803     cps->supportsNPS = UNKNOWN;
804     cps->memSize = FALSE;
805     cps->maxCores = FALSE;
806     cps->egtFormats[0] = NULLCHAR;
807
808     /* [HGM] options */
809     cps->optionSettings  = appData.engOptions[n];
810
811     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812     cps->isUCI = appData.isUCI[n]; /* [AS] */
813     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814
815     if (appData.protocolVersion[n] > PROTOVER
816         || appData.protocolVersion[n] < 1)
817       {
818         char buf[MSG_SIZ];
819         int len;
820
821         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822                        appData.protocolVersion[n]);
823         if( (len >= MSG_SIZ) && appData.debugMode )
824           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825
826         DisplayFatalError(buf, 0, 2);
827       }
828     else
829       {
830         cps->protocolVersion = appData.protocolVersion[n];
831       }
832
833     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
834     ParseFeatures(appData.featureDefaults, cps);
835 }
836
837 ChessProgramState *savCps;
838
839 void
840 LoadEngine ()
841 {
842     int i;
843     if(WaitForEngine(savCps, LoadEngine)) return;
844     CommonEngineInit(); // recalculate time odds
845     if(gameInfo.variant != StringToVariant(appData.variant)) {
846         // we changed variant when loading the engine; this forces us to reset
847         Reset(TRUE, savCps != &first);
848         EditGameEvent(); // for consistency with other path, as Reset changes mode
849     }
850     InitChessProgram(savCps, FALSE);
851     SendToProgram("force\n", savCps);
852     DisplayMessage("", "");
853     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
855     ThawUI();
856     SetGNUMode();
857 }
858
859 void
860 ReplaceEngine (ChessProgramState *cps, int n)
861 {
862     EditGameEvent();
863     UnloadEngine(cps);
864     appData.noChessProgram = FALSE;
865     appData.clockMode = TRUE;
866     InitEngine(cps, n);
867     UpdateLogos(TRUE);
868     if(n) return; // only startup first engine immediately; second can wait
869     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870     LoadEngine();
871 }
872
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875
876 static char resetOptions[] = 
877         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
881
882 void
883 FloatToFront(char **list, char *engineLine)
884 {
885     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886     int i=0;
887     if(appData.recentEngines <= 0) return;
888     TidyProgramName(engineLine, "localhost", tidy+1);
889     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890     strncpy(buf+1, *list, MSG_SIZ-50);
891     if(p = strstr(buf, tidy)) { // tidy name appears in list
892         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893         while(*p++ = *++q); // squeeze out
894     }
895     strcat(tidy, buf+1); // put list behind tidy name
896     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898     ASSIGN(*list, tidy+1);
899 }
900
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
902
903 void
904 Load (ChessProgramState *cps, int i)
905 {
906     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912         appData.firstProtocolVersion = PROTOVER;
913         ParseArgsFromString(buf);
914         SwapEngines(i);
915         ReplaceEngine(cps, i);
916         FloatToFront(&appData.recentEngineList, engineLine);
917         return;
918     }
919     p = engineName;
920     while(q = strchr(p, SLASH)) p = q+1;
921     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922     if(engineDir[0] != NULLCHAR) {
923         ASSIGN(appData.directory[i], engineDir);
924     } else if(p != engineName) { // derive directory from engine path, when not given
925         p[-1] = 0;
926         ASSIGN(appData.directory[i], engineName);
927         p[-1] = SLASH;
928         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929     } else { ASSIGN(appData.directory[i], "."); }
930     if(params[0]) {
931         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932         snprintf(command, MSG_SIZ, "%s %s", p, params);
933         p = command;
934     }
935     ASSIGN(appData.chessProgram[i], p);
936     appData.isUCI[i] = isUCI;
937     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938     appData.hasOwnBookUCI[i] = hasBook;
939     if(!nickName[0]) useNick = FALSE;
940     if(useNick) ASSIGN(appData.pgnName[i], nickName);
941     if(addToList) {
942         int len;
943         char quote;
944         q = firstChessProgramNames;
945         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948                         quote, p, quote, appData.directory[i], 
949                         useNick ? " -fn \"" : "",
950                         useNick ? nickName : "",
951                         useNick ? "\"" : "",
952                         v1 ? " -firstProtocolVersion 1" : "",
953                         hasBook ? "" : " -fNoOwnBookUCI",
954                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955                         storeVariant ? " -variant " : "",
956                         storeVariant ? VariantName(gameInfo.variant) : "");
957         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959         if(insert != q) insert[-1] = NULLCHAR;
960         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961         if(q)   free(q);
962         FloatToFront(&appData.recentEngineList, buf);
963     }
964     ReplaceEngine(cps, i);
965 }
966
967 void
968 InitTimeControls ()
969 {
970     int matched, min, sec;
971     /*
972      * Parse timeControl resource
973      */
974     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975                           appData.movesPerSession)) {
976         char buf[MSG_SIZ];
977         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978         DisplayFatalError(buf, 0, 2);
979     }
980
981     /*
982      * Parse searchTime resource
983      */
984     if (*appData.searchTime != NULLCHAR) {
985         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986         if (matched == 1) {
987             searchTime = min * 60;
988         } else if (matched == 2) {
989             searchTime = min * 60 + sec;
990         } else {
991             char buf[MSG_SIZ];
992             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993             DisplayFatalError(buf, 0, 2);
994         }
995     }
996 }
997
998 void
999 InitBackEnd1 ()
1000 {
1001
1002     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004
1005     GetTimeMark(&programStartTime);
1006     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007     appData.seedBase = random() + (random()<<15);
1008     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009
1010     ClearProgramStats();
1011     programStats.ok_to_send = 1;
1012     programStats.seen_stat = 0;
1013
1014     /*
1015      * Initialize game list
1016      */
1017     ListNew(&gameList);
1018
1019
1020     /*
1021      * Internet chess server status
1022      */
1023     if (appData.icsActive) {
1024         appData.matchMode = FALSE;
1025         appData.matchGames = 0;
1026 #if ZIPPY
1027         appData.noChessProgram = !appData.zippyPlay;
1028 #else
1029         appData.zippyPlay = FALSE;
1030         appData.zippyTalk = FALSE;
1031         appData.noChessProgram = TRUE;
1032 #endif
1033         if (*appData.icsHelper != NULLCHAR) {
1034             appData.useTelnet = TRUE;
1035             appData.telnetProgram = appData.icsHelper;
1036         }
1037     } else {
1038         appData.zippyTalk = appData.zippyPlay = FALSE;
1039     }
1040
1041     /* [AS] Initialize pv info list [HGM] and game state */
1042     {
1043         int i, j;
1044
1045         for( i=0; i<=framePtr; i++ ) {
1046             pvInfoList[i].depth = -1;
1047             boards[i][EP_STATUS] = EP_NONE;
1048             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1049         }
1050     }
1051
1052     InitTimeControls();
1053
1054     /* [AS] Adjudication threshold */
1055     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056
1057     InitEngine(&first, 0);
1058     InitEngine(&second, 1);
1059     CommonEngineInit();
1060
1061     pairing.which = "pairing"; // pairing engine
1062     pairing.pr = NoProc;
1063     pairing.isr = NULL;
1064     pairing.program = appData.pairingEngine;
1065     pairing.host = "localhost";
1066     pairing.dir = ".";
1067
1068     if (appData.icsActive) {
1069         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1070     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071         appData.clockMode = FALSE;
1072         first.sendTime = second.sendTime = 0;
1073     }
1074
1075 #if ZIPPY
1076     /* Override some settings from environment variables, for backward
1077        compatibility.  Unfortunately it's not feasible to have the env
1078        vars just set defaults, at least in xboard.  Ugh.
1079     */
1080     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1081       ZippyInit();
1082     }
1083 #endif
1084
1085     if (!appData.icsActive) {
1086       char buf[MSG_SIZ];
1087       int len;
1088
1089       /* Check for variants that are supported only in ICS mode,
1090          or not at all.  Some that are accepted here nevertheless
1091          have bugs; see comments below.
1092       */
1093       VariantClass variant = StringToVariant(appData.variant);
1094       switch (variant) {
1095       case VariantBughouse:     /* need four players and two boards */
1096       case VariantKriegspiel:   /* need to hide pieces and move details */
1097         /* case VariantFischeRandom: (Fabien: moved below) */
1098         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099         if( (len >= MSG_SIZ) && appData.debugMode )
1100           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101
1102         DisplayFatalError(buf, 0, 2);
1103         return;
1104
1105       case VariantUnknown:
1106       case VariantLoadable:
1107       case Variant29:
1108       case Variant30:
1109       case Variant31:
1110       case Variant32:
1111       case Variant33:
1112       case Variant34:
1113       case Variant35:
1114       case Variant36:
1115       default:
1116         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117         if( (len >= MSG_SIZ) && appData.debugMode )
1118           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119
1120         DisplayFatalError(buf, 0, 2);
1121         return;
1122
1123       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1124       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1125       case VariantGothic:     /* [HGM] should work */
1126       case VariantCapablanca: /* [HGM] should work */
1127       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1128       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1129       case VariantKnightmate: /* [HGM] should work */
1130       case VariantCylinder:   /* [HGM] untested */
1131       case VariantFalcon:     /* [HGM] untested */
1132       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133                                  offboard interposition not understood */
1134       case VariantNormal:     /* definitely works! */
1135       case VariantWildCastle: /* pieces not automatically shuffled */
1136       case VariantNoCastle:   /* pieces not automatically shuffled */
1137       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138       case VariantLosers:     /* should work except for win condition,
1139                                  and doesn't know captures are mandatory */
1140       case VariantSuicide:    /* should work except for win condition,
1141                                  and doesn't know captures are mandatory */
1142       case VariantGiveaway:   /* should work except for win condition,
1143                                  and doesn't know captures are mandatory */
1144       case VariantTwoKings:   /* should work */
1145       case VariantAtomic:     /* should work except for win condition */
1146       case Variant3Check:     /* should work except for win condition */
1147       case VariantShatranj:   /* should work except for all win conditions */
1148       case VariantMakruk:     /* should work except for draw countdown */
1149       case VariantBerolina:   /* might work if TestLegality is off */
1150       case VariantCapaRandom: /* should work */
1151       case VariantJanus:      /* should work */
1152       case VariantSuper:      /* experimental */
1153       case VariantGreat:      /* experimental, requires legality testing to be off */
1154       case VariantSChess:     /* S-Chess, should work */
1155       case VariantGrand:      /* should work */
1156       case VariantSpartan:    /* should work */
1157         break;
1158       }
1159     }
1160
1161 }
1162
1163 int
1164 NextIntegerFromString (char ** str, long * value)
1165 {
1166     int result = -1;
1167     char * s = *str;
1168
1169     while( *s == ' ' || *s == '\t' ) {
1170         s++;
1171     }
1172
1173     *value = 0;
1174
1175     if( *s >= '0' && *s <= '9' ) {
1176         while( *s >= '0' && *s <= '9' ) {
1177             *value = *value * 10 + (*s - '0');
1178             s++;
1179         }
1180
1181         result = 0;
1182     }
1183
1184     *str = s;
1185
1186     return result;
1187 }
1188
1189 int
1190 NextTimeControlFromString (char ** str, long * value)
1191 {
1192     long temp;
1193     int result = NextIntegerFromString( str, &temp );
1194
1195     if( result == 0 ) {
1196         *value = temp * 60; /* Minutes */
1197         if( **str == ':' ) {
1198             (*str)++;
1199             result = NextIntegerFromString( str, &temp );
1200             *value += temp; /* Seconds */
1201         }
1202     }
1203
1204     return result;
1205 }
1206
1207 int
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210     int result = -1, type = 0; long temp, temp2;
1211
1212     if(**str != ':') return -1; // old params remain in force!
1213     (*str)++;
1214     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215     if( NextIntegerFromString( str, &temp ) ) return -1;
1216     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1217
1218     if(**str != '/') {
1219         /* time only: incremental or sudden-death time control */
1220         if(**str == '+') { /* increment follows; read it */
1221             (*str)++;
1222             if(**str == '!') type = *(*str)++; // Bronstein TC
1223             if(result = NextIntegerFromString( str, &temp2)) return -1;
1224             *inc = temp2 * 1000;
1225             if(**str == '.') { // read fraction of increment
1226                 char *start = ++(*str);
1227                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228                 temp2 *= 1000;
1229                 while(start++ < *str) temp2 /= 10;
1230                 *inc += temp2;
1231             }
1232         } else *inc = 0;
1233         *moves = 0; *tc = temp * 1000; *incType = type;
1234         return 0;
1235     }
1236
1237     (*str)++; /* classical time control */
1238     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1239
1240     if(result == 0) {
1241         *moves = temp;
1242         *tc    = temp2 * 1000;
1243         *inc   = 0;
1244         *incType = type;
1245     }
1246     return result;
1247 }
1248
1249 int
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 {   /* [HGM] get time to add from the multi-session time-control string */
1252     int incType, moves=1; /* kludge to force reading of first session */
1253     long time, increment;
1254     char *s = tcString;
1255
1256     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257     do {
1258         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260         if(movenr == -1) return time;    /* last move before new session     */
1261         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263         if(!moves) return increment;     /* current session is incremental   */
1264         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265     } while(movenr >= -1);               /* try again for next session       */
1266
1267     return 0; // no new time quota on this move
1268 }
1269
1270 int
1271 ParseTimeControl (char *tc, float ti, int mps)
1272 {
1273   long tc1;
1274   long tc2;
1275   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1276   int min, sec=0;
1277
1278   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1281   if(ti > 0) {
1282
1283     if(mps)
1284       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285     else 
1286       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1287   } else {
1288     if(mps)
1289       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290     else 
1291       snprintf(buf, MSG_SIZ, ":%s", mytc);
1292   }
1293   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294   
1295   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1296     return FALSE;
1297   }
1298
1299   if( *tc == '/' ) {
1300     /* Parse second time control */
1301     tc++;
1302
1303     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304       return FALSE;
1305     }
1306
1307     if( tc2 == 0 ) {
1308       return FALSE;
1309     }
1310
1311     timeControl_2 = tc2 * 1000;
1312   }
1313   else {
1314     timeControl_2 = 0;
1315   }
1316
1317   if( tc1 == 0 ) {
1318     return FALSE;
1319   }
1320
1321   timeControl = tc1 * 1000;
1322
1323   if (ti >= 0) {
1324     timeIncrement = ti * 1000;  /* convert to ms */
1325     movesPerSession = 0;
1326   } else {
1327     timeIncrement = 0;
1328     movesPerSession = mps;
1329   }
1330   return TRUE;
1331 }
1332
1333 void
1334 InitBackEnd2 ()
1335 {
1336     if (appData.debugMode) {
1337         fprintf(debugFP, "%s\n", programVersion);
1338     }
1339     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340
1341     set_cont_sequence(appData.wrapContSeq);
1342     if (appData.matchGames > 0) {
1343         appData.matchMode = TRUE;
1344     } else if (appData.matchMode) {
1345         appData.matchGames = 1;
1346     }
1347     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348         appData.matchGames = appData.sameColorGames;
1349     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1352     }
1353     Reset(TRUE, FALSE);
1354     if (appData.noChessProgram || first.protocolVersion == 1) {
1355       InitBackEnd3();
1356     } else {
1357       /* kludge: allow timeout for initial "feature" commands */
1358       FreezeUI();
1359       DisplayMessage("", _("Starting chess program"));
1360       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1361     }
1362 }
1363
1364 int
1365 CalculateIndex (int index, int gameNr)
1366 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367     int res;
1368     if(index > 0) return index; // fixed nmber
1369     if(index == 0) return 1;
1370     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1372     return res;
1373 }
1374
1375 int
1376 LoadGameOrPosition (int gameNr)
1377 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378     if (*appData.loadGameFile != NULLCHAR) {
1379         if (!LoadGameFromFile(appData.loadGameFile,
1380                 CalculateIndex(appData.loadGameIndex, gameNr),
1381                               appData.loadGameFile, FALSE)) {
1382             DisplayFatalError(_("Bad game file"), 0, 1);
1383             return 0;
1384         }
1385     } else if (*appData.loadPositionFile != NULLCHAR) {
1386         if (!LoadPositionFromFile(appData.loadPositionFile,
1387                 CalculateIndex(appData.loadPositionIndex, gameNr),
1388                                   appData.loadPositionFile)) {
1389             DisplayFatalError(_("Bad position file"), 0, 1);
1390             return 0;
1391         }
1392     }
1393     return 1;
1394 }
1395
1396 void
1397 ReserveGame (int gameNr, char resChar)
1398 {
1399     FILE *tf = fopen(appData.tourneyFile, "r+");
1400     char *p, *q, c, buf[MSG_SIZ];
1401     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402     safeStrCpy(buf, lastMsg, MSG_SIZ);
1403     DisplayMessage(_("Pick new game"), "");
1404     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405     ParseArgsFromFile(tf);
1406     p = q = appData.results;
1407     if(appData.debugMode) {
1408       char *r = appData.participants;
1409       fprintf(debugFP, "results = '%s'\n", p);
1410       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411       fprintf(debugFP, "\n");
1412     }
1413     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414     nextGame = q - p;
1415     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416     safeStrCpy(q, p, strlen(p) + 2);
1417     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1421         q[nextGame] = '*';
1422     }
1423     fseek(tf, -(strlen(p)+4), SEEK_END);
1424     c = fgetc(tf);
1425     if(c != '"') // depending on DOS or Unix line endings we can be one off
1426          fseek(tf, -(strlen(p)+2), SEEK_END);
1427     else fseek(tf, -(strlen(p)+3), SEEK_END);
1428     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429     DisplayMessage(buf, "");
1430     free(p); appData.results = q;
1431     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433       int round = appData.defaultMatchGames * appData.tourneyType;
1434       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1435          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436         UnloadEngine(&first);  // next game belongs to other pairing;
1437         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438     }
1439     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1440 }
1441
1442 void
1443 MatchEvent (int mode)
1444 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445         int dummy;
1446         if(matchMode) { // already in match mode: switch it off
1447             abortMatch = TRUE;
1448             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1449             return;
1450         }
1451 //      if(gameMode != BeginningOfGame) {
1452 //          DisplayError(_("You can only start a match from the initial position."), 0);
1453 //          return;
1454 //      }
1455         abortMatch = FALSE;
1456         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457         /* Set up machine vs. machine match */
1458         nextGame = 0;
1459         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460         if(appData.tourneyFile[0]) {
1461             ReserveGame(-1, 0);
1462             if(nextGame > appData.matchGames) {
1463                 char buf[MSG_SIZ];
1464                 if(strchr(appData.results, '*') == NULL) {
1465                     FILE *f;
1466                     appData.tourneyCycles++;
1467                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468                         fclose(f);
1469                         NextTourneyGame(-1, &dummy);
1470                         ReserveGame(-1, 0);
1471                         if(nextGame <= appData.matchGames) {
1472                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473                             matchMode = mode;
1474                             ScheduleDelayedEvent(NextMatchGame, 10000);
1475                             return;
1476                         }
1477                     }
1478                 }
1479                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480                 DisplayError(buf, 0);
1481                 appData.tourneyFile[0] = 0;
1482                 return;
1483             }
1484         } else
1485         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1486             DisplayFatalError(_("Can't have a match with no chess programs"),
1487                               0, 2);
1488             return;
1489         }
1490         matchMode = mode;
1491         matchGame = roundNr = 1;
1492         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1493         NextMatchGame();
1494 }
1495
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1497
1498 void
1499 InitBackEnd3 P((void))
1500 {
1501     GameMode initialMode;
1502     char buf[MSG_SIZ];
1503     int err, len;
1504
1505     InitChessProgram(&first, startedFromSetupPosition);
1506
1507     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1508         free(programVersion);
1509         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1512     }
1513
1514     if (appData.icsActive) {
1515 #ifdef WIN32
1516         /* [DM] Make a console window if needed [HGM] merged ifs */
1517         ConsoleCreate();
1518 #endif
1519         err = establish();
1520         if (err != 0)
1521           {
1522             if (*appData.icsCommPort != NULLCHAR)
1523               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524                              appData.icsCommPort);
1525             else
1526               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527                         appData.icsHost, appData.icsPort);
1528
1529             if( (len >= MSG_SIZ) && appData.debugMode )
1530               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531
1532             DisplayFatalError(buf, err, 1);
1533             return;
1534         }
1535         SetICSMode();
1536         telnetISR =
1537           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538         fromUserISR =
1539           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542     } else if (appData.noChessProgram) {
1543         SetNCPMode();
1544     } else {
1545         SetGNUMode();
1546     }
1547
1548     if (*appData.cmailGameName != NULLCHAR) {
1549         SetCmailMode();
1550         OpenLoopback(&cmailPR);
1551         cmailISR =
1552           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1553     }
1554
1555     ThawUI();
1556     DisplayMessage("", "");
1557     if (StrCaseCmp(appData.initialMode, "") == 0) {
1558       initialMode = BeginningOfGame;
1559       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1563         ModeHighlight();
1564       }
1565     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566       initialMode = TwoMachinesPlay;
1567     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568       initialMode = AnalyzeFile;
1569     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570       initialMode = AnalyzeMode;
1571     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572       initialMode = MachinePlaysWhite;
1573     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574       initialMode = MachinePlaysBlack;
1575     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576       initialMode = EditGame;
1577     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578       initialMode = EditPosition;
1579     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580       initialMode = Training;
1581     } else {
1582       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583       if( (len >= MSG_SIZ) && appData.debugMode )
1584         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585
1586       DisplayFatalError(buf, 0, 2);
1587       return;
1588     }
1589
1590     if (appData.matchMode) {
1591         if(appData.tourneyFile[0]) { // start tourney from command line
1592             FILE *f;
1593             if(f = fopen(appData.tourneyFile, "r")) {
1594                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595                 fclose(f);
1596                 appData.clockMode = TRUE;
1597                 SetGNUMode();
1598             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1599         }
1600         MatchEvent(TRUE);
1601     } else if (*appData.cmailGameName != NULLCHAR) {
1602         /* Set up cmail mode */
1603         ReloadCmailMsgEvent(TRUE);
1604     } else {
1605         /* Set up other modes */
1606         if (initialMode == AnalyzeFile) {
1607           if (*appData.loadGameFile == NULLCHAR) {
1608             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1609             return;
1610           }
1611         }
1612         if (*appData.loadGameFile != NULLCHAR) {
1613             (void) LoadGameFromFile(appData.loadGameFile,
1614                                     appData.loadGameIndex,
1615                                     appData.loadGameFile, TRUE);
1616         } else if (*appData.loadPositionFile != NULLCHAR) {
1617             (void) LoadPositionFromFile(appData.loadPositionFile,
1618                                         appData.loadPositionIndex,
1619                                         appData.loadPositionFile);
1620             /* [HGM] try to make self-starting even after FEN load */
1621             /* to allow automatic setup of fairy variants with wtm */
1622             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623                 gameMode = BeginningOfGame;
1624                 setboardSpoiledMachineBlack = 1;
1625             }
1626             /* [HGM] loadPos: make that every new game uses the setup */
1627             /* from file as long as we do not switch variant          */
1628             if(!blackPlaysFirst) {
1629                 startedFromPositionFile = TRUE;
1630                 CopyBoard(filePosition, boards[0]);
1631             }
1632         }
1633         if (initialMode == AnalyzeMode) {
1634           if (appData.noChessProgram) {
1635             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1636             return;
1637           }
1638           if (appData.icsActive) {
1639             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1640             return;
1641           }
1642           AnalyzeModeEvent();
1643         } else if (initialMode == AnalyzeFile) {
1644           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645           ShowThinkingEvent();
1646           AnalyzeFileEvent();
1647           AnalysisPeriodicEvent(1);
1648         } else if (initialMode == MachinePlaysWhite) {
1649           if (appData.noChessProgram) {
1650             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1651                               0, 2);
1652             return;
1653           }
1654           if (appData.icsActive) {
1655             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1656                               0, 2);
1657             return;
1658           }
1659           MachineWhiteEvent();
1660         } else if (initialMode == MachinePlaysBlack) {
1661           if (appData.noChessProgram) {
1662             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1663                               0, 2);
1664             return;
1665           }
1666           if (appData.icsActive) {
1667             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1668                               0, 2);
1669             return;
1670           }
1671           MachineBlackEvent();
1672         } else if (initialMode == TwoMachinesPlay) {
1673           if (appData.noChessProgram) {
1674             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1675                               0, 2);
1676             return;
1677           }
1678           if (appData.icsActive) {
1679             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1680                               0, 2);
1681             return;
1682           }
1683           TwoMachinesEvent();
1684         } else if (initialMode == EditGame) {
1685           EditGameEvent();
1686         } else if (initialMode == EditPosition) {
1687           EditPositionEvent();
1688         } else if (initialMode == Training) {
1689           if (*appData.loadGameFile == NULLCHAR) {
1690             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1691             return;
1692           }
1693           TrainingEvent();
1694         }
1695     }
1696 }
1697
1698 void
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 {
1701     DisplayBook(current+1);
1702
1703     MoveHistorySet( movelist, first, last, current, pvInfoList );
1704
1705     EvalGraphSet( first, last, current, pvInfoList );
1706
1707     MakeEngineOutputTitle();
1708 }
1709
1710 /*
1711  * Establish will establish a contact to a remote host.port.
1712  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713  *  used to talk to the host.
1714  * Returns 0 if okay, error code if not.
1715  */
1716 int
1717 establish ()
1718 {
1719     char buf[MSG_SIZ];
1720
1721     if (*appData.icsCommPort != NULLCHAR) {
1722         /* Talk to the host through a serial comm port */
1723         return OpenCommPort(appData.icsCommPort, &icsPR);
1724
1725     } else if (*appData.gateway != NULLCHAR) {
1726         if (*appData.remoteShell == NULLCHAR) {
1727             /* Use the rcmd protocol to run telnet program on a gateway host */
1728             snprintf(buf, sizeof(buf), "%s %s %s",
1729                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1730             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1731
1732         } else {
1733             /* Use the rsh program to run telnet program on a gateway host */
1734             if (*appData.remoteUser == NULLCHAR) {
1735                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736                         appData.gateway, appData.telnetProgram,
1737                         appData.icsHost, appData.icsPort);
1738             } else {
1739                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740                         appData.remoteShell, appData.gateway,
1741                         appData.remoteUser, appData.telnetProgram,
1742                         appData.icsHost, appData.icsPort);
1743             }
1744             return StartChildProcess(buf, "", &icsPR);
1745
1746         }
1747     } else if (appData.useTelnet) {
1748         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1749
1750     } else {
1751         /* TCP socket interface differs somewhat between
1752            Unix and NT; handle details in the front end.
1753            */
1754         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1755     }
1756 }
1757
1758 void
1759 EscapeExpand (char *p, char *q)
1760 {       // [HGM] initstring: routine to shape up string arguments
1761         while(*p++ = *q++) if(p[-1] == '\\')
1762             switch(*q++) {
1763                 case 'n': p[-1] = '\n'; break;
1764                 case 'r': p[-1] = '\r'; break;
1765                 case 't': p[-1] = '\t'; break;
1766                 case '\\': p[-1] = '\\'; break;
1767                 case 0: *p = 0; return;
1768                 default: p[-1] = q[-1]; break;
1769             }
1770 }
1771
1772 void
1773 show_bytes (FILE *fp, char *buf, int count)
1774 {
1775     while (count--) {
1776         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777             fprintf(fp, "\\%03o", *buf & 0xff);
1778         } else {
1779             putc(*buf, fp);
1780         }
1781         buf++;
1782     }
1783     fflush(fp);
1784 }
1785
1786 /* Returns an errno value */
1787 int
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 {
1790     char buf[8192], *p, *q, *buflim;
1791     int left, newcount, outcount;
1792
1793     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794         *appData.gateway != NULLCHAR) {
1795         if (appData.debugMode) {
1796             fprintf(debugFP, ">ICS: ");
1797             show_bytes(debugFP, message, count);
1798             fprintf(debugFP, "\n");
1799         }
1800         return OutputToProcess(pr, message, count, outError);
1801     }
1802
1803     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804     p = message;
1805     q = buf;
1806     left = count;
1807     newcount = 0;
1808     while (left) {
1809         if (q >= buflim) {
1810             if (appData.debugMode) {
1811                 fprintf(debugFP, ">ICS: ");
1812                 show_bytes(debugFP, buf, newcount);
1813                 fprintf(debugFP, "\n");
1814             }
1815             outcount = OutputToProcess(pr, buf, newcount, outError);
1816             if (outcount < newcount) return -1; /* to be sure */
1817             q = buf;
1818             newcount = 0;
1819         }
1820         if (*p == '\n') {
1821             *q++ = '\r';
1822             newcount++;
1823         } else if (((unsigned char) *p) == TN_IAC) {
1824             *q++ = (char) TN_IAC;
1825             newcount ++;
1826         }
1827         *q++ = *p++;
1828         newcount++;
1829         left--;
1830     }
1831     if (appData.debugMode) {
1832         fprintf(debugFP, ">ICS: ");
1833         show_bytes(debugFP, buf, newcount);
1834         fprintf(debugFP, "\n");
1835     }
1836     outcount = OutputToProcess(pr, buf, newcount, outError);
1837     if (outcount < newcount) return -1; /* to be sure */
1838     return count;
1839 }
1840
1841 void
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 {
1844     int outError, outCount;
1845     static int gotEof = 0;
1846
1847     /* Pass data read from player on to ICS */
1848     if (count > 0) {
1849         gotEof = 0;
1850         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851         if (outCount < count) {
1852             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853         }
1854     } else if (count < 0) {
1855         RemoveInputSource(isr);
1856         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857     } else if (gotEof++ > 0) {
1858         RemoveInputSource(isr);
1859         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1860     }
1861 }
1862
1863 void
1864 KeepAlive ()
1865 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868     SendToICS("date\n");
1869     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1870 }
1871
1872 /* added routine for printf style output to ics */
1873 void
1874 ics_printf (char *format, ...)
1875 {
1876     char buffer[MSG_SIZ];
1877     va_list args;
1878
1879     va_start(args, format);
1880     vsnprintf(buffer, sizeof(buffer), format, args);
1881     buffer[sizeof(buffer)-1] = '\0';
1882     SendToICS(buffer);
1883     va_end(args);
1884 }
1885
1886 void
1887 SendToICS (char *s)
1888 {
1889     int count, outCount, outError;
1890
1891     if (icsPR == NoProc) return;
1892
1893     count = strlen(s);
1894     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895     if (outCount < count) {
1896         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897     }
1898 }
1899
1900 /* This is used for sending logon scripts to the ICS. Sending
1901    without a delay causes problems when using timestamp on ICC
1902    (at least on my machine). */
1903 void
1904 SendToICSDelayed (char *s, long msdelay)
1905 {
1906     int count, outCount, outError;
1907
1908     if (icsPR == NoProc) return;
1909
1910     count = strlen(s);
1911     if (appData.debugMode) {
1912         fprintf(debugFP, ">ICS: ");
1913         show_bytes(debugFP, s, count);
1914         fprintf(debugFP, "\n");
1915     }
1916     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917                                       msdelay);
1918     if (outCount < count) {
1919         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1920     }
1921 }
1922
1923
1924 /* Remove all highlighting escape sequences in s
1925    Also deletes any suffix starting with '('
1926    */
1927 char *
1928 StripHighlightAndTitle (char *s)
1929 {
1930     static char retbuf[MSG_SIZ];
1931     char *p = retbuf;
1932
1933     while (*s != NULLCHAR) {
1934         while (*s == '\033') {
1935             while (*s != NULLCHAR && !isalpha(*s)) s++;
1936             if (*s != NULLCHAR) s++;
1937         }
1938         while (*s != NULLCHAR && *s != '\033') {
1939             if (*s == '(' || *s == '[') {
1940                 *p = NULLCHAR;
1941                 return retbuf;
1942             }
1943             *p++ = *s++;
1944         }
1945     }
1946     *p = NULLCHAR;
1947     return retbuf;
1948 }
1949
1950 /* Remove all highlighting escape sequences in s */
1951 char *
1952 StripHighlight (char *s)
1953 {
1954     static char retbuf[MSG_SIZ];
1955     char *p = retbuf;
1956
1957     while (*s != NULLCHAR) {
1958         while (*s == '\033') {
1959             while (*s != NULLCHAR && !isalpha(*s)) s++;
1960             if (*s != NULLCHAR) s++;
1961         }
1962         while (*s != NULLCHAR && *s != '\033') {
1963             *p++ = *s++;
1964         }
1965     }
1966     *p = NULLCHAR;
1967     return retbuf;
1968 }
1969
1970 char *variantNames[] = VARIANT_NAMES;
1971 char *
1972 VariantName (VariantClass v)
1973 {
1974     return variantNames[v];
1975 }
1976
1977
1978 /* Identify a variant from the strings the chess servers use or the
1979    PGN Variant tag names we use. */
1980 VariantClass
1981 StringToVariant (char *e)
1982 {
1983     char *p;
1984     int wnum = -1;
1985     VariantClass v = VariantNormal;
1986     int i, found = FALSE;
1987     char buf[MSG_SIZ];
1988     int len;
1989
1990     if (!e) return v;
1991
1992     /* [HGM] skip over optional board-size prefixes */
1993     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995         while( *e++ != '_');
1996     }
1997
1998     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1999         v = VariantNormal;
2000         found = TRUE;
2001     } else
2002     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003       if (StrCaseStr(e, variantNames[i])) {
2004         v = (VariantClass) i;
2005         found = TRUE;
2006         break;
2007       }
2008     }
2009
2010     if (!found) {
2011       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012           || StrCaseStr(e, "wild/fr")
2013           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014         v = VariantFischeRandom;
2015       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016                  (i = 1, p = StrCaseStr(e, "w"))) {
2017         p += i;
2018         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019         if (isdigit(*p)) {
2020           wnum = atoi(p);
2021         } else {
2022           wnum = -1;
2023         }
2024         switch (wnum) {
2025         case 0: /* FICS only, actually */
2026         case 1:
2027           /* Castling legal even if K starts on d-file */
2028           v = VariantWildCastle;
2029           break;
2030         case 2:
2031         case 3:
2032         case 4:
2033           /* Castling illegal even if K & R happen to start in
2034              normal positions. */
2035           v = VariantNoCastle;
2036           break;
2037         case 5:
2038         case 7:
2039         case 8:
2040         case 10:
2041         case 11:
2042         case 12:
2043         case 13:
2044         case 14:
2045         case 15:
2046         case 18:
2047         case 19:
2048           /* Castling legal iff K & R start in normal positions */
2049           v = VariantNormal;
2050           break;
2051         case 6:
2052         case 20:
2053         case 21:
2054           /* Special wilds for position setup; unclear what to do here */
2055           v = VariantLoadable;
2056           break;
2057         case 9:
2058           /* Bizarre ICC game */
2059           v = VariantTwoKings;
2060           break;
2061         case 16:
2062           v = VariantKriegspiel;
2063           break;
2064         case 17:
2065           v = VariantLosers;
2066           break;
2067         case 22:
2068           v = VariantFischeRandom;
2069           break;
2070         case 23:
2071           v = VariantCrazyhouse;
2072           break;
2073         case 24:
2074           v = VariantBughouse;
2075           break;
2076         case 25:
2077           v = Variant3Check;
2078           break;
2079         case 26:
2080           /* Not quite the same as FICS suicide! */
2081           v = VariantGiveaway;
2082           break;
2083         case 27:
2084           v = VariantAtomic;
2085           break;
2086         case 28:
2087           v = VariantShatranj;
2088           break;
2089
2090         /* Temporary names for future ICC types.  The name *will* change in
2091            the next xboard/WinBoard release after ICC defines it. */
2092         case 29:
2093           v = Variant29;
2094           break;
2095         case 30:
2096           v = Variant30;
2097           break;
2098         case 31:
2099           v = Variant31;
2100           break;
2101         case 32:
2102           v = Variant32;
2103           break;
2104         case 33:
2105           v = Variant33;
2106           break;
2107         case 34:
2108           v = Variant34;
2109           break;
2110         case 35:
2111           v = Variant35;
2112           break;
2113         case 36:
2114           v = Variant36;
2115           break;
2116         case 37:
2117           v = VariantShogi;
2118           break;
2119         case 38:
2120           v = VariantXiangqi;
2121           break;
2122         case 39:
2123           v = VariantCourier;
2124           break;
2125         case 40:
2126           v = VariantGothic;
2127           break;
2128         case 41:
2129           v = VariantCapablanca;
2130           break;
2131         case 42:
2132           v = VariantKnightmate;
2133           break;
2134         case 43:
2135           v = VariantFairy;
2136           break;
2137         case 44:
2138           v = VariantCylinder;
2139           break;
2140         case 45:
2141           v = VariantFalcon;
2142           break;
2143         case 46:
2144           v = VariantCapaRandom;
2145           break;
2146         case 47:
2147           v = VariantBerolina;
2148           break;
2149         case 48:
2150           v = VariantJanus;
2151           break;
2152         case 49:
2153           v = VariantSuper;
2154           break;
2155         case 50:
2156           v = VariantGreat;
2157           break;
2158         case -1:
2159           /* Found "wild" or "w" in the string but no number;
2160              must assume it's normal chess. */
2161           v = VariantNormal;
2162           break;
2163         default:
2164           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165           if( (len >= MSG_SIZ) && appData.debugMode )
2166             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167
2168           DisplayError(buf, 0);
2169           v = VariantUnknown;
2170           break;
2171         }
2172       }
2173     }
2174     if (appData.debugMode) {
2175       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176               e, wnum, VariantName(v));
2177     }
2178     return v;
2179 }
2180
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2183
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185    advance *index beyond it, and set leftover_start to the new value of
2186    *index; else return FALSE.  If pattern contains the character '*', it
2187    matches any sequence of characters not containing '\r', '\n', or the
2188    character following the '*' (if any), and the matched sequence(s) are
2189    copied into star_match.
2190    */
2191 int
2192 looking_at ( char *buf, int *index, char *pattern)
2193 {
2194     char *bufp = &buf[*index], *patternp = pattern;
2195     int star_count = 0;
2196     char *matchp = star_match[0];
2197
2198     for (;;) {
2199         if (*patternp == NULLCHAR) {
2200             *index = leftover_start = bufp - buf;
2201             *matchp = NULLCHAR;
2202             return TRUE;
2203         }
2204         if (*bufp == NULLCHAR) return FALSE;
2205         if (*patternp == '*') {
2206             if (*bufp == *(patternp + 1)) {
2207                 *matchp = NULLCHAR;
2208                 matchp = star_match[++star_count];
2209                 patternp += 2;
2210                 bufp++;
2211                 continue;
2212             } else if (*bufp == '\n' || *bufp == '\r') {
2213                 patternp++;
2214                 if (*patternp == NULLCHAR)
2215                   continue;
2216                 else
2217                   return FALSE;
2218             } else {
2219                 *matchp++ = *bufp++;
2220                 continue;
2221             }
2222         }
2223         if (*patternp != *bufp) return FALSE;
2224         patternp++;
2225         bufp++;
2226     }
2227 }
2228
2229 void
2230 SendToPlayer (char *data, int length)
2231 {
2232     int error, outCount;
2233     outCount = OutputToProcess(NoProc, data, length, &error);
2234     if (outCount < length) {
2235         DisplayFatalError(_("Error writing to display"), error, 1);
2236     }
2237 }
2238
2239 void
2240 PackHolding (char packed[], char *holding)
2241 {
2242     char *p = holding;
2243     char *q = packed;
2244     int runlength = 0;
2245     int curr = 9999;
2246     do {
2247         if (*p == curr) {
2248             runlength++;
2249         } else {
2250             switch (runlength) {
2251               case 0:
2252                 break;
2253               case 1:
2254                 *q++ = curr;
2255                 break;
2256               case 2:
2257                 *q++ = curr;
2258                 *q++ = curr;
2259                 break;
2260               default:
2261                 sprintf(q, "%d", runlength);
2262                 while (*q) q++;
2263                 *q++ = curr;
2264                 break;
2265             }
2266             runlength = 1;
2267             curr = *p;
2268         }
2269     } while (*p++);
2270     *q = NULLCHAR;
2271 }
2272
2273 /* Telnet protocol requests from the front end */
2274 void
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2276 {
2277     unsigned char msg[3];
2278     int outCount, outError;
2279
2280     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281
2282     if (appData.debugMode) {
2283         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2284         switch (ddww) {
2285           case TN_DO:
2286             ddwwStr = "DO";
2287             break;
2288           case TN_DONT:
2289             ddwwStr = "DONT";
2290             break;
2291           case TN_WILL:
2292             ddwwStr = "WILL";
2293             break;
2294           case TN_WONT:
2295             ddwwStr = "WONT";
2296             break;
2297           default:
2298             ddwwStr = buf1;
2299             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2300             break;
2301         }
2302         switch (option) {
2303           case TN_ECHO:
2304             optionStr = "ECHO";
2305             break;
2306           default:
2307             optionStr = buf2;
2308             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2309             break;
2310         }
2311         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2312     }
2313     msg[0] = TN_IAC;
2314     msg[1] = ddww;
2315     msg[2] = option;
2316     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317     if (outCount < 3) {
2318         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319     }
2320 }
2321
2322 void
2323 DoEcho ()
2324 {
2325     if (!appData.icsActive) return;
2326     TelnetRequest(TN_DO, TN_ECHO);
2327 }
2328
2329 void
2330 DontEcho ()
2331 {
2332     if (!appData.icsActive) return;
2333     TelnetRequest(TN_DONT, TN_ECHO);
2334 }
2335
2336 void
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 {
2339     /* put the holdings sent to us by the server on the board holdings area */
2340     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2341     char p;
2342     ChessSquare piece;
2343
2344     if(gameInfo.holdingsWidth < 2)  return;
2345     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346         return; // prevent overwriting by pre-board holdings
2347
2348     if( (int)lowestPiece >= BlackPawn ) {
2349         holdingsColumn = 0;
2350         countsColumn = 1;
2351         holdingsStartRow = BOARD_HEIGHT-1;
2352         direction = -1;
2353     } else {
2354         holdingsColumn = BOARD_WIDTH-1;
2355         countsColumn = BOARD_WIDTH-2;
2356         holdingsStartRow = 0;
2357         direction = 1;
2358     }
2359
2360     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361         board[i][holdingsColumn] = EmptySquare;
2362         board[i][countsColumn]   = (ChessSquare) 0;
2363     }
2364     while( (p=*holdings++) != NULLCHAR ) {
2365         piece = CharToPiece( ToUpper(p) );
2366         if(piece == EmptySquare) continue;
2367         /*j = (int) piece - (int) WhitePawn;*/
2368         j = PieceToNumber(piece);
2369         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370         if(j < 0) continue;               /* should not happen */
2371         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373         board[holdingsStartRow+j*direction][countsColumn]++;
2374     }
2375 }
2376
2377
2378 void
2379 VariantSwitch (Board board, VariantClass newVariant)
2380 {
2381    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382    static Board oldBoard;
2383
2384    startedFromPositionFile = FALSE;
2385    if(gameInfo.variant == newVariant) return;
2386
2387    /* [HGM] This routine is called each time an assignment is made to
2388     * gameInfo.variant during a game, to make sure the board sizes
2389     * are set to match the new variant. If that means adding or deleting
2390     * holdings, we shift the playing board accordingly
2391     * This kludge is needed because in ICS observe mode, we get boards
2392     * of an ongoing game without knowing the variant, and learn about the
2393     * latter only later. This can be because of the move list we requested,
2394     * in which case the game history is refilled from the beginning anyway,
2395     * but also when receiving holdings of a crazyhouse game. In the latter
2396     * case we want to add those holdings to the already received position.
2397     */
2398
2399
2400    if (appData.debugMode) {
2401      fprintf(debugFP, "Switch board from %s to %s\n",
2402              VariantName(gameInfo.variant), VariantName(newVariant));
2403      setbuf(debugFP, NULL);
2404    }
2405    shuffleOpenings = 0;       /* [HGM] shuffle */
2406    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2407    switch(newVariant)
2408      {
2409      case VariantShogi:
2410        newWidth = 9;  newHeight = 9;
2411        gameInfo.holdingsSize = 7;
2412      case VariantBughouse:
2413      case VariantCrazyhouse:
2414        newHoldingsWidth = 2; break;
2415      case VariantGreat:
2416        newWidth = 10;
2417      case VariantSuper:
2418        newHoldingsWidth = 2;
2419        gameInfo.holdingsSize = 8;
2420        break;
2421      case VariantGothic:
2422      case VariantCapablanca:
2423      case VariantCapaRandom:
2424        newWidth = 10;
2425      default:
2426        newHoldingsWidth = gameInfo.holdingsSize = 0;
2427      };
2428
2429    if(newWidth  != gameInfo.boardWidth  ||
2430       newHeight != gameInfo.boardHeight ||
2431       newHoldingsWidth != gameInfo.holdingsWidth ) {
2432
2433      /* shift position to new playing area, if needed */
2434      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435        for(i=0; i<BOARD_HEIGHT; i++)
2436          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438              board[i][j];
2439        for(i=0; i<newHeight; i++) {
2440          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442        }
2443      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444        for(i=0; i<BOARD_HEIGHT; i++)
2445          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2447              board[i][j];
2448      }
2449      gameInfo.boardWidth  = newWidth;
2450      gameInfo.boardHeight = newHeight;
2451      gameInfo.holdingsWidth = newHoldingsWidth;
2452      gameInfo.variant = newVariant;
2453      InitDrawingSizes(-2, 0);
2454    } else gameInfo.variant = newVariant;
2455    CopyBoard(oldBoard, board);   // remember correctly formatted board
2456      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2457    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2458 }
2459
2460 static int loggedOn = FALSE;
2461
2462 /*-- Game start info cache: --*/
2463 int gs_gamenum;
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\   ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2471
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2474
2475 // [HGM] seekgraph
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2479 #define SQUARE 0x80
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2488
2489 void
2490 PlotSeekAd (int i)
2491 {
2492         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494         if(r < minRating+100 && r >=0 ) r = minRating+100;
2495         if(r > maxRating) r = maxRating;
2496         if(tc < 1.) tc = 1.;
2497         if(tc > 95.) tc = 95.;
2498         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499         y = ((double)r - minRating)/(maxRating - minRating)
2500             * (h-vMargin-squareSize/8-1) + vMargin;
2501         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502         if(strstr(seekAdList[i], " u ")) color = 1;
2503         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504            !strstr(seekAdList[i], "bullet") &&
2505            !strstr(seekAdList[i], "blitz") &&
2506            !strstr(seekAdList[i], "standard") ) color = 2;
2507         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2509 }
2510
2511 void
2512 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2513 {
2514         char buf[MSG_SIZ], *ext = "";
2515         VariantClass v = StringToVariant(type);
2516         if(strstr(type, "wild")) {
2517             ext = type + 4; // append wild number
2518             if(v == VariantFischeRandom) type = "chess960"; else
2519             if(v == VariantLoadable) type = "setup"; else
2520             type = VariantName(v);
2521         }
2522         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528             seekNrList[nrOfSeekAds] = nr;
2529             zList[nrOfSeekAds] = 0;
2530             seekAdList[nrOfSeekAds++] = StrSave(buf);
2531             if(plot) PlotSeekAd(nrOfSeekAds-1);
2532         }
2533 }
2534
2535 void
2536 EraseSeekDot (int i)
2537 {
2538     int x = xList[i], y = yList[i], d=squareSize/4, k;
2539     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541     // now replot every dot that overlapped
2542     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543         int xx = xList[k], yy = yList[k];
2544         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545             DrawSeekDot(xx, yy, colorList[k]);
2546     }
2547 }
2548
2549 void
2550 RemoveSeekAd (int nr)
2551 {
2552         int i;
2553         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2554             EraseSeekDot(i);
2555             if(seekAdList[i]) free(seekAdList[i]);
2556             seekAdList[i] = seekAdList[--nrOfSeekAds];
2557             seekNrList[i] = seekNrList[nrOfSeekAds];
2558             ratingList[i] = ratingList[nrOfSeekAds];
2559             colorList[i]  = colorList[nrOfSeekAds];
2560             tcList[i] = tcList[nrOfSeekAds];
2561             xList[i]  = xList[nrOfSeekAds];
2562             yList[i]  = yList[nrOfSeekAds];
2563             zList[i]  = zList[nrOfSeekAds];
2564             seekAdList[nrOfSeekAds] = NULL;
2565             break;
2566         }
2567 }
2568
2569 Boolean
2570 MatchSoughtLine (char *line)
2571 {
2572     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573     int nr, base, inc, u=0; char dummy;
2574
2575     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2577        (u=1) &&
2578        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2580         // match: compact and save the line
2581         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582         return TRUE;
2583     }
2584     return FALSE;
2585 }
2586
2587 int
2588 DrawSeekGraph ()
2589 {
2590     int i;
2591     if(!seekGraphUp) return FALSE;
2592     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2594
2595     DrawSeekBackground(0, 0, w, h);
2596     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2600         yy = h-1-yy;
2601         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2602         if(i%500 == 0) {
2603             char buf[MSG_SIZ];
2604             snprintf(buf, MSG_SIZ, "%d", i);
2605             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2606         }
2607     }
2608     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609     for(i=1; i<100; i+=(i<10?1:5)) {
2610         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2613             char buf[MSG_SIZ];
2614             snprintf(buf, MSG_SIZ, "%d", i);
2615             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2616         }
2617     }
2618     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2619     return TRUE;
2620 }
2621
2622 int
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2624 {
2625     static int lastDown = 0, displayed = 0, lastSecond;
2626     if(y < 0) return FALSE;
2627     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629         if(!seekGraphUp) return FALSE;
2630         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631         DrawPosition(TRUE, NULL);
2632         return TRUE;
2633     }
2634     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635         if(click == Release || moving) return FALSE;
2636         nrOfSeekAds = 0;
2637         soughtPending = TRUE;
2638         SendToICS(ics_prefix);
2639         SendToICS("sought\n"); // should this be "sought all"?
2640     } else { // issue challenge based on clicked ad
2641         int dist = 10000; int i, closest = 0, second = 0;
2642         for(i=0; i<nrOfSeekAds; i++) {
2643             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2644             if(d < dist) { dist = d; closest = i; }
2645             second += (d - zList[i] < 120); // count in-range ads
2646             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2647         }
2648         if(dist < 120) {
2649             char buf[MSG_SIZ];
2650             second = (second > 1);
2651             if(displayed != closest || second != lastSecond) {
2652                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653                 lastSecond = second; displayed = closest;
2654             }
2655             if(click == Press) {
2656                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2657                 lastDown = closest;
2658                 return TRUE;
2659             } // on press 'hit', only show info
2660             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662             SendToICS(ics_prefix);
2663             SendToICS(buf);
2664             return TRUE; // let incoming board of started game pop down the graph
2665         } else if(click == Release) { // release 'miss' is ignored
2666             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667             if(moving == 2) { // right up-click
2668                 nrOfSeekAds = 0; // refresh graph
2669                 soughtPending = TRUE;
2670                 SendToICS(ics_prefix);
2671                 SendToICS("sought\n"); // should this be "sought all"?
2672             }
2673             return TRUE;
2674         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675         // press miss or release hit 'pop down' seek graph
2676         seekGraphUp = FALSE;
2677         DrawPosition(TRUE, NULL);
2678     }
2679     return TRUE;
2680 }
2681
2682 void
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2684 {
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2694
2695     static int started = STARTED_NONE;
2696     static char parse[20000];
2697     static int parse_pos = 0;
2698     static char buf[BUF_SIZE + 1];
2699     static int firstTime = TRUE, intfSet = FALSE;
2700     static ColorClass prevColor = ColorNormal;
2701     static int savingComment = FALSE;
2702     static int cmatch = 0; // continuation sequence match
2703     char *bp;
2704     char str[MSG_SIZ];
2705     int i, oldi;
2706     int buf_len;
2707     int next_out;
2708     int tkind;
2709     int backup;    /* [DM] For zippy color lines */
2710     char *p;
2711     char talker[MSG_SIZ]; // [HGM] chat
2712     int channel;
2713
2714     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2715
2716     if (appData.debugMode) {
2717       if (!error) {
2718         fprintf(debugFP, "<ICS: ");
2719         show_bytes(debugFP, data, count);
2720         fprintf(debugFP, "\n");
2721       }
2722     }
2723
2724     if (appData.debugMode) { int f = forwardMostMove;
2725         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2728     }
2729     if (count > 0) {
2730         /* If last read ended with a partial line that we couldn't parse,
2731            prepend it to the new read and try again. */
2732         if (leftover_len > 0) {
2733             for (i=0; i<leftover_len; i++)
2734               buf[i] = buf[leftover_start + i];
2735         }
2736
2737     /* copy new characters into the buffer */
2738     bp = buf + leftover_len;
2739     buf_len=leftover_len;
2740     for (i=0; i<count; i++)
2741     {
2742         // ignore these
2743         if (data[i] == '\r')
2744             continue;
2745
2746         // join lines split by ICS?
2747         if (!appData.noJoin)
2748         {
2749             /*
2750                 Joining just consists of finding matches against the
2751                 continuation sequence, and discarding that sequence
2752                 if found instead of copying it.  So, until a match
2753                 fails, there's nothing to do since it might be the
2754                 complete sequence, and thus, something we don't want
2755                 copied.
2756             */
2757             if (data[i] == cont_seq[cmatch])
2758             {
2759                 cmatch++;
2760                 if (cmatch == strlen(cont_seq))
2761                 {
2762                     cmatch = 0; // complete match.  just reset the counter
2763
2764                     /*
2765                         it's possible for the ICS to not include the space
2766                         at the end of the last word, making our [correct]
2767                         join operation fuse two separate words.  the server
2768                         does this when the space occurs at the width setting.
2769                     */
2770                     if (!buf_len || buf[buf_len-1] != ' ')
2771                     {
2772                         *bp++ = ' ';
2773                         buf_len++;
2774                     }
2775                 }
2776                 continue;
2777             }
2778             else if (cmatch)
2779             {
2780                 /*
2781                     match failed, so we have to copy what matched before
2782                     falling through and copying this character.  In reality,
2783                     this will only ever be just the newline character, but
2784                     it doesn't hurt to be precise.
2785                 */
2786                 strncpy(bp, cont_seq, cmatch);
2787                 bp += cmatch;
2788                 buf_len += cmatch;
2789                 cmatch = 0;
2790             }
2791         }
2792
2793         // copy this char
2794         *bp++ = data[i];
2795         buf_len++;
2796     }
2797
2798         buf[buf_len] = NULLCHAR;
2799 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2800         next_out = 0;
2801         leftover_start = 0;
2802
2803         i = 0;
2804         while (i < buf_len) {
2805             /* Deal with part of the TELNET option negotiation
2806                protocol.  We refuse to do anything beyond the
2807                defaults, except that we allow the WILL ECHO option,
2808                which ICS uses to turn off password echoing when we are
2809                directly connected to it.  We reject this option
2810                if localLineEditing mode is on (always on in xboard)
2811                and we are talking to port 23, which might be a real
2812                telnet server that will try to keep WILL ECHO on permanently.
2813              */
2814             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816                 unsigned char option;
2817                 oldi = i;
2818                 switch ((unsigned char) buf[++i]) {
2819                   case TN_WILL:
2820                     if (appData.debugMode)
2821                       fprintf(debugFP, "\n<WILL ");
2822                     switch (option = (unsigned char) buf[++i]) {
2823                       case TN_ECHO:
2824                         if (appData.debugMode)
2825                           fprintf(debugFP, "ECHO ");
2826                         /* Reply only if this is a change, according
2827                            to the protocol rules. */
2828                         if (remoteEchoOption) break;
2829                         if (appData.localLineEditing &&
2830                             atoi(appData.icsPort) == TN_PORT) {
2831                             TelnetRequest(TN_DONT, TN_ECHO);
2832                         } else {
2833                             EchoOff();
2834                             TelnetRequest(TN_DO, TN_ECHO);
2835                             remoteEchoOption = TRUE;
2836                         }
2837                         break;
2838                       default:
2839                         if (appData.debugMode)
2840                           fprintf(debugFP, "%d ", option);
2841                         /* Whatever this is, we don't want it. */
2842                         TelnetRequest(TN_DONT, option);
2843                         break;
2844                     }
2845                     break;
2846                   case TN_WONT:
2847                     if (appData.debugMode)
2848                       fprintf(debugFP, "\n<WONT ");
2849                     switch (option = (unsigned char) buf[++i]) {
2850                       case TN_ECHO:
2851                         if (appData.debugMode)
2852                           fprintf(debugFP, "ECHO ");
2853                         /* Reply only if this is a change, according
2854                            to the protocol rules. */
2855                         if (!remoteEchoOption) break;
2856                         EchoOn();
2857                         TelnetRequest(TN_DONT, TN_ECHO);
2858                         remoteEchoOption = FALSE;
2859                         break;
2860                       default:
2861                         if (appData.debugMode)
2862                           fprintf(debugFP, "%d ", (unsigned char) option);
2863                         /* Whatever this is, it must already be turned
2864                            off, because we never agree to turn on
2865                            anything non-default, so according to the
2866                            protocol rules, we don't reply. */
2867                         break;
2868                     }
2869                     break;
2870                   case TN_DO:
2871                     if (appData.debugMode)
2872                       fprintf(debugFP, "\n<DO ");
2873                     switch (option = (unsigned char) buf[++i]) {
2874                       default:
2875                         /* Whatever this is, we refuse to do it. */
2876                         if (appData.debugMode)
2877                           fprintf(debugFP, "%d ", option);
2878                         TelnetRequest(TN_WONT, option);
2879                         break;
2880                     }
2881                     break;
2882                   case TN_DONT:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<DONT ");
2885                     switch (option = (unsigned char) buf[++i]) {
2886                       default:
2887                         if (appData.debugMode)
2888                           fprintf(debugFP, "%d ", option);
2889                         /* Whatever this is, we are already not doing
2890                            it, because we never agree to do anything
2891                            non-default, so according to the protocol
2892                            rules, we don't reply. */
2893                         break;
2894                     }
2895                     break;
2896                   case TN_IAC:
2897                     if (appData.debugMode)
2898                       fprintf(debugFP, "\n<IAC ");
2899                     /* Doubled IAC; pass it through */
2900                     i--;
2901                     break;
2902                   default:
2903                     if (appData.debugMode)
2904                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905                     /* Drop all other telnet commands on the floor */
2906                     break;
2907                 }
2908                 if (oldi > next_out)
2909                   SendToPlayer(&buf[next_out], oldi - next_out);
2910                 if (++i > next_out)
2911                   next_out = i;
2912                 continue;
2913             }
2914
2915             /* OK, this at least will *usually* work */
2916             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2917                 loggedOn = TRUE;
2918             }
2919
2920             if (loggedOn && !intfSet) {
2921                 if (ics_type == ICS_ICC) {
2922                   snprintf(str, MSG_SIZ,
2923                           "/set-quietly interface %s\n/set-quietly style 12\n",
2924                           programVersion);
2925                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2927                 } else if (ics_type == ICS_CHESSNET) {
2928                   snprintf(str, MSG_SIZ, "/style 12\n");
2929                 } else {
2930                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931                   strcat(str, programVersion);
2932                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2935 #ifdef WIN32
2936                   strcat(str, "$iset nohighlight 1\n");
2937 #endif
2938                   strcat(str, "$iset lock 1\n$style 12\n");
2939                 }
2940                 SendToICS(str);
2941                 NotifyFrontendLogin();
2942                 intfSet = TRUE;
2943             }
2944
2945             if (started == STARTED_COMMENT) {
2946                 /* Accumulate characters in comment */
2947                 parse[parse_pos++] = buf[i];
2948                 if (buf[i] == '\n') {
2949                     parse[parse_pos] = NULLCHAR;
2950                     if(chattingPartner>=0) {
2951                         char mess[MSG_SIZ];
2952                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953                         OutputChatMessage(chattingPartner, mess);
2954                         chattingPartner = -1;
2955                         next_out = i+1; // [HGM] suppress printing in ICS window
2956                     } else
2957                     if(!suppressKibitz) // [HGM] kibitz
2958                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960                         int nrDigit = 0, nrAlph = 0, j;
2961                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963                         parse[parse_pos] = NULLCHAR;
2964                         // try to be smart: if it does not look like search info, it should go to
2965                         // ICS interaction window after all, not to engine-output window.
2966                         for(j=0; j<parse_pos; j++) { // count letters and digits
2967                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2969                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2970                         }
2971                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972                             int depth=0; float score;
2973                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975                                 pvInfoList[forwardMostMove-1].depth = depth;
2976                                 pvInfoList[forwardMostMove-1].score = 100*score;
2977                             }
2978                             OutputKibitz(suppressKibitz, parse);
2979                         } else {
2980                             char tmp[MSG_SIZ];
2981                             if(gameMode == IcsObserving) // restore original ICS messages
2982                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2983                             else
2984                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985                             SendToPlayer(tmp, strlen(tmp));
2986                         }
2987                         next_out = i+1; // [HGM] suppress printing in ICS window
2988                     }
2989                     started = STARTED_NONE;
2990                 } else {
2991                     /* Don't match patterns against characters in comment */
2992                     i++;
2993                     continue;
2994                 }
2995             }
2996             if (started == STARTED_CHATTER) {
2997                 if (buf[i] != '\n') {
2998                     /* Don't match patterns against characters in chatter */
2999                     i++;
3000                     continue;
3001                 }
3002                 started = STARTED_NONE;
3003                 if(suppressKibitz) next_out = i+1;
3004             }
3005
3006             /* Kludge to deal with rcmd protocol */
3007             if (firstTime && looking_at(buf, &i, "\001*")) {
3008                 DisplayFatalError(&buf[1], 0, 1);
3009                 continue;
3010             } else {
3011                 firstTime = FALSE;
3012             }
3013
3014             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3015                 ics_type = ICS_ICC;
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, "freechess.org")) {
3022                 ics_type = ICS_FICS;
3023                 ics_prefix = "$";
3024                 if (appData.debugMode)
3025                   fprintf(debugFP, "ics_type %d\n", ics_type);
3026                 continue;
3027             }
3028             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029                 ics_type = ICS_CHESSNET;
3030                 ics_prefix = "/";
3031                 if (appData.debugMode)
3032                   fprintf(debugFP, "ics_type %d\n", ics_type);
3033                 continue;
3034             }
3035
3036             if (!loggedOn &&
3037                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3039                  looking_at(buf, &i, "will be \"*\""))) {
3040               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3041               continue;
3042             }
3043
3044             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3045               char buf[MSG_SIZ];
3046               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047               DisplayIcsInteractionTitle(buf);
3048               have_set_title = TRUE;
3049             }
3050
3051             /* skip finger notes */
3052             if (started == STARTED_NONE &&
3053                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054                  (buf[i] == '1' && buf[i+1] == '0')) &&
3055                 buf[i+2] == ':' && buf[i+3] == ' ') {
3056               started = STARTED_CHATTER;
3057               i += 3;
3058               continue;
3059             }
3060
3061             oldi = i;
3062             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063             if(appData.seekGraph) {
3064                 if(soughtPending && MatchSoughtLine(buf+i)) {
3065                     i = strstr(buf+i, "rated") - buf;
3066                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                     next_out = leftover_start = i;
3068                     started = STARTED_CHATTER;
3069                     suppressKibitz = TRUE;
3070                     continue;
3071                 }
3072                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073                         && looking_at(buf, &i, "* ads displayed")) {
3074                     soughtPending = FALSE;
3075                     seekGraphUp = TRUE;
3076                     DrawSeekGraph();
3077                     continue;
3078                 }
3079                 if(appData.autoRefresh) {
3080                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081                         int s = (ics_type == ICS_ICC); // ICC format differs
3082                         if(seekGraphUp)
3083                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085                         looking_at(buf, &i, "*% "); // eat prompt
3086                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = i; // suppress
3089                         continue;
3090                     }
3091                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092                         char *p = star_match[0];
3093                         while(*p) {
3094                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3095                             while(*p && *p++ != ' '); // next
3096                         }
3097                         looking_at(buf, &i, "*% "); // eat prompt
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         continue;
3101                     }
3102                 }
3103             }
3104
3105             /* skip formula vars */
3106             if (started == STARTED_NONE &&
3107                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108               started = STARTED_CHATTER;
3109               i += 3;
3110               continue;
3111             }
3112
3113             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114             if (appData.autoKibitz && started == STARTED_NONE &&
3115                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3116                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3121                         suppressKibitz = TRUE;
3122                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                         next_out = i;
3124                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125                                 && (gameMode == IcsPlayingWhite)) ||
3126                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3128                             started = STARTED_CHATTER; // own kibitz we simply discard
3129                         else {
3130                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131                             parse_pos = 0; parse[0] = NULLCHAR;
3132                             savingComment = TRUE;
3133                             suppressKibitz = gameMode != IcsObserving ? 2 :
3134                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3135                         }
3136                         continue;
3137                 } else
3138                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140                          && atoi(star_match[0])) {
3141                     // suppress the acknowledgements of our own autoKibitz
3142                     char *p;
3143                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145                     SendToPlayer(star_match[0], strlen(star_match[0]));
3146                     if(looking_at(buf, &i, "*% ")) // eat prompt
3147                         suppressKibitz = FALSE;
3148                     next_out = i;
3149                     continue;
3150                 }
3151             } // [HGM] kibitz: end of patch
3152
3153             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3154
3155             // [HGM] chat: intercept tells by users for which we have an open chat window
3156             channel = -1;
3157             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158                                            looking_at(buf, &i, "* whispers:") ||
3159                                            looking_at(buf, &i, "* kibitzes:") ||
3160                                            looking_at(buf, &i, "* shouts:") ||
3161                                            looking_at(buf, &i, "* c-shouts:") ||
3162                                            looking_at(buf, &i, "--> * ") ||
3163                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3167                 int p;
3168                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169                 chattingPartner = -1;
3170
3171                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172                 for(p=0; p<MAX_CHAT; p++) {
3173                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174                     talker[0] = '['; strcat(talker, "] ");
3175                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176                     chattingPartner = p; break;
3177                     }
3178                 } else
3179                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180                 for(p=0; p<MAX_CHAT; p++) {
3181                     if(!strcmp("kibitzes", chatPartner[p])) {
3182                         talker[0] = '['; strcat(talker, "] ");
3183                         chattingPartner = p; break;
3184                     }
3185                 } else
3186                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187                 for(p=0; p<MAX_CHAT; p++) {
3188                     if(!strcmp("whispers", chatPartner[p])) {
3189                         talker[0] = '['; strcat(talker, "] ");
3190                         chattingPartner = p; break;
3191                     }
3192                 } else
3193                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194                   if(buf[i-8] == '-' && buf[i-3] == 't')
3195                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196                     if(!strcmp("c-shouts", chatPartner[p])) {
3197                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198                         chattingPartner = p; break;
3199                     }
3200                   }
3201                   if(chattingPartner < 0)
3202                   for(p=0; p<MAX_CHAT; p++) {
3203                     if(!strcmp("shouts", chatPartner[p])) {
3204                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207                         chattingPartner = p; break;
3208                     }
3209                   }
3210                 }
3211                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213                     talker[0] = 0; Colorize(ColorTell, FALSE);
3214                     chattingPartner = p; break;
3215                 }
3216                 if(chattingPartner<0) i = oldi; else {
3217                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220                     started = STARTED_COMMENT;
3221                     parse_pos = 0; parse[0] = NULLCHAR;
3222                     savingComment = 3 + chattingPartner; // counts as TRUE
3223                     suppressKibitz = TRUE;
3224                     continue;
3225                 }
3226             } // [HGM] chat: end of patch
3227
3228           backup = i;
3229             if (appData.zippyTalk || appData.zippyPlay) {
3230                 /* [DM] Backup address for color zippy lines */
3231 #if ZIPPY
3232                if (loggedOn == TRUE)
3233                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3235 #endif
3236             } // [DM] 'else { ' deleted
3237                 if (
3238                     /* Regular tells and says */
3239                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3241                     looking_at(buf, &i, "* says: ") ||
3242                     /* Don't color "message" or "messages" output */
3243                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244                     looking_at(buf, &i, "*. * at *:*: ") ||
3245                     looking_at(buf, &i, "--* (*:*): ") ||
3246                     /* Message notifications (same color as tells) */
3247                     looking_at(buf, &i, "* has left a message ") ||
3248                     looking_at(buf, &i, "* just sent you a message:\n") ||
3249                     /* Whispers and kibitzes */
3250                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251                     looking_at(buf, &i, "* kibitzes: ") ||
3252                     /* Channel tells */
3253                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3254
3255                   if (tkind == 1 && strchr(star_match[0], ':')) {
3256                       /* Avoid "tells you:" spoofs in channels */
3257                      tkind = 3;
3258                   }
3259                   if (star_match[0][0] == NULLCHAR ||
3260                       strchr(star_match[0], ' ') ||
3261                       (tkind == 3 && strchr(star_match[1], ' '))) {
3262                     /* Reject bogus matches */
3263                     i = oldi;
3264                   } else {
3265                     if (appData.colorize) {
3266                       if (oldi > next_out) {
3267                         SendToPlayer(&buf[next_out], oldi - next_out);
3268                         next_out = oldi;
3269                       }
3270                       switch (tkind) {
3271                       case 1:
3272                         Colorize(ColorTell, FALSE);
3273                         curColor = ColorTell;
3274                         break;
3275                       case 2:
3276                         Colorize(ColorKibitz, FALSE);
3277                         curColor = ColorKibitz;
3278                         break;
3279                       case 3:
3280                         p = strrchr(star_match[1], '(');
3281                         if (p == NULL) {
3282                           p = star_match[1];
3283                         } else {
3284                           p++;
3285                         }
3286                         if (atoi(p) == 1) {
3287                           Colorize(ColorChannel1, FALSE);
3288                           curColor = ColorChannel1;
3289                         } else {
3290                           Colorize(ColorChannel, FALSE);
3291                           curColor = ColorChannel;
3292                         }
3293                         break;
3294                       case 5:
3295                         curColor = ColorNormal;
3296                         break;
3297                       }
3298                     }
3299                     if (started == STARTED_NONE && appData.autoComment &&
3300                         (gameMode == IcsObserving ||
3301                          gameMode == IcsPlayingWhite ||
3302                          gameMode == IcsPlayingBlack)) {
3303                       parse_pos = i - oldi;
3304                       memcpy(parse, &buf[oldi], parse_pos);
3305                       parse[parse_pos] = NULLCHAR;
3306                       started = STARTED_COMMENT;
3307                       savingComment = TRUE;
3308                     } else {
3309                       started = STARTED_CHATTER;
3310                       savingComment = FALSE;
3311                     }
3312                     loggedOn = TRUE;
3313                     continue;
3314                   }
3315                 }
3316
3317                 if (looking_at(buf, &i, "* s-shouts: ") ||
3318                     looking_at(buf, &i, "* c-shouts: ")) {
3319                     if (appData.colorize) {
3320                         if (oldi > next_out) {
3321                             SendToPlayer(&buf[next_out], oldi - next_out);
3322                             next_out = oldi;
3323                         }
3324                         Colorize(ColorSShout, FALSE);
3325                         curColor = ColorSShout;
3326                     }
3327                     loggedOn = TRUE;
3328                     started = STARTED_CHATTER;
3329                     continue;
3330                 }
3331
3332                 if (looking_at(buf, &i, "--->")) {
3333                     loggedOn = TRUE;
3334                     continue;
3335                 }
3336
3337                 if (looking_at(buf, &i, "* shouts: ") ||
3338                     looking_at(buf, &i, "--> ")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorShout, FALSE);
3345                         curColor = ColorShout;
3346                     }
3347                     loggedOn = TRUE;
3348                     started = STARTED_CHATTER;
3349                     continue;
3350                 }
3351
3352                 if (looking_at( buf, &i, "Challenge:")) {
3353                     if (appData.colorize) {
3354                         if (oldi > next_out) {
3355                             SendToPlayer(&buf[next_out], oldi - next_out);
3356                             next_out = oldi;
3357                         }
3358                         Colorize(ColorChallenge, FALSE);
3359                         curColor = ColorChallenge;
3360                     }
3361                     loggedOn = TRUE;
3362                     continue;
3363                 }
3364
3365                 if (looking_at(buf, &i, "* offers you") ||
3366                     looking_at(buf, &i, "* offers to be") ||
3367                     looking_at(buf, &i, "* would like to") ||
3368                     looking_at(buf, &i, "* requests to") ||
3369                     looking_at(buf, &i, "Your opponent offers") ||
3370                     looking_at(buf, &i, "Your opponent requests")) {
3371
3372                     if (appData.colorize) {
3373                         if (oldi > next_out) {
3374                             SendToPlayer(&buf[next_out], oldi - next_out);
3375                             next_out = oldi;
3376                         }
3377                         Colorize(ColorRequest, FALSE);
3378                         curColor = ColorRequest;
3379                     }
3380                     continue;
3381                 }
3382
3383                 if (looking_at(buf, &i, "* (*) seeking")) {
3384                     if (appData.colorize) {
3385                         if (oldi > next_out) {
3386                             SendToPlayer(&buf[next_out], oldi - next_out);
3387                             next_out = oldi;
3388                         }
3389                         Colorize(ColorSeek, FALSE);
3390                         curColor = ColorSeek;
3391                     }
3392                     continue;
3393             }
3394
3395           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3396
3397             if (looking_at(buf, &i, "\\   ")) {
3398                 if (prevColor != ColorNormal) {
3399                     if (oldi > next_out) {
3400                         SendToPlayer(&buf[next_out], oldi - next_out);
3401                         next_out = oldi;
3402                     }
3403                     Colorize(prevColor, TRUE);
3404                     curColor = prevColor;
3405                 }
3406                 if (savingComment) {
3407                     parse_pos = i - oldi;
3408                     memcpy(parse, &buf[oldi], parse_pos);
3409                     parse[parse_pos] = NULLCHAR;
3410                     started = STARTED_COMMENT;
3411                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412                         chattingPartner = savingComment - 3; // kludge to remember the box
3413                 } else {
3414                     started = STARTED_CHATTER;
3415                 }
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "Black Strength :") ||
3420                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421                 looking_at(buf, &i, "<10>") ||
3422                 looking_at(buf, &i, "#@#")) {
3423                 /* Wrong board style */
3424                 loggedOn = TRUE;
3425                 SendToICS(ics_prefix);
3426                 SendToICS("set style 12\n");
3427                 SendToICS(ics_prefix);
3428                 SendToICS("refresh\n");
3429                 continue;
3430             }
3431
3432             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3433                 ICSInitScript();
3434                 have_sent_ICS_logon = 1;
3435                 continue;
3436             }
3437
3438             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439                 (looking_at(buf, &i, "\n<12> ") ||
3440                  looking_at(buf, &i, "<12> "))) {
3441                 loggedOn = TRUE;
3442                 if (oldi > next_out) {
3443                     SendToPlayer(&buf[next_out], oldi - next_out);
3444                 }
3445                 next_out = i;
3446                 started = STARTED_BOARD;
3447                 parse_pos = 0;
3448                 continue;
3449             }
3450
3451             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452                 looking_at(buf, &i, "<b1> ")) {
3453                 if (oldi > next_out) {
3454                     SendToPlayer(&buf[next_out], oldi - next_out);
3455                 }
3456                 next_out = i;
3457                 started = STARTED_HOLDINGS;
3458                 parse_pos = 0;
3459                 continue;
3460             }
3461
3462             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3463                 loggedOn = TRUE;
3464                 /* Header for a move list -- first line */
3465
3466                 switch (ics_getting_history) {
3467                   case H_FALSE:
3468                     switch (gameMode) {
3469                       case IcsIdle:
3470                       case BeginningOfGame:
3471                         /* User typed "moves" or "oldmoves" while we
3472                            were idle.  Pretend we asked for these
3473                            moves and soak them up so user can step
3474                            through them and/or save them.
3475                            */
3476                         Reset(FALSE, TRUE);
3477                         gameMode = IcsObserving;
3478                         ModeHighlight();
3479                         ics_gamenum = -1;
3480                         ics_getting_history = H_GOT_UNREQ_HEADER;
3481                         break;
3482                       case EditGame: /*?*/
3483                       case EditPosition: /*?*/
3484                         /* Should above feature work in these modes too? */
3485                         /* For now it doesn't */
3486                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3487                         break;
3488                       default:
3489                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3490                         break;
3491                     }
3492                     break;
3493                   case H_REQUESTED:
3494                     /* Is this the right one? */
3495                     if (gameInfo.white && gameInfo.black &&
3496                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3497                         strcmp(gameInfo.black, star_match[2]) == 0) {
3498                         /* All is well */
3499                         ics_getting_history = H_GOT_REQ_HEADER;
3500                     }
3501                     break;
3502                   case H_GOT_REQ_HEADER:
3503                   case H_GOT_UNREQ_HEADER:
3504                   case H_GOT_UNWANTED_HEADER:
3505                   case H_GETTING_MOVES:
3506                     /* Should not happen */
3507                     DisplayError(_("Error gathering move list: two headers"), 0);
3508                     ics_getting_history = H_FALSE;
3509                     break;
3510                 }
3511
3512                 /* Save player ratings into gameInfo if needed */
3513                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515                     (gameInfo.whiteRating == -1 ||
3516                      gameInfo.blackRating == -1)) {
3517
3518                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3519                     gameInfo.blackRating = string_to_rating(star_match[3]);
3520                     if (appData.debugMode)
3521                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522                               gameInfo.whiteRating, gameInfo.blackRating);
3523                 }
3524                 continue;
3525             }
3526
3527             if (looking_at(buf, &i,
3528               "* * match, initial time: * minute*, increment: * second")) {
3529                 /* Header for a move list -- second line */
3530                 /* Initial board will follow if this is a wild game */
3531                 if (gameInfo.event != NULL) free(gameInfo.event);
3532                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533                 gameInfo.event = StrSave(str);
3534                 /* [HGM] we switched variant. Translate boards if needed. */
3535                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3536                 continue;
3537             }
3538
3539             if (looking_at(buf, &i, "Move  ")) {
3540                 /* Beginning of a move list */
3541                 switch (ics_getting_history) {
3542                   case H_FALSE:
3543                     /* Normally should not happen */
3544                     /* Maybe user hit reset while we were parsing */
3545                     break;
3546                   case H_REQUESTED:
3547                     /* Happens if we are ignoring a move list that is not
3548                      * the one we just requested.  Common if the user
3549                      * tries to observe two games without turning off
3550                      * getMoveList */
3551                     break;
3552                   case H_GETTING_MOVES:
3553                     /* Should not happen */
3554                     DisplayError(_("Error gathering move list: nested"), 0);
3555                     ics_getting_history = H_FALSE;
3556                     break;
3557                   case H_GOT_REQ_HEADER:
3558                     ics_getting_history = H_GETTING_MOVES;
3559                     started = STARTED_MOVES;
3560                     parse_pos = 0;
3561                     if (oldi > next_out) {
3562                         SendToPlayer(&buf[next_out], oldi - next_out);
3563                     }
3564                     break;
3565                   case H_GOT_UNREQ_HEADER:
3566                     ics_getting_history = H_GETTING_MOVES;
3567                     started = STARTED_MOVES_NOHIDE;
3568                     parse_pos = 0;
3569                     break;
3570                   case H_GOT_UNWANTED_HEADER:
3571                     ics_getting_history = H_FALSE;
3572                     break;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "% ") ||
3578                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581                     soughtPending = FALSE;
3582                     seekGraphUp = TRUE;
3583                     DrawSeekGraph();
3584                 }
3585                 if(suppressKibitz) next_out = i;
3586                 savingComment = FALSE;
3587                 suppressKibitz = 0;
3588                 switch (started) {
3589                   case STARTED_MOVES:
3590                   case STARTED_MOVES_NOHIDE:
3591                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592                     parse[parse_pos + i - oldi] = NULLCHAR;
3593                     ParseGameHistory(parse);
3594 #if ZIPPY
3595                     if (appData.zippyPlay && first.initDone) {
3596                         FeedMovesToProgram(&first, forwardMostMove);
3597                         if (gameMode == IcsPlayingWhite) {
3598                             if (WhiteOnMove(forwardMostMove)) {
3599                                 if (first.sendTime) {
3600                                   if (first.useColors) {
3601                                     SendToProgram("black\n", &first);
3602                                   }
3603                                   SendTimeRemaining(&first, TRUE);
3604                                 }
3605                                 if (first.useColors) {
3606                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3607                                 }
3608                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609                                 first.maybeThinking = TRUE;
3610                             } else {
3611                                 if (first.usePlayother) {
3612                                   if (first.sendTime) {
3613                                     SendTimeRemaining(&first, TRUE);
3614                                   }
3615                                   SendToProgram("playother\n", &first);
3616                                   firstMove = FALSE;
3617                                 } else {
3618                                   firstMove = TRUE;
3619                                 }
3620                             }
3621                         } else if (gameMode == IcsPlayingBlack) {
3622                             if (!WhiteOnMove(forwardMostMove)) {
3623                                 if (first.sendTime) {
3624                                   if (first.useColors) {
3625                                     SendToProgram("white\n", &first);
3626                                   }
3627                                   SendTimeRemaining(&first, FALSE);
3628                                 }
3629                                 if (first.useColors) {
3630                                   SendToProgram("black\n", &first);
3631                                 }
3632                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633                                 first.maybeThinking = TRUE;
3634                             } else {
3635                                 if (first.usePlayother) {
3636                                   if (first.sendTime) {
3637                                     SendTimeRemaining(&first, FALSE);
3638                                   }
3639                                   SendToProgram("playother\n", &first);
3640                                   firstMove = FALSE;
3641                                 } else {
3642                                   firstMove = TRUE;
3643                                 }
3644                             }
3645                         }
3646                     }
3647 #endif
3648                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3649                         /* Moves came from oldmoves or moves command
3650                            while we weren't doing anything else.
3651                            */
3652                         currentMove = forwardMostMove;
3653                         ClearHighlights();/*!!could figure this out*/
3654                         flipView = appData.flipView;
3655                         DrawPosition(TRUE, boards[currentMove]);
3656                         DisplayBothClocks();
3657                         snprintf(str, MSG_SIZ, "%s %s %s",
3658                                 gameInfo.white, _("vs."),  gameInfo.black);
3659                         DisplayTitle(str);
3660                         gameMode = IcsIdle;
3661                     } else {
3662                         /* Moves were history of an active game */
3663                         if (gameInfo.resultDetails != NULL) {
3664                             free(gameInfo.resultDetails);
3665                             gameInfo.resultDetails = NULL;
3666                         }
3667                     }
3668                     HistorySet(parseList, backwardMostMove,
3669                                forwardMostMove, currentMove-1);
3670                     DisplayMove(currentMove - 1);
3671                     if (started == STARTED_MOVES) next_out = i;
3672                     started = STARTED_NONE;
3673                     ics_getting_history = H_FALSE;
3674                     break;
3675
3676                   case STARTED_OBSERVE:
3677                     started = STARTED_NONE;
3678                     SendToICS(ics_prefix);
3679                     SendToICS("refresh\n");
3680                     break;
3681
3682                   default:
3683                     break;
3684                 }
3685                 if(bookHit) { // [HGM] book: simulate book reply
3686                     static char bookMove[MSG_SIZ]; // a bit generous?
3687
3688                     programStats.nodes = programStats.depth = programStats.time =
3689                     programStats.score = programStats.got_only_move = 0;
3690                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3691
3692                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693                     strcat(bookMove, bookHit);
3694                     HandleMachineMove(bookMove, &first);
3695                 }
3696                 continue;
3697             }
3698
3699             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700                  started == STARTED_HOLDINGS ||
3701                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702                 /* Accumulate characters in move list or board */
3703                 parse[parse_pos++] = buf[i];
3704             }
3705
3706             /* Start of game messages.  Mostly we detect start of game
3707                when the first board image arrives.  On some versions
3708                of the ICS, though, we need to do a "refresh" after starting
3709                to observe in order to get the current board right away. */
3710             if (looking_at(buf, &i, "Adding game * to observation list")) {
3711                 started = STARTED_OBSERVE;
3712                 continue;
3713             }
3714
3715             /* Handle auto-observe */
3716             if (appData.autoObserve &&
3717                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3719                 char *player;
3720                 /* Choose the player that was highlighted, if any. */
3721                 if (star_match[0][0] == '\033' ||
3722                     star_match[1][0] != '\033') {
3723                     player = star_match[0];
3724                 } else {
3725                     player = star_match[2];
3726                 }
3727                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728                         ics_prefix, StripHighlightAndTitle(player));
3729                 SendToICS(str);
3730
3731                 /* Save ratings from notify string */
3732                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733                 player1Rating = string_to_rating(star_match[1]);
3734                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735                 player2Rating = string_to_rating(star_match[3]);
3736
3737                 if (appData.debugMode)
3738                   fprintf(debugFP,
3739                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3740                           player1Name, player1Rating,
3741                           player2Name, player2Rating);
3742
3743                 continue;
3744             }
3745
3746             /* Deal with automatic examine mode after a game,
3747                and with IcsObserving -> IcsExamining transition */
3748             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749                 looking_at(buf, &i, "has made you an examiner of game *")) {
3750
3751                 int gamenum = atoi(star_match[0]);
3752                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753                     gamenum == ics_gamenum) {
3754                     /* We were already playing or observing this game;
3755                        no need to refetch history */
3756                     gameMode = IcsExamining;
3757                     if (pausing) {
3758                         pauseExamForwardMostMove = forwardMostMove;
3759                     } else if (currentMove < forwardMostMove) {
3760                         ForwardInner(forwardMostMove);
3761                     }
3762                 } else {
3763                     /* I don't think this case really can happen */
3764                     SendToICS(ics_prefix);
3765                     SendToICS("refresh\n");
3766                 }
3767                 continue;
3768             }
3769
3770             /* Error messages */
3771 //          if (ics_user_moved) {
3772             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773                 if (looking_at(buf, &i, "Illegal move") ||
3774                     looking_at(buf, &i, "Not a legal move") ||
3775                     looking_at(buf, &i, "Your king is in check") ||
3776                     looking_at(buf, &i, "It isn't your turn") ||
3777                     looking_at(buf, &i, "It is not your move")) {
3778                     /* Illegal move */
3779                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780                         currentMove = forwardMostMove-1;
3781                         DisplayMove(currentMove - 1); /* before DMError */
3782                         DrawPosition(FALSE, boards[currentMove]);
3783                         SwitchClocks(forwardMostMove-1); // [HGM] race
3784                         DisplayBothClocks();
3785                     }
3786                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3787                     ics_user_moved = 0;
3788                     continue;
3789                 }
3790             }
3791
3792             if (looking_at(buf, &i, "still have time") ||
3793                 looking_at(buf, &i, "not out of time") ||
3794                 looking_at(buf, &i, "either player is out of time") ||
3795                 looking_at(buf, &i, "has timeseal; checking")) {
3796                 /* We must have called his flag a little too soon */
3797                 whiteFlag = blackFlag = FALSE;
3798                 continue;
3799             }
3800
3801             if (looking_at(buf, &i, "added * seconds to") ||
3802                 looking_at(buf, &i, "seconds were added to")) {
3803                 /* Update the clocks */
3804                 SendToICS(ics_prefix);
3805                 SendToICS("refresh\n");
3806                 continue;
3807             }
3808
3809             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810                 ics_clock_paused = TRUE;
3811                 StopClocks();
3812                 continue;
3813             }
3814
3815             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816                 ics_clock_paused = FALSE;
3817                 StartClocks();
3818                 continue;
3819             }
3820
3821             /* Grab player ratings from the Creating: message.
3822                Note we have to check for the special case when
3823                the ICS inserts things like [white] or [black]. */
3824             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3826                 /* star_matches:
3827                    0    player 1 name (not necessarily white)
3828                    1    player 1 rating
3829                    2    empty, white, or black (IGNORED)
3830                    3    player 2 name (not necessarily black)
3831                    4    player 2 rating
3832
3833                    The names/ratings are sorted out when the game
3834                    actually starts (below).
3835                 */
3836                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837                 player1Rating = string_to_rating(star_match[1]);
3838                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839                 player2Rating = string_to_rating(star_match[4]);
3840
3841                 if (appData.debugMode)
3842                   fprintf(debugFP,
3843                           "Ratings from 'Creating:' %s %d, %s %d\n",
3844                           player1Name, player1Rating,
3845                           player2Name, player2Rating);
3846
3847                 continue;
3848             }
3849
3850             /* Improved generic start/end-of-game messages */
3851             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853                 /* If tkind == 0: */
3854                 /* star_match[0] is the game number */
3855                 /*           [1] is the white player's name */
3856                 /*           [2] is the black player's name */
3857                 /* For end-of-game: */
3858                 /*           [3] is the reason for the game end */
3859                 /*           [4] is a PGN end game-token, preceded by " " */
3860                 /* For start-of-game: */
3861                 /*           [3] begins with "Creating" or "Continuing" */
3862                 /*           [4] is " *" or empty (don't care). */
3863                 int gamenum = atoi(star_match[0]);
3864                 char *whitename, *blackname, *why, *endtoken;
3865                 ChessMove endtype = EndOfFile;
3866
3867                 if (tkind == 0) {
3868                   whitename = star_match[1];
3869                   blackname = star_match[2];
3870                   why = star_match[3];
3871                   endtoken = star_match[4];
3872                 } else {
3873                   whitename = star_match[1];
3874                   blackname = star_match[3];
3875                   why = star_match[5];
3876                   endtoken = star_match[6];
3877                 }
3878
3879                 /* Game start messages */
3880                 if (strncmp(why, "Creating ", 9) == 0 ||
3881                     strncmp(why, "Continuing ", 11) == 0) {
3882                     gs_gamenum = gamenum;
3883                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3884                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3885                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3886 #if ZIPPY
3887                     if (appData.zippyPlay) {
3888                         ZippyGameStart(whitename, blackname);
3889                     }
3890 #endif /*ZIPPY*/
3891                     partnerBoardValid = FALSE; // [HGM] bughouse
3892                     continue;
3893                 }
3894
3895                 /* Game end messages */
3896                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3897                     ics_gamenum != gamenum) {
3898                     continue;
3899                 }
3900                 while (endtoken[0] == ' ') endtoken++;
3901                 switch (endtoken[0]) {
3902                   case '*':
3903                   default:
3904                     endtype = GameUnfinished;
3905                     break;
3906                   case '0':
3907                     endtype = BlackWins;
3908                     break;
3909                   case '1':
3910                     if (endtoken[1] == '/')
3911                       endtype = GameIsDrawn;
3912                     else
3913                       endtype = WhiteWins;
3914                     break;
3915                 }
3916                 GameEnds(endtype, why, GE_ICS);
3917 #if ZIPPY
3918                 if (appData.zippyPlay && first.initDone) {
3919                     ZippyGameEnd(endtype, why);
3920                     if (first.pr == NoProc) {
3921                       /* Start the next process early so that we'll
3922                          be ready for the next challenge */
3923                       StartChessProgram(&first);
3924                     }
3925                     /* Send "new" early, in case this command takes
3926                        a long time to finish, so that we'll be ready
3927                        for the next challenge. */
3928                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3929                     Reset(TRUE, TRUE);
3930                 }
3931 #endif /*ZIPPY*/
3932                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3933                 continue;
3934             }
3935
3936             if (looking_at(buf, &i, "Removing game * from observation") ||
3937                 looking_at(buf, &i, "no longer observing game *") ||
3938                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3939                 if (gameMode == IcsObserving &&
3940                     atoi(star_match[0]) == ics_gamenum)
3941                   {
3942                       /* icsEngineAnalyze */
3943                       if (appData.icsEngineAnalyze) {
3944                             ExitAnalyzeMode();
3945                             ModeHighlight();
3946                       }
3947                       StopClocks();
3948                       gameMode = IcsIdle;
3949                       ics_gamenum = -1;
3950                       ics_user_moved = FALSE;
3951                   }
3952                 continue;
3953             }
3954
3955             if (looking_at(buf, &i, "no longer examining game *")) {
3956                 if (gameMode == IcsExamining &&
3957                     atoi(star_match[0]) == ics_gamenum)
3958                   {
3959                       gameMode = IcsIdle;
3960                       ics_gamenum = -1;
3961                       ics_user_moved = FALSE;
3962                   }
3963                 continue;
3964             }
3965
3966             /* Advance leftover_start past any newlines we find,
3967                so only partial lines can get reparsed */
3968             if (looking_at(buf, &i, "\n")) {
3969                 prevColor = curColor;
3970                 if (curColor != ColorNormal) {
3971                     if (oldi > next_out) {
3972                         SendToPlayer(&buf[next_out], oldi - next_out);
3973                         next_out = oldi;
3974                     }
3975                     Colorize(ColorNormal, FALSE);
3976                     curColor = ColorNormal;
3977                 }
3978                 if (started == STARTED_BOARD) {
3979                     started = STARTED_NONE;
3980                     parse[parse_pos] = NULLCHAR;
3981                     ParseBoard12(parse);
3982                     ics_user_moved = 0;
3983
3984                     /* Send premove here */
3985                     if (appData.premove) {
3986                       char str[MSG_SIZ];
3987                       if (currentMove == 0 &&
3988                           gameMode == IcsPlayingWhite &&
3989                           appData.premoveWhite) {
3990                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3991                         if (appData.debugMode)
3992                           fprintf(debugFP, "Sending premove:\n");
3993                         SendToICS(str);
3994                       } else if (currentMove == 1 &&
3995                                  gameMode == IcsPlayingBlack &&
3996                                  appData.premoveBlack) {
3997                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3998                         if (appData.debugMode)
3999                           fprintf(debugFP, "Sending premove:\n");
4000                         SendToICS(str);
4001                       } else if (gotPremove) {
4002                         gotPremove = 0;
4003                         ClearPremoveHighlights();
4004                         if (appData.debugMode)
4005                           fprintf(debugFP, "Sending premove:\n");
4006                           UserMoveEvent(premoveFromX, premoveFromY,
4007                                         premoveToX, premoveToY,
4008                                         premovePromoChar);
4009                       }
4010                     }
4011
4012                     /* Usually suppress following prompt */
4013                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4014                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4015                         if (looking_at(buf, &i, "*% ")) {
4016                             savingComment = FALSE;
4017                             suppressKibitz = 0;
4018                         }
4019                     }
4020                     next_out = i;
4021                 } else if (started == STARTED_HOLDINGS) {
4022                     int gamenum;
4023                     char new_piece[MSG_SIZ];
4024                     started = STARTED_NONE;
4025                     parse[parse_pos] = NULLCHAR;
4026                     if (appData.debugMode)
4027                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4028                                                         parse, currentMove);
4029                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4030                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4031                         if (gameInfo.variant == VariantNormal) {
4032                           /* [HGM] We seem to switch variant during a game!
4033                            * Presumably no holdings were displayed, so we have
4034                            * to move the position two files to the right to
4035                            * create room for them!
4036                            */
4037                           VariantClass newVariant;
4038                           switch(gameInfo.boardWidth) { // base guess on board width
4039                                 case 9:  newVariant = VariantShogi; break;
4040                                 case 10: newVariant = VariantGreat; break;
4041                                 default: newVariant = VariantCrazyhouse; break;
4042                           }
4043                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4044                           /* Get a move list just to see the header, which
4045                              will tell us whether this is really bug or zh */
4046                           if (ics_getting_history == H_FALSE) {
4047                             ics_getting_history = H_REQUESTED;
4048                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049                             SendToICS(str);
4050                           }
4051                         }
4052                         new_piece[0] = NULLCHAR;
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to board holdings area */
4059                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4060                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4061                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4062 #if ZIPPY
4063                         if (appData.zippyPlay && first.initDone) {
4064                             ZippyHoldings(white_holding, black_holding,
4065                                           new_piece);
4066                         }
4067 #endif /*ZIPPY*/
4068                         if (tinyLayout || smallLayout) {
4069                             char wh[16], bh[16];
4070                             PackHolding(wh, white_holding);
4071                             PackHolding(bh, black_holding);
4072                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4073                                     gameInfo.white, gameInfo.black);
4074                         } else {
4075                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4076                                     gameInfo.white, white_holding, _("vs."),
4077                                     gameInfo.black, black_holding);
4078                         }
4079                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4080                         DrawPosition(FALSE, boards[currentMove]);
4081                         DisplayTitle(str);
4082                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4083                         sscanf(parse, "game %d white [%s black [%s <- %s",
4084                                &gamenum, white_holding, black_holding,
4085                                new_piece);
4086                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4087                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4088                         /* [HGM] copy holdings to partner-board holdings area */
4089                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4090                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4091                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4092                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4093                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4094                       }
4095                     }
4096                     /* Suppress following prompt */
4097                     if (looking_at(buf, &i, "*% ")) {
4098                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4099                         savingComment = FALSE;
4100                         suppressKibitz = 0;
4101                     }
4102                     next_out = i;
4103                 }
4104                 continue;
4105             }
4106
4107             i++;                /* skip unparsed character and loop back */
4108         }
4109
4110         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4111 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4112 //          SendToPlayer(&buf[next_out], i - next_out);
4113             started != STARTED_HOLDINGS && leftover_start > next_out) {
4114             SendToPlayer(&buf[next_out], leftover_start - next_out);
4115             next_out = i;
4116         }
4117
4118         leftover_len = buf_len - leftover_start;
4119         /* if buffer ends with something we couldn't parse,
4120            reparse it after appending the next read */
4121
4122     } else if (count == 0) {
4123         RemoveInputSource(isr);
4124         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4125     } else {
4126         DisplayFatalError(_("Error reading from ICS"), error, 1);
4127     }
4128 }
4129
4130
4131 /* Board style 12 looks like this:
4132
4133    <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
4134
4135  * The "<12> " is stripped before it gets to this routine.  The two
4136  * trailing 0's (flip state and clock ticking) are later addition, and
4137  * some chess servers may not have them, or may have only the first.
4138  * Additional trailing fields may be added in the future.
4139  */
4140
4141 #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"
4142
4143 #define RELATION_OBSERVING_PLAYED    0
4144 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4145 #define RELATION_PLAYING_MYMOVE      1
4146 #define RELATION_PLAYING_NOTMYMOVE  -1
4147 #define RELATION_EXAMINING           2
4148 #define RELATION_ISOLATED_BOARD     -3
4149 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4150
4151 void
4152 ParseBoard12 (char *string)
4153 {
4154     GameMode newGameMode;
4155     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4156     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4157     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4158     char to_play, board_chars[200];
4159     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4160     char black[32], white[32];
4161     Board board;
4162     int prevMove = currentMove;
4163     int ticking = 2;
4164     ChessMove moveType;
4165     int fromX, fromY, toX, toY;
4166     char promoChar;
4167     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4168     char *bookHit = NULL; // [HGM] book
4169     Boolean weird = FALSE, reqFlag = FALSE;
4170
4171     fromX = fromY = toX = toY = -1;
4172
4173     newGame = FALSE;
4174
4175     if (appData.debugMode)
4176       fprintf(debugFP, _("Parsing board: %s\n"), string);
4177
4178     move_str[0] = NULLCHAR;
4179     elapsed_time[0] = NULLCHAR;
4180     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4181         int  i = 0, j;
4182         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4183             if(string[i] == ' ') { ranks++; files = 0; }
4184             else files++;
4185             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4186             i++;
4187         }
4188         for(j = 0; j <i; j++) board_chars[j] = string[j];
4189         board_chars[i] = '\0';
4190         string += i + 1;
4191     }
4192     n = sscanf(string, PATTERN, &to_play, &double_push,
4193                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4194                &gamenum, white, black, &relation, &basetime, &increment,
4195                &white_stren, &black_stren, &white_time, &black_time,
4196                &moveNum, str, elapsed_time, move_str, &ics_flip,
4197                &ticking);
4198
4199     if (n < 21) {
4200         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4201         DisplayError(str, 0);
4202         return;
4203     }
4204
4205     /* Convert the move number to internal form */
4206     moveNum = (moveNum - 1) * 2;
4207     if (to_play == 'B') moveNum++;
4208     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4209       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4210                         0, 1);
4211       return;
4212     }
4213
4214     switch (relation) {
4215       case RELATION_OBSERVING_PLAYED:
4216       case RELATION_OBSERVING_STATIC:
4217         if (gamenum == -1) {
4218             /* Old ICC buglet */
4219             relation = RELATION_OBSERVING_STATIC;
4220         }
4221         newGameMode = IcsObserving;
4222         break;
4223       case RELATION_PLAYING_MYMOVE:
4224       case RELATION_PLAYING_NOTMYMOVE:
4225         newGameMode =
4226           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4227             IcsPlayingWhite : IcsPlayingBlack;
4228         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4229         break;
4230       case RELATION_EXAMINING:
4231         newGameMode = IcsExamining;
4232         break;
4233       case RELATION_ISOLATED_BOARD:
4234       default:
4235         /* Just display this board.  If user was doing something else,
4236            we will forget about it until the next board comes. */
4237         newGameMode = IcsIdle;
4238         break;
4239       case RELATION_STARTING_POSITION:
4240         newGameMode = gameMode;
4241         break;
4242     }
4243
4244     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4245          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4246       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4247       char *toSqr;
4248       for (k = 0; k < ranks; k++) {
4249         for (j = 0; j < files; j++)
4250           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4251         if(gameInfo.holdingsWidth > 1) {
4252              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4253              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4254         }
4255       }
4256       CopyBoard(partnerBoard, board);
4257       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4258         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4259         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4260       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4261       if(toSqr = strchr(str, '-')) {
4262         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4263         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4264       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4265       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4266       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4267       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4268       if(twoBoards) {
4269           DisplayWhiteClock(white_time, to_play == 'W');
4270           DisplayBlackClock(black_time, to_play != 'W');
4271                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4272       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4273                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4274       DisplayMessage(partnerStatus, "");
4275         partnerBoardValid = TRUE;
4276       return;
4277     }
4278
4279     /* Modify behavior for initial board display on move listing
4280        of wild games.
4281        */
4282     switch (ics_getting_history) {
4283       case H_FALSE:
4284       case H_REQUESTED:
4285         break;
4286       case H_GOT_REQ_HEADER:
4287       case H_GOT_UNREQ_HEADER:
4288         /* This is the initial position of the current game */
4289         gamenum = ics_gamenum;
4290         moveNum = 0;            /* old ICS bug workaround */
4291         if (to_play == 'B') {
4292           startedFromSetupPosition = TRUE;
4293           blackPlaysFirst = TRUE;
4294           moveNum = 1;
4295           if (forwardMostMove == 0) forwardMostMove = 1;
4296           if (backwardMostMove == 0) backwardMostMove = 1;
4297           if (currentMove == 0) currentMove = 1;
4298         }
4299         newGameMode = gameMode;
4300         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4301         break;
4302       case H_GOT_UNWANTED_HEADER:
4303         /* This is an initial board that we don't want */
4304         return;
4305       case H_GETTING_MOVES:
4306         /* Should not happen */
4307         DisplayError(_("Error gathering move list: extra board"), 0);
4308         ics_getting_history = H_FALSE;
4309         return;
4310     }
4311
4312    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4313                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4314      /* [HGM] We seem to have switched variant unexpectedly
4315       * Try to guess new variant from board size
4316       */
4317           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4318           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4319           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4320           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4321           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4322           if(!weird) newVariant = VariantNormal;
4323           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4324           /* Get a move list just to see the header, which
4325              will tell us whether this is really bug or zh */
4326           if (ics_getting_history == H_FALSE) {
4327             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4328             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4329             SendToICS(str);
4330           }
4331     }
4332
4333     /* Take action if this is the first board of a new game, or of a
4334        different game than is currently being displayed.  */
4335     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4336         relation == RELATION_ISOLATED_BOARD) {
4337
4338         /* Forget the old game and get the history (if any) of the new one */
4339         if (gameMode != BeginningOfGame) {
4340           Reset(TRUE, TRUE);
4341         }
4342         newGame = TRUE;
4343         if (appData.autoRaiseBoard) BoardToTop();
4344         prevMove = -3;
4345         if (gamenum == -1) {
4346             newGameMode = IcsIdle;
4347         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4348                    appData.getMoveList && !reqFlag) {
4349             /* Need to get game history */
4350             ics_getting_history = H_REQUESTED;
4351             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4352             SendToICS(str);
4353         }
4354
4355         /* Initially flip the board to have black on the bottom if playing
4356            black or if the ICS flip flag is set, but let the user change
4357            it with the Flip View button. */
4358         flipView = appData.autoFlipView ?
4359           (newGameMode == IcsPlayingBlack) || ics_flip :
4360           appData.flipView;
4361
4362         /* Done with values from previous mode; copy in new ones */
4363         gameMode = newGameMode;
4364         ModeHighlight();
4365         ics_gamenum = gamenum;
4366         if (gamenum == gs_gamenum) {
4367             int klen = strlen(gs_kind);
4368             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4369             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4370             gameInfo.event = StrSave(str);
4371         } else {
4372             gameInfo.event = StrSave("ICS game");
4373         }
4374         gameInfo.site = StrSave(appData.icsHost);
4375         gameInfo.date = PGNDate();
4376         gameInfo.round = StrSave("-");
4377         gameInfo.white = StrSave(white);
4378         gameInfo.black = StrSave(black);
4379         timeControl = basetime * 60 * 1000;
4380         timeControl_2 = 0;
4381         timeIncrement = increment * 1000;
4382         movesPerSession = 0;
4383         gameInfo.timeControl = TimeControlTagValue();
4384         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4385   if (appData.debugMode) {
4386     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4387     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4388     setbuf(debugFP, NULL);
4389   }
4390
4391         gameInfo.outOfBook = NULL;
4392
4393         /* Do we have the ratings? */
4394         if (strcmp(player1Name, white) == 0 &&
4395             strcmp(player2Name, black) == 0) {
4396             if (appData.debugMode)
4397               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4398                       player1Rating, player2Rating);
4399             gameInfo.whiteRating = player1Rating;
4400             gameInfo.blackRating = player2Rating;
4401         } else if (strcmp(player2Name, white) == 0 &&
4402                    strcmp(player1Name, black) == 0) {
4403             if (appData.debugMode)
4404               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4405                       player2Rating, player1Rating);
4406             gameInfo.whiteRating = player2Rating;
4407             gameInfo.blackRating = player1Rating;
4408         }
4409         player1Name[0] = player2Name[0] = NULLCHAR;
4410
4411         /* Silence shouts if requested */
4412         if (appData.quietPlay &&
4413             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4414             SendToICS(ics_prefix);
4415             SendToICS("set shout 0\n");
4416         }
4417     }
4418
4419     /* Deal with midgame name changes */
4420     if (!newGame) {
4421         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4422             if (gameInfo.white) free(gameInfo.white);
4423             gameInfo.white = StrSave(white);
4424         }
4425         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4426             if (gameInfo.black) free(gameInfo.black);
4427             gameInfo.black = StrSave(black);
4428         }
4429     }
4430
4431     /* Throw away game result if anything actually changes in examine mode */
4432     if (gameMode == IcsExamining && !newGame) {
4433         gameInfo.result = GameUnfinished;
4434         if (gameInfo.resultDetails != NULL) {
4435             free(gameInfo.resultDetails);
4436             gameInfo.resultDetails = NULL;
4437         }
4438     }
4439
4440     /* In pausing && IcsExamining mode, we ignore boards coming
4441        in if they are in a different variation than we are. */
4442     if (pauseExamInvalid) return;
4443     if (pausing && gameMode == IcsExamining) {
4444         if (moveNum <= pauseExamForwardMostMove) {
4445             pauseExamInvalid = TRUE;
4446             forwardMostMove = pauseExamForwardMostMove;
4447             return;
4448         }
4449     }
4450
4451   if (appData.debugMode) {
4452     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4453   }
4454     /* Parse the board */
4455     for (k = 0; k < ranks; k++) {
4456       for (j = 0; j < files; j++)
4457         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4458       if(gameInfo.holdingsWidth > 1) {
4459            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4460            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4461       }
4462     }
4463     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4464       board[5][BOARD_RGHT+1] = WhiteAngel;
4465       board[6][BOARD_RGHT+1] = WhiteMarshall;
4466       board[1][0] = BlackMarshall;
4467       board[2][0] = BlackAngel;
4468       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4469     }
4470     CopyBoard(boards[moveNum], board);
4471     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4472     if (moveNum == 0) {
4473         startedFromSetupPosition =
4474           !CompareBoards(board, initialPosition);
4475         if(startedFromSetupPosition)
4476             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4477     }
4478
4479     /* [HGM] Set castling rights. Take the outermost Rooks,
4480        to make it also work for FRC opening positions. Note that board12
4481        is really defective for later FRC positions, as it has no way to
4482        indicate which Rook can castle if they are on the same side of King.
4483        For the initial position we grant rights to the outermost Rooks,
4484        and remember thos rights, and we then copy them on positions
4485        later in an FRC game. This means WB might not recognize castlings with
4486        Rooks that have moved back to their original position as illegal,
4487        but in ICS mode that is not its job anyway.
4488     */
4489     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4490     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4491
4492         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4493             if(board[0][i] == WhiteRook) j = i;
4494         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4495         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4496             if(board[0][i] == WhiteRook) j = i;
4497         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4498         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4499             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4500         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4501         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4502             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4503         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4504
4505         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4506         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4507         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4508             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4509         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4510             if(board[BOARD_HEIGHT-1][k] == bKing)
4511                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4512         if(gameInfo.variant == VariantTwoKings) {
4513             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4514             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4515             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4516         }
4517     } else { int r;
4518         r = boards[moveNum][CASTLING][0] = initialRights[0];
4519         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4520         r = boards[moveNum][CASTLING][1] = initialRights[1];
4521         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4522         r = boards[moveNum][CASTLING][3] = initialRights[3];
4523         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4524         r = boards[moveNum][CASTLING][4] = initialRights[4];
4525         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4526         /* wildcastle kludge: always assume King has rights */
4527         r = boards[moveNum][CASTLING][2] = initialRights[2];
4528         r = boards[moveNum][CASTLING][5] = initialRights[5];
4529     }
4530     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4531     boards[moveNum][EP_STATUS] = EP_NONE;
4532     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4533     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4534     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4535
4536
4537     if (ics_getting_history == H_GOT_REQ_HEADER ||
4538         ics_getting_history == H_GOT_UNREQ_HEADER) {
4539         /* This was an initial position from a move list, not
4540            the current position */
4541         return;
4542     }
4543
4544     /* Update currentMove and known move number limits */
4545     newMove = newGame || moveNum > forwardMostMove;
4546
4547     if (newGame) {
4548         forwardMostMove = backwardMostMove = currentMove = moveNum;
4549         if (gameMode == IcsExamining && moveNum == 0) {
4550           /* Workaround for ICS limitation: we are not told the wild
4551              type when starting to examine a game.  But if we ask for
4552              the move list, the move list header will tell us */
4553             ics_getting_history = H_REQUESTED;
4554             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4555             SendToICS(str);
4556         }
4557     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4558                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4559 #if ZIPPY
4560         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4561         /* [HGM] applied this also to an engine that is silently watching        */
4562         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4563             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4564             gameInfo.variant == currentlyInitializedVariant) {
4565           takeback = forwardMostMove - moveNum;
4566           for (i = 0; i < takeback; i++) {
4567             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4568             SendToProgram("undo\n", &first);
4569           }
4570         }
4571 #endif
4572
4573         forwardMostMove = moveNum;
4574         if (!pausing || currentMove > forwardMostMove)
4575           currentMove = forwardMostMove;
4576     } else {
4577         /* New part of history that is not contiguous with old part */
4578         if (pausing && gameMode == IcsExamining) {
4579             pauseExamInvalid = TRUE;
4580             forwardMostMove = pauseExamForwardMostMove;
4581             return;
4582         }
4583         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4584 #if ZIPPY
4585             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4586                 // [HGM] when we will receive the move list we now request, it will be
4587                 // fed to the engine from the first move on. So if the engine is not
4588                 // in the initial position now, bring it there.
4589                 InitChessProgram(&first, 0);
4590             }
4591 #endif
4592             ics_getting_history = H_REQUESTED;
4593             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4594             SendToICS(str);
4595         }
4596         forwardMostMove = backwardMostMove = currentMove = moveNum;
4597     }
4598
4599     /* Update the clocks */
4600     if (strchr(elapsed_time, '.')) {
4601       /* Time is in ms */
4602       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4603       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4604     } else {
4605       /* Time is in seconds */
4606       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4607       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4608     }
4609
4610
4611 #if ZIPPY
4612     if (appData.zippyPlay && newGame &&
4613         gameMode != IcsObserving && gameMode != IcsIdle &&
4614         gameMode != IcsExamining)
4615       ZippyFirstBoard(moveNum, basetime, increment);
4616 #endif
4617
4618     /* Put the move on the move list, first converting
4619        to canonical algebraic form. */
4620     if (moveNum > 0) {
4621   if (appData.debugMode) {
4622     if (appData.debugMode) { int f = forwardMostMove;
4623         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4624                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4625                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4626     }
4627     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4628     fprintf(debugFP, "moveNum = %d\n", moveNum);
4629     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4630     setbuf(debugFP, NULL);
4631   }
4632         if (moveNum <= backwardMostMove) {
4633             /* We don't know what the board looked like before
4634                this move.  Punt. */
4635           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4636             strcat(parseList[moveNum - 1], " ");
4637             strcat(parseList[moveNum - 1], elapsed_time);
4638             moveList[moveNum - 1][0] = NULLCHAR;
4639         } else if (strcmp(move_str, "none") == 0) {
4640             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4641             /* Again, we don't know what the board looked like;
4642                this is really the start of the game. */
4643             parseList[moveNum - 1][0] = NULLCHAR;
4644             moveList[moveNum - 1][0] = NULLCHAR;
4645             backwardMostMove = moveNum;
4646             startedFromSetupPosition = TRUE;
4647             fromX = fromY = toX = toY = -1;
4648         } else {
4649           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4650           //                 So we parse the long-algebraic move string in stead of the SAN move
4651           int valid; char buf[MSG_SIZ], *prom;
4652
4653           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4654                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4655           // str looks something like "Q/a1-a2"; kill the slash
4656           if(str[1] == '/')
4657             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4658           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4659           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4660                 strcat(buf, prom); // long move lacks promo specification!
4661           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4662                 if(appData.debugMode)
4663                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4664                 safeStrCpy(move_str, buf, MSG_SIZ);
4665           }
4666           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4667                                 &fromX, &fromY, &toX, &toY, &promoChar)
4668                || ParseOneMove(buf, moveNum - 1, &moveType,
4669                                 &fromX, &fromY, &toX, &toY, &promoChar);
4670           // end of long SAN patch
4671           if (valid) {
4672             (void) CoordsToAlgebraic(boards[moveNum - 1],
4673                                      PosFlags(moveNum - 1),
4674                                      fromY, fromX, toY, toX, promoChar,
4675                                      parseList[moveNum-1]);
4676             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4677               case MT_NONE:
4678               case MT_STALEMATE:
4679               default:
4680                 break;
4681               case MT_CHECK:
4682                 if(gameInfo.variant != VariantShogi)
4683                     strcat(parseList[moveNum - 1], "+");
4684                 break;
4685               case MT_CHECKMATE:
4686               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4687                 strcat(parseList[moveNum - 1], "#");
4688                 break;
4689             }
4690             strcat(parseList[moveNum - 1], " ");
4691             strcat(parseList[moveNum - 1], elapsed_time);
4692             /* currentMoveString is set as a side-effect of ParseOneMove */
4693             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4694             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4695             strcat(moveList[moveNum - 1], "\n");
4696
4697             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4698                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4699               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4700                 ChessSquare old, new = boards[moveNum][k][j];
4701                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4702                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4703                   if(old == new) continue;
4704                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4705                   else if(new == WhiteWazir || new == BlackWazir) {
4706                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4707                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4708                       else boards[moveNum][k][j] = old; // preserve type of Gold
4709                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4710                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4711               }
4712           } else {
4713             /* Move from ICS was illegal!?  Punt. */
4714             if (appData.debugMode) {
4715               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4716               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4717             }
4718             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4719             strcat(parseList[moveNum - 1], " ");
4720             strcat(parseList[moveNum - 1], elapsed_time);
4721             moveList[moveNum - 1][0] = NULLCHAR;
4722             fromX = fromY = toX = toY = -1;
4723           }
4724         }
4725   if (appData.debugMode) {
4726     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4727     setbuf(debugFP, NULL);
4728   }
4729
4730 #if ZIPPY
4731         /* Send move to chess program (BEFORE animating it). */
4732         if (appData.zippyPlay && !newGame && newMove &&
4733            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4734
4735             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4736                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4737                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4738                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4739                             move_str);
4740                     DisplayError(str, 0);
4741                 } else {
4742                     if (first.sendTime) {
4743                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4744                     }
4745                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4746                     if (firstMove && !bookHit) {
4747                         firstMove = FALSE;
4748                         if (first.useColors) {
4749                           SendToProgram(gameMode == IcsPlayingWhite ?
4750                                         "white\ngo\n" :
4751                                         "black\ngo\n", &first);
4752                         } else {
4753                           SendToProgram("go\n", &first);
4754                         }
4755                         first.maybeThinking = TRUE;
4756                     }
4757                 }
4758             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4759               if (moveList[moveNum - 1][0] == NULLCHAR) {
4760                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4761                 DisplayError(str, 0);
4762               } else {
4763                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4764                 SendMoveToProgram(moveNum - 1, &first);
4765               }
4766             }
4767         }
4768 #endif
4769     }
4770
4771     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4772         /* If move comes from a remote source, animate it.  If it
4773            isn't remote, it will have already been animated. */
4774         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4775             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4776         }
4777         if (!pausing && appData.highlightLastMove) {
4778             SetHighlights(fromX, fromY, toX, toY);
4779         }
4780     }
4781
4782     /* Start the clocks */
4783     whiteFlag = blackFlag = FALSE;
4784     appData.clockMode = !(basetime == 0 && increment == 0);
4785     if (ticking == 0) {
4786       ics_clock_paused = TRUE;
4787       StopClocks();
4788     } else if (ticking == 1) {
4789       ics_clock_paused = FALSE;
4790     }
4791     if (gameMode == IcsIdle ||
4792         relation == RELATION_OBSERVING_STATIC ||
4793         relation == RELATION_EXAMINING ||
4794         ics_clock_paused)
4795       DisplayBothClocks();
4796     else
4797       StartClocks();
4798
4799     /* Display opponents and material strengths */
4800     if (gameInfo.variant != VariantBughouse &&
4801         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4802         if (tinyLayout || smallLayout) {
4803             if(gameInfo.variant == VariantNormal)
4804               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4805                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4806                     basetime, increment);
4807             else
4808               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4809                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4810                     basetime, increment, (int) gameInfo.variant);
4811         } else {
4812             if(gameInfo.variant == VariantNormal)
4813               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4814                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4815                     basetime, increment);
4816             else
4817               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4818                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4819                     basetime, increment, VariantName(gameInfo.variant));
4820         }
4821         DisplayTitle(str);
4822   if (appData.debugMode) {
4823     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4824   }
4825     }
4826
4827
4828     /* Display the board */
4829     if (!pausing && !appData.noGUI) {
4830
4831       if (appData.premove)
4832           if (!gotPremove ||
4833              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4834              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4835               ClearPremoveHighlights();
4836
4837       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4838         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4839       DrawPosition(j, boards[currentMove]);
4840
4841       DisplayMove(moveNum - 1);
4842       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4843             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4844               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4845         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4846       }
4847     }
4848
4849     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4850 #if ZIPPY
4851     if(bookHit) { // [HGM] book: simulate book reply
4852         static char bookMove[MSG_SIZ]; // a bit generous?
4853
4854         programStats.nodes = programStats.depth = programStats.time =
4855         programStats.score = programStats.got_only_move = 0;
4856         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4857
4858         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4859         strcat(bookMove, bookHit);
4860         HandleMachineMove(bookMove, &first);
4861     }
4862 #endif
4863 }
4864
4865 void
4866 GetMoveListEvent ()
4867 {
4868     char buf[MSG_SIZ];
4869     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4870         ics_getting_history = H_REQUESTED;
4871         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4872         SendToICS(buf);
4873     }
4874 }
4875
4876 void
4877 AnalysisPeriodicEvent (int force)
4878 {
4879     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4880          && !force) || !appData.periodicUpdates)
4881       return;
4882
4883     /* Send . command to Crafty to collect stats */
4884     SendToProgram(".\n", &first);
4885
4886     /* Don't send another until we get a response (this makes
4887        us stop sending to old Crafty's which don't understand
4888        the "." command (sending illegal cmds resets node count & time,
4889        which looks bad)) */
4890     programStats.ok_to_send = 0;
4891 }
4892
4893 void
4894 ics_update_width (int new_width)
4895 {
4896         ics_printf("set width %d\n", new_width);
4897 }
4898
4899 void
4900 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4901 {
4902     char buf[MSG_SIZ];
4903
4904     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4905         // null move in variant where engine does not understand it (for analysis purposes)
4906         SendBoard(cps, moveNum + 1); // send position after move in stead.
4907         return;
4908     }
4909     if (cps->useUsermove) {
4910       SendToProgram("usermove ", cps);
4911     }
4912     if (cps->useSAN) {
4913       char *space;
4914       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4915         int len = space - parseList[moveNum];
4916         memcpy(buf, parseList[moveNum], len);
4917         buf[len++] = '\n';
4918         buf[len] = NULLCHAR;
4919       } else {
4920         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4921       }
4922       SendToProgram(buf, cps);
4923     } else {
4924       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4925         AlphaRank(moveList[moveNum], 4);
4926         SendToProgram(moveList[moveNum], cps);
4927         AlphaRank(moveList[moveNum], 4); // and back
4928       } else
4929       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4930        * the engine. It would be nice to have a better way to identify castle
4931        * moves here. */
4932       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4933                                                                          && cps->useOOCastle) {
4934         int fromX = moveList[moveNum][0] - AAA;
4935         int fromY = moveList[moveNum][1] - ONE;
4936         int toX = moveList[moveNum][2] - AAA;
4937         int toY = moveList[moveNum][3] - ONE;
4938         if((boards[moveNum][fromY][fromX] == WhiteKing
4939             && boards[moveNum][toY][toX] == WhiteRook)
4940            || (boards[moveNum][fromY][fromX] == BlackKing
4941                && boards[moveNum][toY][toX] == BlackRook)) {
4942           if(toX > fromX) SendToProgram("O-O\n", cps);
4943           else SendToProgram("O-O-O\n", cps);
4944         }
4945         else SendToProgram(moveList[moveNum], cps);
4946       } else
4947       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4948         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4949           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4950           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4951                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4952         } else
4953           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4954                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4955         SendToProgram(buf, cps);
4956       }
4957       else SendToProgram(moveList[moveNum], cps);
4958       /* End of additions by Tord */
4959     }
4960
4961     /* [HGM] setting up the opening has brought engine in force mode! */
4962     /*       Send 'go' if we are in a mode where machine should play. */
4963     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4964         (gameMode == TwoMachinesPlay   ||
4965 #if ZIPPY
4966          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4967 #endif
4968          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4969         SendToProgram("go\n", cps);
4970   if (appData.debugMode) {
4971     fprintf(debugFP, "(extra)\n");
4972   }
4973     }
4974     setboardSpoiledMachineBlack = 0;
4975 }
4976
4977 void
4978 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4979 {
4980     char user_move[MSG_SIZ];
4981     char suffix[4];
4982
4983     if(gameInfo.variant == VariantSChess && promoChar) {
4984         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4985         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4986     } else suffix[0] = NULLCHAR;
4987
4988     switch (moveType) {
4989       default:
4990         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4991                 (int)moveType, fromX, fromY, toX, toY);
4992         DisplayError(user_move + strlen("say "), 0);
4993         break;
4994       case WhiteKingSideCastle:
4995       case BlackKingSideCastle:
4996       case WhiteQueenSideCastleWild:
4997       case BlackQueenSideCastleWild:
4998       /* PUSH Fabien */
4999       case WhiteHSideCastleFR:
5000       case BlackHSideCastleFR:
5001       /* POP Fabien */
5002         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5003         break;
5004       case WhiteQueenSideCastle:
5005       case BlackQueenSideCastle:
5006       case WhiteKingSideCastleWild:
5007       case BlackKingSideCastleWild:
5008       /* PUSH Fabien */
5009       case WhiteASideCastleFR:
5010       case BlackASideCastleFR:
5011       /* POP Fabien */
5012         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5013         break;
5014       case WhiteNonPromotion:
5015       case BlackNonPromotion:
5016         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5017         break;
5018       case WhitePromotion:
5019       case BlackPromotion:
5020         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5021           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5022                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5023                 PieceToChar(WhiteFerz));
5024         else if(gameInfo.variant == VariantGreat)
5025           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5026                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5027                 PieceToChar(WhiteMan));
5028         else
5029           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5030                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5031                 promoChar);
5032         break;
5033       case WhiteDrop:
5034       case BlackDrop:
5035       drop:
5036         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5037                  ToUpper(PieceToChar((ChessSquare) fromX)),
5038                  AAA + toX, ONE + toY);
5039         break;
5040       case IllegalMove:  /* could be a variant we don't quite understand */
5041         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5042       case NormalMove:
5043       case WhiteCapturesEnPassant:
5044       case BlackCapturesEnPassant:
5045         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5046                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5047         break;
5048     }
5049     SendToICS(user_move);
5050     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5051         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5052 }
5053
5054 void
5055 UploadGameEvent ()
5056 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5057     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5058     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5059     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5060       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5061       return;
5062     }
5063     if(gameMode != IcsExamining) { // is this ever not the case?
5064         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5065
5066         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5067           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5068         } else { // on FICS we must first go to general examine mode
5069           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5070         }
5071         if(gameInfo.variant != VariantNormal) {
5072             // try figure out wild number, as xboard names are not always valid on ICS
5073             for(i=1; i<=36; i++) {
5074               snprintf(buf, MSG_SIZ, "wild/%d", i);
5075                 if(StringToVariant(buf) == gameInfo.variant) break;
5076             }
5077             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5078             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5079             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5080         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5081         SendToICS(ics_prefix);
5082         SendToICS(buf);
5083         if(startedFromSetupPosition || backwardMostMove != 0) {
5084           fen = PositionToFEN(backwardMostMove, NULL);
5085           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5086             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5087             SendToICS(buf);
5088           } else { // FICS: everything has to set by separate bsetup commands
5089             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5090             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5091             SendToICS(buf);
5092             if(!WhiteOnMove(backwardMostMove)) {
5093                 SendToICS("bsetup tomove black\n");
5094             }
5095             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5096             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5097             SendToICS(buf);
5098             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5099             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5100             SendToICS(buf);
5101             i = boards[backwardMostMove][EP_STATUS];
5102             if(i >= 0) { // set e.p.
5103               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5104                 SendToICS(buf);
5105             }
5106             bsetup++;
5107           }
5108         }
5109       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5110             SendToICS("bsetup done\n"); // switch to normal examining.
5111     }
5112     for(i = backwardMostMove; i<last; i++) {
5113         char buf[20];
5114         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5115         SendToICS(buf);
5116     }
5117     SendToICS(ics_prefix);
5118     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5119 }
5120
5121 void
5122 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5123 {
5124     if (rf == DROP_RANK) {
5125       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5126       sprintf(move, "%c@%c%c\n",
5127                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5128     } else {
5129         if (promoChar == 'x' || promoChar == NULLCHAR) {
5130           sprintf(move, "%c%c%c%c\n",
5131                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5132         } else {
5133             sprintf(move, "%c%c%c%c%c\n",
5134                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5135         }
5136     }
5137 }
5138
5139 void
5140 ProcessICSInitScript (FILE *f)
5141 {
5142     char buf[MSG_SIZ];
5143
5144     while (fgets(buf, MSG_SIZ, f)) {
5145         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5146     }
5147
5148     fclose(f);
5149 }
5150
5151
5152 static int lastX, lastY, selectFlag, dragging;
5153
5154 void
5155 Sweep (int step)
5156 {
5157     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5158     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5159     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5160     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5161     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5162     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5163     do {
5164         promoSweep -= step;
5165         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5166         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5167         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5168         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5169         if(!step) step = -1;
5170     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5171             appData.testLegality && (promoSweep == king ||
5172             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5173     ChangeDragPiece(promoSweep);
5174 }
5175
5176 int
5177 PromoScroll (int x, int y)
5178 {
5179   int step = 0;
5180
5181   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5182   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5183   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5184   if(!step) return FALSE;
5185   lastX = x; lastY = y;
5186   if((promoSweep < BlackPawn) == flipView) step = -step;
5187   if(step > 0) selectFlag = 1;
5188   if(!selectFlag) Sweep(step);
5189   return FALSE;
5190 }
5191
5192 void
5193 NextPiece (int step)
5194 {
5195     ChessSquare piece = boards[currentMove][toY][toX];
5196     do {
5197         pieceSweep -= step;
5198         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5199         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5200         if(!step) step = -1;
5201     } while(PieceToChar(pieceSweep) == '.');
5202     boards[currentMove][toY][toX] = pieceSweep;
5203     DrawPosition(FALSE, boards[currentMove]);
5204     boards[currentMove][toY][toX] = piece;
5205 }
5206 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5207 void
5208 AlphaRank (char *move, int n)
5209 {
5210 //    char *p = move, c; int x, y;
5211
5212     if (appData.debugMode) {
5213         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5214     }
5215
5216     if(move[1]=='*' &&
5217        move[2]>='0' && move[2]<='9' &&
5218        move[3]>='a' && move[3]<='x'    ) {
5219         move[1] = '@';
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[0]>='0' && move[0]<='9' &&
5224        move[1]>='a' && move[1]<='x' &&
5225        move[2]>='0' && move[2]<='9' &&
5226        move[3]>='a' && move[3]<='x'    ) {
5227         /* input move, Shogi -> normal */
5228         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5229         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5230         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5231         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5232     } else
5233     if(move[1]=='@' &&
5234        move[3]>='0' && move[3]<='9' &&
5235        move[2]>='a' && move[2]<='x'    ) {
5236         move[1] = '*';
5237         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5238         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5239     } else
5240     if(
5241        move[0]>='a' && move[0]<='x' &&
5242        move[3]>='0' && move[3]<='9' &&
5243        move[2]>='a' && move[2]<='x'    ) {
5244          /* output move, normal -> Shogi */
5245         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5246         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5247         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5248         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5249         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5250     }
5251     if (appData.debugMode) {
5252         fprintf(debugFP, "   out = '%s'\n", move);
5253     }
5254 }
5255
5256 char yy_textstr[8000];
5257
5258 /* Parser for moves from gnuchess, ICS, or user typein box */
5259 Boolean
5260 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5261 {
5262     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5263
5264     switch (*moveType) {
5265       case WhitePromotion:
5266       case BlackPromotion:
5267       case WhiteNonPromotion:
5268       case BlackNonPromotion:
5269       case NormalMove:
5270       case WhiteCapturesEnPassant:
5271       case BlackCapturesEnPassant:
5272       case WhiteKingSideCastle:
5273       case WhiteQueenSideCastle:
5274       case BlackKingSideCastle:
5275       case BlackQueenSideCastle:
5276       case WhiteKingSideCastleWild:
5277       case WhiteQueenSideCastleWild:
5278       case BlackKingSideCastleWild:
5279       case BlackQueenSideCastleWild:
5280       /* Code added by Tord: */
5281       case WhiteHSideCastleFR:
5282       case WhiteASideCastleFR:
5283       case BlackHSideCastleFR:
5284       case BlackASideCastleFR:
5285       /* End of code added by Tord */
5286       case IllegalMove:         /* bug or odd chess variant */
5287         *fromX = currentMoveString[0] - AAA;
5288         *fromY = currentMoveString[1] - ONE;
5289         *toX = currentMoveString[2] - AAA;
5290         *toY = currentMoveString[3] - ONE;
5291         *promoChar = currentMoveString[4];
5292         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5293             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5294     if (appData.debugMode) {
5295         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5296     }
5297             *fromX = *fromY = *toX = *toY = 0;
5298             return FALSE;
5299         }
5300         if (appData.testLegality) {
5301           return (*moveType != IllegalMove);
5302         } else {
5303           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5304                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5305         }
5306
5307       case WhiteDrop:
5308       case BlackDrop:
5309         *fromX = *moveType == WhiteDrop ?
5310           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5311           (int) CharToPiece(ToLower(currentMoveString[0]));
5312         *fromY = DROP_RANK;
5313         *toX = currentMoveString[2] - AAA;
5314         *toY = currentMoveString[3] - ONE;
5315         *promoChar = NULLCHAR;
5316         return TRUE;
5317
5318       case AmbiguousMove:
5319       case ImpossibleMove:
5320       case EndOfFile:
5321       case ElapsedTime:
5322       case Comment:
5323       case PGNTag:
5324       case NAG:
5325       case WhiteWins:
5326       case BlackWins:
5327       case GameIsDrawn:
5328       default:
5329     if (appData.debugMode) {
5330         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5331     }
5332         /* bug? */
5333         *fromX = *fromY = *toX = *toY = 0;
5334         *promoChar = NULLCHAR;
5335         return FALSE;
5336     }
5337 }
5338
5339 Boolean pushed = FALSE;
5340 char *lastParseAttempt;
5341
5342 void
5343 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5344 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5345   int fromX, fromY, toX, toY; char promoChar;
5346   ChessMove moveType;
5347   Boolean valid;
5348   int nr = 0;
5349
5350   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5351     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5352     pushed = TRUE;
5353   }
5354   endPV = forwardMostMove;
5355   do {
5356     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5357     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5358     lastParseAttempt = pv;
5359     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5360     if(!valid && nr == 0 &&
5361        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5362         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5363         // Hande case where played move is different from leading PV move
5364         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5365         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5366         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5367         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5368           endPV += 2; // if position different, keep this
5369           moveList[endPV-1][0] = fromX + AAA;
5370           moveList[endPV-1][1] = fromY + ONE;
5371           moveList[endPV-1][2] = toX + AAA;
5372           moveList[endPV-1][3] = toY + ONE;
5373           parseList[endPV-1][0] = NULLCHAR;
5374           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5375         }
5376       }
5377     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5378     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5379     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5380     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5381         valid++; // allow comments in PV
5382         continue;
5383     }
5384     nr++;
5385     if(endPV+1 > framePtr) break; // no space, truncate
5386     if(!valid) break;
5387     endPV++;
5388     CopyBoard(boards[endPV], boards[endPV-1]);
5389     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5390     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5391     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5392     CoordsToAlgebraic(boards[endPV - 1],
5393                              PosFlags(endPV - 1),
5394                              fromY, fromX, toY, toX, promoChar,
5395                              parseList[endPV - 1]);
5396   } while(valid);
5397   if(atEnd == 2) return; // used hidden, for PV conversion
5398   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5399   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5400   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5401                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5402   DrawPosition(TRUE, boards[currentMove]);
5403 }
5404
5405 int
5406 MultiPV (ChessProgramState *cps)
5407 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5408         int i;
5409         for(i=0; i<cps->nrOptions; i++)
5410             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5411                 return i;
5412         return -1;
5413 }
5414
5415 Boolean
5416 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5417 {
5418         int startPV, multi, lineStart, origIndex = index;
5419         char *p, buf2[MSG_SIZ];
5420
5421         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5422         lastX = x; lastY = y;
5423         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5424         lineStart = startPV = index;
5425         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5426         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5427         index = startPV;
5428         do{ while(buf[index] && buf[index] != '\n') index++;
5429         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5430         buf[index] = 0;
5431         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5432                 int n = first.option[multi].value;
5433                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5434                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5435                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5436                 first.option[multi].value = n;
5437                 *start = *end = 0;
5438                 return FALSE;
5439         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5440                 ExcludeClick(origIndex - lineStart);
5441                 return FALSE;
5442         }
5443         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5444         *start = startPV; *end = index-1;
5445         return TRUE;
5446 }
5447
5448 char *
5449 PvToSAN (char *pv)
5450 {
5451         static char buf[10*MSG_SIZ];
5452         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5453         *buf = NULLCHAR;
5454         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5455         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5456         for(i = forwardMostMove; i<endPV; i++){
5457             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5458             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5459             k += strlen(buf+k);
5460         }
5461         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5462         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5463         endPV = savedEnd;
5464         return buf;
5465 }
5466
5467 Boolean
5468 LoadPV (int x, int y)
5469 { // called on right mouse click to load PV
5470   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5471   lastX = x; lastY = y;
5472   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5473   return TRUE;
5474 }
5475
5476 void
5477 UnLoadPV ()
5478 {
5479   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5480   if(endPV < 0) return;
5481   if(appData.autoCopyPV) CopyFENToClipboard();
5482   endPV = -1;
5483   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5484         Boolean saveAnimate = appData.animate;
5485         if(pushed) {
5486             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5487                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5488             } else storedGames--; // abandon shelved tail of original game
5489         }
5490         pushed = FALSE;
5491         forwardMostMove = currentMove;
5492         currentMove = oldFMM;
5493         appData.animate = FALSE;
5494         ToNrEvent(forwardMostMove);
5495         appData.animate = saveAnimate;
5496   }
5497   currentMove = forwardMostMove;
5498   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5499   ClearPremoveHighlights();
5500   DrawPosition(TRUE, boards[currentMove]);
5501 }
5502
5503 void
5504 MovePV (int x, int y, int h)
5505 { // step through PV based on mouse coordinates (called on mouse move)
5506   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5507
5508   // we must somehow check if right button is still down (might be released off board!)
5509   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5510   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5511   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5512   if(!step) return;
5513   lastX = x; lastY = y;
5514
5515   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5516   if(endPV < 0) return;
5517   if(y < margin) step = 1; else
5518   if(y > h - margin) step = -1;
5519   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5520   currentMove += step;
5521   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5522   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5523                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5524   DrawPosition(FALSE, boards[currentMove]);
5525 }
5526
5527
5528 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5529 // All positions will have equal probability, but the current method will not provide a unique
5530 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5531 #define DARK 1
5532 #define LITE 2
5533 #define ANY 3
5534
5535 int squaresLeft[4];
5536 int piecesLeft[(int)BlackPawn];
5537 int seed, nrOfShuffles;
5538
5539 void
5540 GetPositionNumber ()
5541 {       // sets global variable seed
5542         int i;
5543
5544         seed = appData.defaultFrcPosition;
5545         if(seed < 0) { // randomize based on time for negative FRC position numbers
5546                 for(i=0; i<50; i++) seed += random();
5547                 seed = random() ^ random() >> 8 ^ random() << 8;
5548                 if(seed<0) seed = -seed;
5549         }
5550 }
5551
5552 int
5553 put (Board board, int pieceType, int rank, int n, int shade)
5554 // put the piece on the (n-1)-th empty squares of the given shade
5555 {
5556         int i;
5557
5558         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5559                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5560                         board[rank][i] = (ChessSquare) pieceType;
5561                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5562                         squaresLeft[ANY]--;
5563                         piecesLeft[pieceType]--;
5564                         return i;
5565                 }
5566         }
5567         return -1;
5568 }
5569
5570
5571 void
5572 AddOnePiece (Board board, int pieceType, int rank, int shade)
5573 // calculate where the next piece goes, (any empty square), and put it there
5574 {
5575         int i;
5576
5577         i = seed % squaresLeft[shade];
5578         nrOfShuffles *= squaresLeft[shade];
5579         seed /= squaresLeft[shade];
5580         put(board, pieceType, rank, i, shade);
5581 }
5582
5583 void
5584 AddTwoPieces (Board board, int pieceType, int rank)
5585 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5586 {
5587         int i, n=squaresLeft[ANY], j=n-1, k;
5588
5589         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5590         i = seed % k;  // pick one
5591         nrOfShuffles *= k;
5592         seed /= k;
5593         while(i >= j) i -= j--;
5594         j = n - 1 - j; i += j;
5595         put(board, pieceType, rank, j, ANY);
5596         put(board, pieceType, rank, i, ANY);
5597 }
5598
5599 void
5600 SetUpShuffle (Board board, int number)
5601 {
5602         int i, p, first=1;
5603
5604         GetPositionNumber(); nrOfShuffles = 1;
5605
5606         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5607         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5608         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5609
5610         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5611
5612         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5613             p = (int) board[0][i];
5614             if(p < (int) BlackPawn) piecesLeft[p] ++;
5615             board[0][i] = EmptySquare;
5616         }
5617
5618         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5619             // shuffles restricted to allow normal castling put KRR first
5620             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5621                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5622             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5623                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5624             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5625                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5626             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5627                 put(board, WhiteRook, 0, 0, ANY);
5628             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5629         }
5630
5631         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5632             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5633             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5634                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5635                 while(piecesLeft[p] >= 2) {
5636                     AddOnePiece(board, p, 0, LITE);
5637                     AddOnePiece(board, p, 0, DARK);
5638                 }
5639                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5640             }
5641
5642         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5643             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5644             // but we leave King and Rooks for last, to possibly obey FRC restriction
5645             if(p == (int)WhiteRook) continue;
5646             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5647             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5648         }
5649
5650         // now everything is placed, except perhaps King (Unicorn) and Rooks
5651
5652         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5653             // Last King gets castling rights
5654             while(piecesLeft[(int)WhiteUnicorn]) {
5655                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5656                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5657             }
5658
5659             while(piecesLeft[(int)WhiteKing]) {
5660                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5661                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5662             }
5663
5664
5665         } else {
5666             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5667             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5668         }
5669
5670         // Only Rooks can be left; simply place them all
5671         while(piecesLeft[(int)WhiteRook]) {
5672                 i = put(board, WhiteRook, 0, 0, ANY);
5673                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5674                         if(first) {
5675                                 first=0;
5676                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5677                         }
5678                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5679                 }
5680         }
5681         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5682             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5683         }
5684
5685         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5686 }
5687
5688 int
5689 SetCharTable (char *table, const char * map)
5690 /* [HGM] moved here from winboard.c because of its general usefulness */
5691 /*       Basically a safe strcpy that uses the last character as King */
5692 {
5693     int result = FALSE; int NrPieces;
5694
5695     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5696                     && NrPieces >= 12 && !(NrPieces&1)) {
5697         int i; /* [HGM] Accept even length from 12 to 34 */
5698
5699         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5700         for( i=0; i<NrPieces/2-1; i++ ) {
5701             table[i] = map[i];
5702             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5703         }
5704         table[(int) WhiteKing]  = map[NrPieces/2-1];
5705         table[(int) BlackKing]  = map[NrPieces-1];
5706
5707         result = TRUE;
5708     }
5709
5710     return result;
5711 }
5712
5713 void
5714 Prelude (Board board)
5715 {       // [HGM] superchess: random selection of exo-pieces
5716         int i, j, k; ChessSquare p;
5717         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5718
5719         GetPositionNumber(); // use FRC position number
5720
5721         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5722             SetCharTable(pieceToChar, appData.pieceToCharTable);
5723             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5724                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5725         }
5726
5727         j = seed%4;                 seed /= 4;
5728         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5729         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5730         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5731         j = seed%3 + (seed%3 >= j); seed /= 3;
5732         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5733         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5734         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5735         j = seed%3;                 seed /= 3;
5736         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5737         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5738         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5739         j = seed%2 + (seed%2 >= j); seed /= 2;
5740         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5741         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5742         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5743         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5744         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5745         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5746         put(board, exoPieces[0],    0, 0, ANY);
5747         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5748 }
5749
5750 void
5751 InitPosition (int redraw)
5752 {
5753     ChessSquare (* pieces)[BOARD_FILES];
5754     int i, j, pawnRow, overrule,
5755     oldx = gameInfo.boardWidth,
5756     oldy = gameInfo.boardHeight,
5757     oldh = gameInfo.holdingsWidth;
5758     static int oldv;
5759
5760     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5761
5762     /* [AS] Initialize pv info list [HGM] and game status */
5763     {
5764         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5765             pvInfoList[i].depth = 0;
5766             boards[i][EP_STATUS] = EP_NONE;
5767             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5768         }
5769
5770         initialRulePlies = 0; /* 50-move counter start */
5771
5772         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5773         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5774     }
5775
5776
5777     /* [HGM] logic here is completely changed. In stead of full positions */
5778     /* the initialized data only consist of the two backranks. The switch */
5779     /* selects which one we will use, which is than copied to the Board   */
5780     /* initialPosition, which for the rest is initialized by Pawns and    */
5781     /* empty squares. This initial position is then copied to boards[0],  */
5782     /* possibly after shuffling, so that it remains available.            */
5783
5784     gameInfo.holdingsWidth = 0; /* default board sizes */
5785     gameInfo.boardWidth    = 8;
5786     gameInfo.boardHeight   = 8;
5787     gameInfo.holdingsSize  = 0;
5788     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5789     for(i=0; i<BOARD_FILES-2; i++)
5790       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5791     initialPosition[EP_STATUS] = EP_NONE;
5792     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5793     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5794          SetCharTable(pieceNickName, appData.pieceNickNames);
5795     else SetCharTable(pieceNickName, "............");
5796     pieces = FIDEArray;
5797
5798     switch (gameInfo.variant) {
5799     case VariantFischeRandom:
5800       shuffleOpenings = TRUE;
5801     default:
5802       break;
5803     case VariantShatranj:
5804       pieces = ShatranjArray;
5805       nrCastlingRights = 0;
5806       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5807       break;
5808     case VariantMakruk:
5809       pieces = makrukArray;
5810       nrCastlingRights = 0;
5811       startedFromSetupPosition = TRUE;
5812       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5813       break;
5814     case VariantTwoKings:
5815       pieces = twoKingsArray;
5816       break;
5817     case VariantGrand:
5818       pieces = GrandArray;
5819       nrCastlingRights = 0;
5820       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5821       gameInfo.boardWidth = 10;
5822       gameInfo.boardHeight = 10;
5823       gameInfo.holdingsSize = 7;
5824       break;
5825     case VariantCapaRandom:
5826       shuffleOpenings = TRUE;
5827     case VariantCapablanca:
5828       pieces = CapablancaArray;
5829       gameInfo.boardWidth = 10;
5830       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5831       break;
5832     case VariantGothic:
5833       pieces = GothicArray;
5834       gameInfo.boardWidth = 10;
5835       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5836       break;
5837     case VariantSChess:
5838       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5839       gameInfo.holdingsSize = 7;
5840       break;
5841     case VariantJanus:
5842       pieces = JanusArray;
5843       gameInfo.boardWidth = 10;
5844       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5845       nrCastlingRights = 6;
5846         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5847         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5848         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5849         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5850         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5851         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5852       break;
5853     case VariantFalcon:
5854       pieces = FalconArray;
5855       gameInfo.boardWidth = 10;
5856       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5857       break;
5858     case VariantXiangqi:
5859       pieces = XiangqiArray;
5860       gameInfo.boardWidth  = 9;
5861       gameInfo.boardHeight = 10;
5862       nrCastlingRights = 0;
5863       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5864       break;
5865     case VariantShogi:
5866       pieces = ShogiArray;
5867       gameInfo.boardWidth  = 9;
5868       gameInfo.boardHeight = 9;
5869       gameInfo.holdingsSize = 7;
5870       nrCastlingRights = 0;
5871       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5872       break;
5873     case VariantCourier:
5874       pieces = CourierArray;
5875       gameInfo.boardWidth  = 12;
5876       nrCastlingRights = 0;
5877       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5878       break;
5879     case VariantKnightmate:
5880       pieces = KnightmateArray;
5881       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5882       break;
5883     case VariantSpartan:
5884       pieces = SpartanArray;
5885       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5886       break;
5887     case VariantFairy:
5888       pieces = fairyArray;
5889       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5890       break;
5891     case VariantGreat:
5892       pieces = GreatArray;
5893       gameInfo.boardWidth = 10;
5894       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5895       gameInfo.holdingsSize = 8;
5896       break;
5897     case VariantSuper:
5898       pieces = FIDEArray;
5899       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5900       gameInfo.holdingsSize = 8;
5901       startedFromSetupPosition = TRUE;
5902       break;
5903     case VariantCrazyhouse:
5904     case VariantBughouse:
5905       pieces = FIDEArray;
5906       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5907       gameInfo.holdingsSize = 5;
5908       break;
5909     case VariantWildCastle:
5910       pieces = FIDEArray;
5911       /* !!?shuffle with kings guaranteed to be on d or e file */
5912       shuffleOpenings = 1;
5913       break;
5914     case VariantNoCastle:
5915       pieces = FIDEArray;
5916       nrCastlingRights = 0;
5917       /* !!?unconstrained back-rank shuffle */
5918       shuffleOpenings = 1;
5919       break;
5920     }
5921
5922     overrule = 0;
5923     if(appData.NrFiles >= 0) {
5924         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5925         gameInfo.boardWidth = appData.NrFiles;
5926     }
5927     if(appData.NrRanks >= 0) {
5928         gameInfo.boardHeight = appData.NrRanks;
5929     }
5930     if(appData.holdingsSize >= 0) {
5931         i = appData.holdingsSize;
5932         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5933         gameInfo.holdingsSize = i;
5934     }
5935     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5936     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5937         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5938
5939     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5940     if(pawnRow < 1) pawnRow = 1;
5941     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5942
5943     /* User pieceToChar list overrules defaults */
5944     if(appData.pieceToCharTable != NULL)
5945         SetCharTable(pieceToChar, appData.pieceToCharTable);
5946
5947     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5948
5949         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5950             s = (ChessSquare) 0; /* account holding counts in guard band */
5951         for( i=0; i<BOARD_HEIGHT; i++ )
5952             initialPosition[i][j] = s;
5953
5954         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5955         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5956         initialPosition[pawnRow][j] = WhitePawn;
5957         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5958         if(gameInfo.variant == VariantXiangqi) {
5959             if(j&1) {
5960                 initialPosition[pawnRow][j] =
5961                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5962                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5963                    initialPosition[2][j] = WhiteCannon;
5964                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5965                 }
5966             }
5967         }
5968         if(gameInfo.variant == VariantGrand) {
5969             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5970                initialPosition[0][j] = WhiteRook;
5971                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5972             }
5973         }
5974         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5975     }
5976     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5977
5978             j=BOARD_LEFT+1;
5979             initialPosition[1][j] = WhiteBishop;
5980             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5981             j=BOARD_RGHT-2;
5982             initialPosition[1][j] = WhiteRook;
5983             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5984     }
5985
5986     if( nrCastlingRights == -1) {
5987         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5988         /*       This sets default castling rights from none to normal corners   */
5989         /* Variants with other castling rights must set them themselves above    */
5990         nrCastlingRights = 6;
5991
5992         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5993         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5994         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5995         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5996         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5997         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5998      }
5999
6000      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6001      if(gameInfo.variant == VariantGreat) { // promotion commoners
6002         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6003         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6004         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6005         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6006      }
6007      if( gameInfo.variant == VariantSChess ) {
6008       initialPosition[1][0] = BlackMarshall;
6009       initialPosition[2][0] = BlackAngel;
6010       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6011       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6012       initialPosition[1][1] = initialPosition[2][1] = 
6013       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6014      }
6015   if (appData.debugMode) {
6016     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6017   }
6018     if(shuffleOpenings) {
6019         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6020         startedFromSetupPosition = TRUE;
6021     }
6022     if(startedFromPositionFile) {
6023       /* [HGM] loadPos: use PositionFile for every new game */
6024       CopyBoard(initialPosition, filePosition);
6025       for(i=0; i<nrCastlingRights; i++)
6026           initialRights[i] = filePosition[CASTLING][i];
6027       startedFromSetupPosition = TRUE;
6028     }
6029
6030     CopyBoard(boards[0], initialPosition);
6031
6032     if(oldx != gameInfo.boardWidth ||
6033        oldy != gameInfo.boardHeight ||
6034        oldv != gameInfo.variant ||
6035        oldh != gameInfo.holdingsWidth
6036                                          )
6037             InitDrawingSizes(-2 ,0);
6038
6039     oldv = gameInfo.variant;
6040     if (redraw)
6041       DrawPosition(TRUE, boards[currentMove]);
6042 }
6043
6044 void
6045 SendBoard (ChessProgramState *cps, int moveNum)
6046 {
6047     char message[MSG_SIZ];
6048
6049     if (cps->useSetboard) {
6050       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6051       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6052       SendToProgram(message, cps);
6053       free(fen);
6054
6055     } else {
6056       ChessSquare *bp;
6057       int i, j, left=0, right=BOARD_WIDTH;
6058       /* Kludge to set black to move, avoiding the troublesome and now
6059        * deprecated "black" command.
6060        */
6061       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6062         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6063
6064       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6065
6066       SendToProgram("edit\n", cps);
6067       SendToProgram("#\n", cps);
6068       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6069         bp = &boards[moveNum][i][left];
6070         for (j = left; j < right; j++, bp++) {
6071           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6072           if ((int) *bp < (int) BlackPawn) {
6073             if(j == BOARD_RGHT+1)
6074                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6075             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6076             if(message[0] == '+' || message[0] == '~') {
6077               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6078                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6079                         AAA + j, ONE + i);
6080             }
6081             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6082                 message[1] = BOARD_RGHT   - 1 - j + '1';
6083                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6084             }
6085             SendToProgram(message, cps);
6086           }
6087         }
6088       }
6089
6090       SendToProgram("c\n", cps);
6091       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6092         bp = &boards[moveNum][i][left];
6093         for (j = left; j < right; j++, bp++) {
6094           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6095           if (((int) *bp != (int) EmptySquare)
6096               && ((int) *bp >= (int) BlackPawn)) {
6097             if(j == BOARD_LEFT-2)
6098                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6099             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6100                     AAA + j, ONE + i);
6101             if(message[0] == '+' || message[0] == '~') {
6102               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6103                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6104                         AAA + j, ONE + i);
6105             }
6106             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6107                 message[1] = BOARD_RGHT   - 1 - j + '1';
6108                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6109             }
6110             SendToProgram(message, cps);
6111           }
6112         }
6113       }
6114
6115       SendToProgram(".\n", cps);
6116     }
6117     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6118 }
6119
6120 char exclusionHeader[MSG_SIZ];
6121 int exCnt, excludePtr;
6122 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6123 static Exclusion excluTab[200];
6124 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6125
6126 static void
6127 WriteMap (int s)
6128 {
6129     int j;
6130     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6131     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6132 }
6133
6134 static void
6135 ClearMap ()
6136 {
6137     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6138     excludePtr = 24; exCnt = 0;
6139     WriteMap(0);
6140 }
6141
6142 static void
6143 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6144 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6145     char buf[2*MOVE_LEN], *p;
6146     Exclusion *e = excluTab;
6147     int i;
6148     for(i=0; i<exCnt; i++)
6149         if(e[i].ff == fromX && e[i].fr == fromY &&
6150            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6151     if(i == exCnt) { // was not in exclude list; add it
6152         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6153         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6154             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6155             return; // abort
6156         }
6157         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6158         excludePtr++; e[i].mark = excludePtr++;
6159         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6160         exCnt++;
6161     }
6162     exclusionHeader[e[i].mark] = state;
6163 }
6164
6165 static int
6166 ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state)
6167 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6168     char buf[MSG_SIZ];
6169     int j, k;
6170     ChessMove moveType;
6171     if(promoChar == -1) { // kludge to indicate best move
6172         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6173             return 1; // if unparsable, abort
6174     }
6175     // update exclusion map (resolving toggle by consulting existing state)
6176     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6177     j = k%8; k >>= 3;
6178     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6179     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6180          excludeMap[k] |=   1<<j;
6181     else excludeMap[k] &= ~(1<<j);
6182     // update header
6183     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6184     // inform engine
6185     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6186     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6187     SendToProgram(buf, &first);
6188     return (state == '+');
6189 }
6190
6191 static void
6192 ExcludeClick (int index)
6193 {
6194     int i, j;
6195     Exclusion *e = excluTab;
6196     if(index < 25) { // none, best or tail clicked
6197         if(index < 13) { // none: include all
6198             WriteMap(0); // clear map
6199             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6200             SendToProgram("include all\n", &first); // and inform engine
6201         } else if(index > 18) { // tail
6202             if(exclusionHeader[19] == '-') { // tail was excluded
6203                 SendToProgram("include all\n", &first);
6204                 WriteMap(0); // clear map completely
6205                 // now re-exclude selected moves
6206                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6207                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6208             } else { // tail was included or in mixed state
6209                 SendToProgram("exclude all\n", &first);
6210                 WriteMap(0xFF); // fill map completely
6211                 // now re-include selected moves
6212                 j = 0; // count them
6213                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6214                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6215                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6216             }
6217         } else { // best
6218             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6219         }
6220     } else {
6221         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6222             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6223             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6224             break;
6225         }
6226     }
6227 }
6228
6229 ChessSquare
6230 DefaultPromoChoice (int white)
6231 {
6232     ChessSquare result;
6233     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6234         result = WhiteFerz; // no choice
6235     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6236         result= WhiteKing; // in Suicide Q is the last thing we want
6237     else if(gameInfo.variant == VariantSpartan)
6238         result = white ? WhiteQueen : WhiteAngel;
6239     else result = WhiteQueen;
6240     if(!white) result = WHITE_TO_BLACK result;
6241     return result;
6242 }
6243
6244 static int autoQueen; // [HGM] oneclick
6245
6246 int
6247 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6248 {
6249     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6250     /* [HGM] add Shogi promotions */
6251     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6252     ChessSquare piece;
6253     ChessMove moveType;
6254     Boolean premove;
6255
6256     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6257     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6258
6259     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6260       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6261         return FALSE;
6262
6263     piece = boards[currentMove][fromY][fromX];
6264     if(gameInfo.variant == VariantShogi) {
6265         promotionZoneSize = BOARD_HEIGHT/3;
6266         highestPromotingPiece = (int)WhiteFerz;
6267     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6268         promotionZoneSize = 3;
6269     }
6270
6271     // Treat Lance as Pawn when it is not representing Amazon
6272     if(gameInfo.variant != VariantSuper) {
6273         if(piece == WhiteLance) piece = WhitePawn; else
6274         if(piece == BlackLance) piece = BlackPawn;
6275     }
6276
6277     // next weed out all moves that do not touch the promotion zone at all
6278     if((int)piece >= BlackPawn) {
6279         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6280              return FALSE;
6281         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6282     } else {
6283         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6284            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6285     }
6286
6287     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6288
6289     // weed out mandatory Shogi promotions
6290     if(gameInfo.variant == VariantShogi) {
6291         if(piece >= BlackPawn) {
6292             if(toY == 0 && piece == BlackPawn ||
6293                toY == 0 && piece == BlackQueen ||
6294                toY <= 1 && piece == BlackKnight) {
6295                 *promoChoice = '+';
6296                 return FALSE;
6297             }
6298         } else {
6299             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6300                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6301                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6302                 *promoChoice = '+';
6303                 return FALSE;
6304             }
6305         }
6306     }
6307
6308     // weed out obviously illegal Pawn moves
6309     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6310         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6311         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6312         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6313         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6314         // note we are not allowed to test for valid (non-)capture, due to premove
6315     }
6316
6317     // we either have a choice what to promote to, or (in Shogi) whether to promote
6318     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6319         *promoChoice = PieceToChar(BlackFerz);  // no choice
6320         return FALSE;
6321     }
6322     // no sense asking what we must promote to if it is going to explode...
6323     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6324         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6325         return FALSE;
6326     }
6327     // give caller the default choice even if we will not make it
6328     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6329     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6330     if(        sweepSelect && gameInfo.variant != VariantGreat
6331                            && gameInfo.variant != VariantGrand
6332                            && gameInfo.variant != VariantSuper) return FALSE;
6333     if(autoQueen) return FALSE; // predetermined
6334
6335     // suppress promotion popup on illegal moves that are not premoves
6336     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6337               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6338     if(appData.testLegality && !premove) {
6339         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6340                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6341         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6342             return FALSE;
6343     }
6344
6345     return TRUE;
6346 }
6347
6348 int
6349 InPalace (int row, int column)
6350 {   /* [HGM] for Xiangqi */
6351     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6352          column < (BOARD_WIDTH + 4)/2 &&
6353          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6354     return FALSE;
6355 }
6356
6357 int
6358 PieceForSquare (int x, int y)
6359 {
6360   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6361      return -1;
6362   else
6363      return boards[currentMove][y][x];
6364 }
6365
6366 int
6367 OKToStartUserMove (int x, int y)
6368 {
6369     ChessSquare from_piece;
6370     int white_piece;
6371
6372     if (matchMode) return FALSE;
6373     if (gameMode == EditPosition) return TRUE;
6374
6375     if (x >= 0 && y >= 0)
6376       from_piece = boards[currentMove][y][x];
6377     else
6378       from_piece = EmptySquare;
6379
6380     if (from_piece == EmptySquare) return FALSE;
6381
6382     white_piece = (int)from_piece >= (int)WhitePawn &&
6383       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6384
6385     switch (gameMode) {
6386       case AnalyzeFile:
6387       case TwoMachinesPlay:
6388       case EndOfGame:
6389         return FALSE;
6390
6391       case IcsObserving:
6392       case IcsIdle:
6393         return FALSE;
6394
6395       case MachinePlaysWhite:
6396       case IcsPlayingBlack:
6397         if (appData.zippyPlay) return FALSE;
6398         if (white_piece) {
6399             DisplayMoveError(_("You are playing Black"));
6400             return FALSE;
6401         }
6402         break;
6403
6404       case MachinePlaysBlack:
6405       case IcsPlayingWhite:
6406         if (appData.zippyPlay) return FALSE;
6407         if (!white_piece) {
6408             DisplayMoveError(_("You are playing White"));
6409             return FALSE;
6410         }
6411         break;
6412
6413       case PlayFromGameFile:
6414             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6415       case EditGame:
6416         if (!white_piece && WhiteOnMove(currentMove)) {
6417             DisplayMoveError(_("It is White's turn"));
6418             return FALSE;
6419         }
6420         if (white_piece && !WhiteOnMove(currentMove)) {
6421             DisplayMoveError(_("It is Black's turn"));
6422             return FALSE;
6423         }
6424         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6425             /* Editing correspondence game history */
6426             /* Could disallow this or prompt for confirmation */
6427             cmailOldMove = -1;
6428         }
6429         break;
6430
6431       case BeginningOfGame:
6432         if (appData.icsActive) return FALSE;
6433         if (!appData.noChessProgram) {
6434             if (!white_piece) {
6435                 DisplayMoveError(_("You are playing White"));
6436                 return FALSE;
6437             }
6438         }
6439         break;
6440
6441       case Training:
6442         if (!white_piece && WhiteOnMove(currentMove)) {
6443             DisplayMoveError(_("It is White's turn"));
6444             return FALSE;
6445         }
6446         if (white_piece && !WhiteOnMove(currentMove)) {
6447             DisplayMoveError(_("It is Black's turn"));
6448             return FALSE;
6449         }
6450         break;
6451
6452       default:
6453       case IcsExamining:
6454         break;
6455     }
6456     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6457         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6458         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6459         && gameMode != AnalyzeFile && gameMode != Training) {
6460         DisplayMoveError(_("Displayed position is not current"));
6461         return FALSE;
6462     }
6463     return TRUE;
6464 }
6465
6466 Boolean
6467 OnlyMove (int *x, int *y, Boolean captures) 
6468 {
6469     DisambiguateClosure cl;
6470     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6471     switch(gameMode) {
6472       case MachinePlaysBlack:
6473       case IcsPlayingWhite:
6474       case BeginningOfGame:
6475         if(!WhiteOnMove(currentMove)) return FALSE;
6476         break;
6477       case MachinePlaysWhite:
6478       case IcsPlayingBlack:
6479         if(WhiteOnMove(currentMove)) return FALSE;
6480         break;
6481       case EditGame:
6482         break;
6483       default:
6484         return FALSE;
6485     }
6486     cl.pieceIn = EmptySquare;
6487     cl.rfIn = *y;
6488     cl.ffIn = *x;
6489     cl.rtIn = -1;
6490     cl.ftIn = -1;
6491     cl.promoCharIn = NULLCHAR;
6492     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6493     if( cl.kind == NormalMove ||
6494         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6495         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6496         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6497       fromX = cl.ff;
6498       fromY = cl.rf;
6499       *x = cl.ft;
6500       *y = cl.rt;
6501       return TRUE;
6502     }
6503     if(cl.kind != ImpossibleMove) return FALSE;
6504     cl.pieceIn = EmptySquare;
6505     cl.rfIn = -1;
6506     cl.ffIn = -1;
6507     cl.rtIn = *y;
6508     cl.ftIn = *x;
6509     cl.promoCharIn = NULLCHAR;
6510     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6511     if( cl.kind == NormalMove ||
6512         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6513         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6514         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6515       fromX = cl.ff;
6516       fromY = cl.rf;
6517       *x = cl.ft;
6518       *y = cl.rt;
6519       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6520       return TRUE;
6521     }
6522     return FALSE;
6523 }
6524
6525 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6526 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6527 int lastLoadGameUseList = FALSE;
6528 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6529 ChessMove lastLoadGameStart = EndOfFile;
6530 int doubleClick;
6531
6532 void
6533 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6534 {
6535     ChessMove moveType;
6536     ChessSquare pdown, pup;
6537     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6538
6539
6540     /* Check if the user is playing in turn.  This is complicated because we
6541        let the user "pick up" a piece before it is his turn.  So the piece he
6542        tried to pick up may have been captured by the time he puts it down!
6543        Therefore we use the color the user is supposed to be playing in this
6544        test, not the color of the piece that is currently on the starting
6545        square---except in EditGame mode, where the user is playing both
6546        sides; fortunately there the capture race can't happen.  (It can
6547        now happen in IcsExamining mode, but that's just too bad.  The user
6548        will get a somewhat confusing message in that case.)
6549        */
6550
6551     switch (gameMode) {
6552       case AnalyzeFile:
6553       case TwoMachinesPlay:
6554       case EndOfGame:
6555       case IcsObserving:
6556       case IcsIdle:
6557         /* We switched into a game mode where moves are not accepted,
6558            perhaps while the mouse button was down. */
6559         return;
6560
6561       case MachinePlaysWhite:
6562         /* User is moving for Black */
6563         if (WhiteOnMove(currentMove)) {
6564             DisplayMoveError(_("It is White's turn"));
6565             return;
6566         }
6567         break;
6568
6569       case MachinePlaysBlack:
6570         /* User is moving for White */
6571         if (!WhiteOnMove(currentMove)) {
6572             DisplayMoveError(_("It is Black's turn"));
6573             return;
6574         }
6575         break;
6576
6577       case PlayFromGameFile:
6578             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6579       case EditGame:
6580       case IcsExamining:
6581       case BeginningOfGame:
6582       case AnalyzeMode:
6583       case Training:
6584         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6585         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6586             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6587             /* User is moving for Black */
6588             if (WhiteOnMove(currentMove)) {
6589                 DisplayMoveError(_("It is White's turn"));
6590                 return;
6591             }
6592         } else {
6593             /* User is moving for White */
6594             if (!WhiteOnMove(currentMove)) {
6595                 DisplayMoveError(_("It is Black's turn"));
6596                 return;
6597             }
6598         }
6599         break;
6600
6601       case IcsPlayingBlack:
6602         /* User is moving for Black */
6603         if (WhiteOnMove(currentMove)) {
6604             if (!appData.premove) {
6605                 DisplayMoveError(_("It is White's turn"));
6606             } else if (toX >= 0 && toY >= 0) {
6607                 premoveToX = toX;
6608                 premoveToY = toY;
6609                 premoveFromX = fromX;
6610                 premoveFromY = fromY;
6611                 premovePromoChar = promoChar;
6612                 gotPremove = 1;
6613                 if (appData.debugMode)
6614                     fprintf(debugFP, "Got premove: fromX %d,"
6615                             "fromY %d, toX %d, toY %d\n",
6616                             fromX, fromY, toX, toY);
6617             }
6618             return;
6619         }
6620         break;
6621
6622       case IcsPlayingWhite:
6623         /* User is moving for White */
6624         if (!WhiteOnMove(currentMove)) {
6625             if (!appData.premove) {
6626                 DisplayMoveError(_("It is Black's turn"));
6627             } else if (toX >= 0 && toY >= 0) {
6628                 premoveToX = toX;
6629                 premoveToY = toY;
6630                 premoveFromX = fromX;
6631                 premoveFromY = fromY;
6632                 premovePromoChar = promoChar;
6633                 gotPremove = 1;
6634                 if (appData.debugMode)
6635                     fprintf(debugFP, "Got premove: fromX %d,"
6636                             "fromY %d, toX %d, toY %d\n",
6637                             fromX, fromY, toX, toY);
6638             }
6639             return;
6640         }
6641         break;
6642
6643       default:
6644         break;
6645
6646       case EditPosition:
6647         /* EditPosition, empty square, or different color piece;
6648            click-click move is possible */
6649         if (toX == -2 || toY == -2) {
6650             boards[0][fromY][fromX] = EmptySquare;
6651             DrawPosition(FALSE, boards[currentMove]);
6652             return;
6653         } else if (toX >= 0 && toY >= 0) {
6654             boards[0][toY][toX] = boards[0][fromY][fromX];
6655             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6656                 if(boards[0][fromY][0] != EmptySquare) {
6657                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6658                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6659                 }
6660             } else
6661             if(fromX == BOARD_RGHT+1) {
6662                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6663                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6664                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6665                 }
6666             } else
6667             boards[0][fromY][fromX] = EmptySquare;
6668             DrawPosition(FALSE, boards[currentMove]);
6669             return;
6670         }
6671         return;
6672     }
6673
6674     if(toX < 0 || toY < 0) return;
6675     pdown = boards[currentMove][fromY][fromX];
6676     pup = boards[currentMove][toY][toX];
6677
6678     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6679     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6680          if( pup != EmptySquare ) return;
6681          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6682            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6683                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6684            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6685            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6686            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6687            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6688          fromY = DROP_RANK;
6689     }
6690
6691     /* [HGM] always test for legality, to get promotion info */
6692     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6693                                          fromY, fromX, toY, toX, promoChar);
6694
6695     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6696
6697     /* [HGM] but possibly ignore an IllegalMove result */
6698     if (appData.testLegality) {
6699         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6700             DisplayMoveError(_("Illegal move"));
6701             return;
6702         }
6703     }
6704
6705     if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
6706         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
6707              ClearPremoveHighlights(); // was included
6708         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
6709         return;
6710     }
6711
6712     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6713 }
6714
6715 /* Common tail of UserMoveEvent and DropMenuEvent */
6716 int
6717 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6718 {
6719     char *bookHit = 0;
6720
6721     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6722         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6723         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6724         if(WhiteOnMove(currentMove)) {
6725             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6726         } else {
6727             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6728         }
6729     }
6730
6731     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6732        move type in caller when we know the move is a legal promotion */
6733     if(moveType == NormalMove && promoChar)
6734         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6735
6736     /* [HGM] <popupFix> The following if has been moved here from
6737        UserMoveEvent(). Because it seemed to belong here (why not allow
6738        piece drops in training games?), and because it can only be
6739        performed after it is known to what we promote. */
6740     if (gameMode == Training) {
6741       /* compare the move played on the board to the next move in the
6742        * game. If they match, display the move and the opponent's response.
6743        * If they don't match, display an error message.
6744        */
6745       int saveAnimate;
6746       Board testBoard;
6747       CopyBoard(testBoard, boards[currentMove]);
6748       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6749
6750       if (CompareBoards(testBoard, boards[currentMove+1])) {
6751         ForwardInner(currentMove+1);
6752
6753         /* Autoplay the opponent's response.
6754          * if appData.animate was TRUE when Training mode was entered,
6755          * the response will be animated.
6756          */
6757         saveAnimate = appData.animate;
6758         appData.animate = animateTraining;
6759         ForwardInner(currentMove+1);
6760         appData.animate = saveAnimate;
6761
6762         /* check for the end of the game */
6763         if (currentMove >= forwardMostMove) {
6764           gameMode = PlayFromGameFile;
6765           ModeHighlight();
6766           SetTrainingModeOff();
6767           DisplayInformation(_("End of game"));
6768         }
6769       } else {
6770         DisplayError(_("Incorrect move"), 0);
6771       }
6772       return 1;
6773     }
6774
6775   /* Ok, now we know that the move is good, so we can kill
6776      the previous line in Analysis Mode */
6777   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6778                                 && currentMove < forwardMostMove) {
6779     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6780     else forwardMostMove = currentMove;
6781   }
6782
6783   ClearMap();
6784
6785   /* If we need the chess program but it's dead, restart it */
6786   ResurrectChessProgram();
6787
6788   /* A user move restarts a paused game*/
6789   if (pausing)
6790     PauseEvent();
6791
6792   thinkOutput[0] = NULLCHAR;
6793
6794   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6795
6796   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6797     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6798     return 1;
6799   }
6800
6801   if (gameMode == BeginningOfGame) {
6802     if (appData.noChessProgram) {
6803       gameMode = EditGame;
6804       SetGameInfo();
6805     } else {
6806       char buf[MSG_SIZ];
6807       gameMode = MachinePlaysBlack;
6808       StartClocks();
6809       SetGameInfo();
6810       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6811       DisplayTitle(buf);
6812       if (first.sendName) {
6813         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6814         SendToProgram(buf, &first);
6815       }
6816       StartClocks();
6817     }
6818     ModeHighlight();
6819   }
6820
6821   /* Relay move to ICS or chess engine */
6822   if (appData.icsActive) {
6823     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6824         gameMode == IcsExamining) {
6825       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6826         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6827         SendToICS("draw ");
6828         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6829       }
6830       // also send plain move, in case ICS does not understand atomic claims
6831       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6832       ics_user_moved = 1;
6833     }
6834   } else {
6835     if (first.sendTime && (gameMode == BeginningOfGame ||
6836                            gameMode == MachinePlaysWhite ||
6837                            gameMode == MachinePlaysBlack)) {
6838       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6839     }
6840     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6841          // [HGM] book: if program might be playing, let it use book
6842         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6843         first.maybeThinking = TRUE;
6844     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6845         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6846         SendBoard(&first, currentMove+1);
6847     } else SendMoveToProgram(forwardMostMove-1, &first);
6848     if (currentMove == cmailOldMove + 1) {
6849       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6850     }
6851   }
6852
6853   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6854
6855   switch (gameMode) {
6856   case EditGame:
6857     if(appData.testLegality)
6858     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6859     case MT_NONE:
6860     case MT_CHECK:
6861       break;
6862     case MT_CHECKMATE:
6863     case MT_STAINMATE:
6864       if (WhiteOnMove(currentMove)) {
6865         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6866       } else {
6867         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6868       }
6869       break;
6870     case MT_STALEMATE:
6871       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6872       break;
6873     }
6874     break;
6875
6876   case MachinePlaysBlack:
6877   case MachinePlaysWhite:
6878     /* disable certain menu options while machine is thinking */
6879     SetMachineThinkingEnables();
6880     break;
6881
6882   default:
6883     break;
6884   }
6885
6886   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6887   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6888
6889   if(bookHit) { // [HGM] book: simulate book reply
6890         static char bookMove[MSG_SIZ]; // a bit generous?
6891
6892         programStats.nodes = programStats.depth = programStats.time =
6893         programStats.score = programStats.got_only_move = 0;
6894         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6895
6896         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6897         strcat(bookMove, bookHit);
6898         HandleMachineMove(bookMove, &first);
6899   }
6900   return 1;
6901 }
6902
6903 void
6904 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6905 {
6906     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6907     Markers *m = (Markers *) closure;
6908     if(rf == fromY && ff == fromX)
6909         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6910                          || kind == WhiteCapturesEnPassant
6911                          || kind == BlackCapturesEnPassant);
6912     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6913 }
6914
6915 void
6916 MarkTargetSquares (int clear)
6917 {
6918   int x, y;
6919   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6920      !appData.testLegality || gameMode == EditPosition) return;
6921   if(clear) {
6922     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6923   } else {
6924     int capt = 0;
6925     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6926     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6927       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6928       if(capt)
6929       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6930     }
6931   }
6932   DrawPosition(TRUE, NULL);
6933 }
6934
6935 int
6936 Explode (Board board, int fromX, int fromY, int toX, int toY)
6937 {
6938     if(gameInfo.variant == VariantAtomic &&
6939        (board[toY][toX] != EmptySquare ||                     // capture?
6940         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6941                          board[fromY][fromX] == BlackPawn   )
6942       )) {
6943         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6944         return TRUE;
6945     }
6946     return FALSE;
6947 }
6948
6949 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6950
6951 int
6952 CanPromote (ChessSquare piece, int y)
6953 {
6954         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6955         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6956         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6957            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6958            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6959                                                   gameInfo.variant == VariantMakruk) return FALSE;
6960         return (piece == BlackPawn && y == 1 ||
6961                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6962                 piece == BlackLance && y == 1 ||
6963                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6964 }
6965
6966 void
6967 LeftClick (ClickType clickType, int xPix, int yPix)
6968 {
6969     int x, y;
6970     Boolean saveAnimate;
6971     static int second = 0, promotionChoice = 0, clearFlag = 0;
6972     char promoChoice = NULLCHAR;
6973     ChessSquare piece;
6974     static TimeMark lastClickTime, prevClickTime;
6975
6976     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
6977
6978     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
6979
6980     if (clickType == Press) ErrorPopDown();
6981
6982     x = EventToSquare(xPix, BOARD_WIDTH);
6983     y = EventToSquare(yPix, BOARD_HEIGHT);
6984     if (!flipView && y >= 0) {
6985         y = BOARD_HEIGHT - 1 - y;
6986     }
6987     if (flipView && x >= 0) {
6988         x = BOARD_WIDTH - 1 - x;
6989     }
6990
6991     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6992         defaultPromoChoice = promoSweep;
6993         promoSweep = EmptySquare;   // terminate sweep
6994         promoDefaultAltered = TRUE;
6995         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6996     }
6997
6998     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6999         if(clickType == Release) return; // ignore upclick of click-click destination
7000         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7001         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7002         if(gameInfo.holdingsWidth &&
7003                 (WhiteOnMove(currentMove)
7004                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7005                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7006             // click in right holdings, for determining promotion piece
7007             ChessSquare p = boards[currentMove][y][x];
7008             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7009             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7010             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7011                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7012                 fromX = fromY = -1;
7013                 return;
7014             }
7015         }
7016         DrawPosition(FALSE, boards[currentMove]);
7017         return;
7018     }
7019
7020     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7021     if(clickType == Press
7022             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7023               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7024               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7025         return;
7026
7027     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7028         // could be static click on premove from-square: abort premove
7029         gotPremove = 0;
7030         ClearPremoveHighlights();
7031     }
7032
7033     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7034         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7035
7036     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7037         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7038                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7039         defaultPromoChoice = DefaultPromoChoice(side);
7040     }
7041
7042     autoQueen = appData.alwaysPromoteToQueen;
7043
7044     if (fromX == -1) {
7045       int originalY = y;
7046       gatingPiece = EmptySquare;
7047       if (clickType != Press) {
7048         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7049             DragPieceEnd(xPix, yPix); dragging = 0;
7050             DrawPosition(FALSE, NULL);
7051         }
7052         return;
7053       }
7054       doubleClick = FALSE;
7055       fromX = x; fromY = y; toX = toY = -1;
7056       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7057          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7058          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7059             /* First square */
7060             if (OKToStartUserMove(fromX, fromY)) {
7061                 second = 0;
7062                 MarkTargetSquares(0);
7063                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7064                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7065                     promoSweep = defaultPromoChoice;
7066                     selectFlag = 0; lastX = xPix; lastY = yPix;
7067                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7068                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7069                 }
7070                 if (appData.highlightDragging) {
7071                     SetHighlights(fromX, fromY, -1, -1);
7072                 }
7073             } else fromX = fromY = -1;
7074             return;
7075         }
7076     }
7077
7078     /* fromX != -1 */
7079     if (clickType == Press && gameMode != EditPosition) {
7080         ChessSquare fromP;
7081         ChessSquare toP;
7082         int frc;
7083
7084         // ignore off-board to clicks
7085         if(y < 0 || x < 0) return;
7086
7087         /* Check if clicking again on the same color piece */
7088         fromP = boards[currentMove][fromY][fromX];
7089         toP = boards[currentMove][y][x];
7090         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
7091         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
7092              WhitePawn <= toP && toP <= WhiteKing &&
7093              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7094              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7095             (BlackPawn <= fromP && fromP <= BlackKing &&
7096              BlackPawn <= toP && toP <= BlackKing &&
7097              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7098              !(fromP == BlackKing && toP == BlackRook && frc))) {
7099             /* Clicked again on same color piece -- changed his mind */
7100             second = (x == fromX && y == fromY);
7101             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7102                 second = FALSE; // first double-click rather than scond click
7103                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7104             }
7105             promoDefaultAltered = FALSE;
7106             MarkTargetSquares(1);
7107            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
7108             if (appData.highlightDragging) {
7109                 SetHighlights(x, y, -1, -1);
7110             } else {
7111                 ClearHighlights();
7112             }
7113             if (OKToStartUserMove(x, y)) {
7114                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7115                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7116                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7117                  gatingPiece = boards[currentMove][fromY][fromX];
7118                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7119                 fromX = x;
7120                 fromY = y; dragging = 1;
7121                 MarkTargetSquares(0);
7122                 DragPieceBegin(xPix, yPix, FALSE);
7123                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7124                     promoSweep = defaultPromoChoice;
7125                     selectFlag = 0; lastX = xPix; lastY = yPix;
7126                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7127                 }
7128             }
7129            }
7130            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7131            second = FALSE; 
7132         }
7133         // ignore clicks on holdings
7134         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7135     }
7136
7137     if (clickType == Release && x == fromX && y == fromY) {
7138         DragPieceEnd(xPix, yPix); dragging = 0;
7139         if(clearFlag) {
7140             // a deferred attempt to click-click move an empty square on top of a piece
7141             boards[currentMove][y][x] = EmptySquare;
7142             ClearHighlights();
7143             DrawPosition(FALSE, boards[currentMove]);
7144             fromX = fromY = -1; clearFlag = 0;
7145             return;
7146         }
7147         if (appData.animateDragging) {
7148             /* Undo animation damage if any */
7149             DrawPosition(FALSE, NULL);
7150         }
7151         if (second) {
7152             /* Second up/down in same square; just abort move */
7153             second = 0;
7154             fromX = fromY = -1;
7155             gatingPiece = EmptySquare;
7156             ClearHighlights();
7157             gotPremove = 0;
7158             ClearPremoveHighlights();
7159         } else {
7160             /* First upclick in same square; start click-click mode */
7161             SetHighlights(x, y, -1, -1);
7162         }
7163         return;
7164     }
7165
7166     clearFlag = 0;
7167
7168     /* we now have a different from- and (possibly off-board) to-square */
7169     /* Completed move */
7170     toX = x;
7171     toY = y;
7172     saveAnimate = appData.animate;
7173     MarkTargetSquares(1);
7174     if (clickType == Press) {
7175         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7176             // must be Edit Position mode with empty-square selected
7177             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7178             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7179             return;
7180         }
7181         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7182             ChessSquare piece = boards[currentMove][fromY][fromX];
7183             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7184             promoSweep = defaultPromoChoice;
7185             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7186             selectFlag = 0; lastX = xPix; lastY = yPix;
7187             Sweep(0); // Pawn that is going to promote: preview promotion piece
7188             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7189             DrawPosition(FALSE, boards[currentMove]);
7190             return;
7191         }
7192         /* Finish clickclick move */
7193         if (appData.animate || appData.highlightLastMove) {
7194             SetHighlights(fromX, fromY, toX, toY);
7195         } else {
7196             ClearHighlights();
7197         }
7198     } else {
7199         /* Finish drag move */
7200         if (appData.highlightLastMove) {
7201             SetHighlights(fromX, fromY, toX, toY);
7202         } else {
7203             ClearHighlights();
7204         }
7205         DragPieceEnd(xPix, yPix); dragging = 0;
7206         /* Don't animate move and drag both */
7207         appData.animate = FALSE;
7208     }
7209
7210     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7211     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7212         ChessSquare piece = boards[currentMove][fromY][fromX];
7213         if(gameMode == EditPosition && piece != EmptySquare &&
7214            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7215             int n;
7216
7217             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7218                 n = PieceToNumber(piece - (int)BlackPawn);
7219                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7220                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7221                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7222             } else
7223             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7224                 n = PieceToNumber(piece);
7225                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7226                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7227                 boards[currentMove][n][BOARD_WIDTH-2]++;
7228             }
7229             boards[currentMove][fromY][fromX] = EmptySquare;
7230         }
7231         ClearHighlights();
7232         fromX = fromY = -1;
7233         DrawPosition(TRUE, boards[currentMove]);
7234         return;
7235     }
7236
7237     // off-board moves should not be highlighted
7238     if(x < 0 || y < 0) ClearHighlights();
7239
7240     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7241
7242     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7243         SetHighlights(fromX, fromY, toX, toY);
7244         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7245             // [HGM] super: promotion to captured piece selected from holdings
7246             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7247             promotionChoice = TRUE;
7248             // kludge follows to temporarily execute move on display, without promoting yet
7249             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7250             boards[currentMove][toY][toX] = p;
7251             DrawPosition(FALSE, boards[currentMove]);
7252             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7253             boards[currentMove][toY][toX] = q;
7254             DisplayMessage("Click in holdings to choose piece", "");
7255             return;
7256         }
7257         PromotionPopUp();
7258     } else {
7259         int oldMove = currentMove;
7260         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7261         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7262         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7263         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7264            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7265             DrawPosition(TRUE, boards[currentMove]);
7266         fromX = fromY = -1;
7267     }
7268     appData.animate = saveAnimate;
7269     if (appData.animate || appData.animateDragging) {
7270         /* Undo animation damage if needed */
7271         DrawPosition(FALSE, NULL);
7272     }
7273 }
7274
7275 int
7276 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7277 {   // front-end-free part taken out of PieceMenuPopup
7278     int whichMenu; int xSqr, ySqr;
7279
7280     if(seekGraphUp) { // [HGM] seekgraph
7281         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7282         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7283         return -2;
7284     }
7285
7286     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7287          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7288         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7289         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7290         if(action == Press)   {
7291             originalFlip = flipView;
7292             flipView = !flipView; // temporarily flip board to see game from partners perspective
7293             DrawPosition(TRUE, partnerBoard);
7294             DisplayMessage(partnerStatus, "");
7295             partnerUp = TRUE;
7296         } else if(action == Release) {
7297             flipView = originalFlip;
7298             DrawPosition(TRUE, boards[currentMove]);
7299             partnerUp = FALSE;
7300         }
7301         return -2;
7302     }
7303
7304     xSqr = EventToSquare(x, BOARD_WIDTH);
7305     ySqr = EventToSquare(y, BOARD_HEIGHT);
7306     if (action == Release) {
7307         if(pieceSweep != EmptySquare) {
7308             EditPositionMenuEvent(pieceSweep, toX, toY);
7309             pieceSweep = EmptySquare;
7310         } else UnLoadPV(); // [HGM] pv
7311     }
7312     if (action != Press) return -2; // return code to be ignored
7313     switch (gameMode) {
7314       case IcsExamining:
7315         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7316       case EditPosition:
7317         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7318         if (xSqr < 0 || ySqr < 0) return -1;
7319         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7320         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7321         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7322         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7323         NextPiece(0);
7324         return 2; // grab
7325       case IcsObserving:
7326         if(!appData.icsEngineAnalyze) return -1;
7327       case IcsPlayingWhite:
7328       case IcsPlayingBlack:
7329         if(!appData.zippyPlay) goto noZip;
7330       case AnalyzeMode:
7331       case AnalyzeFile:
7332       case MachinePlaysWhite:
7333       case MachinePlaysBlack:
7334       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7335         if (!appData.dropMenu) {
7336           LoadPV(x, y);
7337           return 2; // flag front-end to grab mouse events
7338         }
7339         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7340            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7341       case EditGame:
7342       noZip:
7343         if (xSqr < 0 || ySqr < 0) return -1;
7344         if (!appData.dropMenu || appData.testLegality &&
7345             gameInfo.variant != VariantBughouse &&
7346             gameInfo.variant != VariantCrazyhouse) return -1;
7347         whichMenu = 1; // drop menu
7348         break;
7349       default:
7350         return -1;
7351     }
7352
7353     if (((*fromX = xSqr) < 0) ||
7354         ((*fromY = ySqr) < 0)) {
7355         *fromX = *fromY = -1;
7356         return -1;
7357     }
7358     if (flipView)
7359       *fromX = BOARD_WIDTH - 1 - *fromX;
7360     else
7361       *fromY = BOARD_HEIGHT - 1 - *fromY;
7362
7363     return whichMenu;
7364 }
7365
7366 void
7367 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7368 {
7369 //    char * hint = lastHint;
7370     FrontEndProgramStats stats;
7371
7372     stats.which = cps == &first ? 0 : 1;
7373     stats.depth = cpstats->depth;
7374     stats.nodes = cpstats->nodes;
7375     stats.score = cpstats->score;
7376     stats.time = cpstats->time;
7377     stats.pv = cpstats->movelist;
7378     stats.hint = lastHint;
7379     stats.an_move_index = 0;
7380     stats.an_move_count = 0;
7381
7382     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7383         stats.hint = cpstats->move_name;
7384         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7385         stats.an_move_count = cpstats->nr_moves;
7386     }
7387
7388     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
7389
7390     SetProgramStats( &stats );
7391 }
7392
7393 void
7394 ClearEngineOutputPane (int which)
7395 {
7396     static FrontEndProgramStats dummyStats;
7397     dummyStats.which = which;
7398     dummyStats.pv = "#";
7399     SetProgramStats( &dummyStats );
7400 }
7401
7402 #define MAXPLAYERS 500
7403
7404 char *
7405 TourneyStandings (int display)
7406 {
7407     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7408     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7409     char result, *p, *names[MAXPLAYERS];
7410
7411     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7412         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7413     names[0] = p = strdup(appData.participants);
7414     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7415
7416     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7417
7418     while(result = appData.results[nr]) {
7419         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7420         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7421         wScore = bScore = 0;
7422         switch(result) {
7423           case '+': wScore = 2; break;
7424           case '-': bScore = 2; break;
7425           case '=': wScore = bScore = 1; break;
7426           case ' ':
7427           case '*': return strdup("busy"); // tourney not finished
7428         }
7429         score[w] += wScore;
7430         score[b] += bScore;
7431         games[w]++;
7432         games[b]++;
7433         nr++;
7434     }
7435     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7436     for(w=0; w<nPlayers; w++) {
7437         bScore = -1;
7438         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7439         ranking[w] = b; points[w] = bScore; score[b] = -2;
7440     }
7441     p = malloc(nPlayers*34+1);
7442     for(w=0; w<nPlayers && w<display; w++)
7443         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7444     free(names[0]);
7445     return p;
7446 }
7447
7448 void
7449 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7450 {       // count all piece types
7451         int p, f, r;
7452         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7453         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7454         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7455                 p = board[r][f];
7456                 pCnt[p]++;
7457                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7458                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7459                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7460                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7461                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7462                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7463         }
7464 }
7465
7466 int
7467 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7468 {
7469         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7470         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7471
7472         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7473         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7474         if(myPawns == 2 && nMine == 3) // KPP
7475             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7476         if(myPawns == 1 && nMine == 2) // KP
7477             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7478         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7479             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7480         if(myPawns) return FALSE;
7481         if(pCnt[WhiteRook+side])
7482             return pCnt[BlackRook-side] ||
7483                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7484                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7485                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7486         if(pCnt[WhiteCannon+side]) {
7487             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7488             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7489         }
7490         if(pCnt[WhiteKnight+side])
7491             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7492         return FALSE;
7493 }
7494
7495 int
7496 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7497 {
7498         VariantClass v = gameInfo.variant;
7499
7500         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7501         if(v == VariantShatranj) return TRUE; // always winnable through baring
7502         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7503         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7504
7505         if(v == VariantXiangqi) {
7506                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7507
7508                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7509                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7510                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7511                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7512                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7513                 if(stale) // we have at least one last-rank P plus perhaps C
7514                     return majors // KPKX
7515                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7516                 else // KCA*E*
7517                     return pCnt[WhiteFerz+side] // KCAK
7518                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7519                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7520                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7521
7522         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7523                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7524
7525                 if(nMine == 1) return FALSE; // bare King
7526                 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
7527                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7528                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7529                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7530                 if(pCnt[WhiteKnight+side])
7531                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7532                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7533                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7534                 if(nBishops)
7535                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7536                 if(pCnt[WhiteAlfil+side])
7537                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7538                 if(pCnt[WhiteWazir+side])
7539                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7540         }
7541
7542         return TRUE;
7543 }
7544
7545 int
7546 CompareWithRights (Board b1, Board b2)
7547 {
7548     int rights = 0;
7549     if(!CompareBoards(b1, b2)) return FALSE;
7550     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7551     /* compare castling rights */
7552     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7553            rights++; /* King lost rights, while rook still had them */
7554     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7555         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7556            rights++; /* but at least one rook lost them */
7557     }
7558     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7559            rights++;
7560     if( b1[CASTLING][5] != NoRights ) {
7561         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7562            rights++;
7563     }
7564     return rights == 0;
7565 }
7566
7567 int
7568 Adjudicate (ChessProgramState *cps)
7569 {       // [HGM] some adjudications useful with buggy engines
7570         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7571         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7572         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7573         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7574         int k, count = 0; static int bare = 1;
7575         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7576         Boolean canAdjudicate = !appData.icsActive;
7577
7578         // most tests only when we understand the game, i.e. legality-checking on
7579             if( appData.testLegality )
7580             {   /* [HGM] Some more adjudications for obstinate engines */
7581                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7582                 static int moveCount = 6;
7583                 ChessMove result;
7584                 char *reason = NULL;
7585
7586                 /* Count what is on board. */
7587                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7588
7589                 /* Some material-based adjudications that have to be made before stalemate test */
7590                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7591                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7592                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7593                      if(canAdjudicate && appData.checkMates) {
7594                          if(engineOpponent)
7595                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7596                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7597                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7598                          return 1;
7599                      }
7600                 }
7601
7602                 /* Bare King in Shatranj (loses) or Losers (wins) */
7603                 if( nrW == 1 || nrB == 1) {
7604                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7605                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7606                      if(canAdjudicate && appData.checkMates) {
7607                          if(engineOpponent)
7608                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7609                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7610                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7611                          return 1;
7612                      }
7613                   } else
7614                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7615                   {    /* bare King */
7616                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7617                         if(canAdjudicate && appData.checkMates) {
7618                             /* but only adjudicate if adjudication enabled */
7619                             if(engineOpponent)
7620                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7621                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7622                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7623                             return 1;
7624                         }
7625                   }
7626                 } else bare = 1;
7627
7628
7629             // don't wait for engine to announce game end if we can judge ourselves
7630             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7631               case MT_CHECK:
7632                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7633                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7634                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7635                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7636                             checkCnt++;
7637                         if(checkCnt >= 2) {
7638                             reason = "Xboard adjudication: 3rd check";
7639                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7640                             break;
7641                         }
7642                     }
7643                 }
7644               case MT_NONE:
7645               default:
7646                 break;
7647               case MT_STALEMATE:
7648               case MT_STAINMATE:
7649                 reason = "Xboard adjudication: Stalemate";
7650                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7651                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7652                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7653                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7654                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7655                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7656                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7657                                                                         EP_CHECKMATE : EP_WINS);
7658                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7659                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7660                 }
7661                 break;
7662               case MT_CHECKMATE:
7663                 reason = "Xboard adjudication: Checkmate";
7664                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7665                 break;
7666             }
7667
7668                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7669                     case EP_STALEMATE:
7670                         result = GameIsDrawn; break;
7671                     case EP_CHECKMATE:
7672                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7673                     case EP_WINS:
7674                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7675                     default:
7676                         result = EndOfFile;
7677                 }
7678                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7679                     if(engineOpponent)
7680                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7681                     GameEnds( result, reason, GE_XBOARD );
7682                     return 1;
7683                 }
7684
7685                 /* Next absolutely insufficient mating material. */
7686                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7687                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7688                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7689
7690                      /* always flag draws, for judging claims */
7691                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7692
7693                      if(canAdjudicate && appData.materialDraws) {
7694                          /* but only adjudicate them if adjudication enabled */
7695                          if(engineOpponent) {
7696                            SendToProgram("force\n", engineOpponent); // suppress reply
7697                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7698                          }
7699                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7700                          return 1;
7701                      }
7702                 }
7703
7704                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7705                 if(gameInfo.variant == VariantXiangqi ?
7706                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7707                  : nrW + nrB == 4 &&
7708                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7709                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7710                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7711                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7712                    ) ) {
7713                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7714                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7715                           if(engineOpponent) {
7716                             SendToProgram("force\n", engineOpponent); // suppress reply
7717                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7718                           }
7719                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7720                           return 1;
7721                      }
7722                 } else moveCount = 6;
7723             }
7724
7725         // Repetition draws and 50-move rule can be applied independently of legality testing
7726
7727                 /* Check for rep-draws */
7728                 count = 0;
7729                 for(k = forwardMostMove-2;
7730                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7731                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7732                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7733                     k-=2)
7734                 {   int rights=0;
7735                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7736                         /* compare castling rights */
7737                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7738                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7739                                 rights++; /* King lost rights, while rook still had them */
7740                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7741                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7742                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7743                                    rights++; /* but at least one rook lost them */
7744                         }
7745                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7746                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7747                                 rights++;
7748                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7749                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7750                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7751                                    rights++;
7752                         }
7753                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7754                             && appData.drawRepeats > 1) {
7755                              /* adjudicate after user-specified nr of repeats */
7756                              int result = GameIsDrawn;
7757                              char *details = "XBoard adjudication: repetition draw";
7758                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7759                                 // [HGM] xiangqi: check for forbidden perpetuals
7760                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7761                                 for(m=forwardMostMove; m>k; m-=2) {
7762                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7763                                         ourPerpetual = 0; // the current mover did not always check
7764                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7765                                         hisPerpetual = 0; // the opponent did not always check
7766                                 }
7767                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7768                                                                         ourPerpetual, hisPerpetual);
7769                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7770                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7771                                     details = "Xboard adjudication: perpetual checking";
7772                                 } else
7773                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7774                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7775                                 } else
7776                                 // Now check for perpetual chases
7777                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7778                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7779                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7780                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7781                                         static char resdet[MSG_SIZ];
7782                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7783                                         details = resdet;
7784                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7785                                     } else
7786                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7787                                         break; // Abort repetition-checking loop.
7788                                 }
7789                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7790                              }
7791                              if(engineOpponent) {
7792                                SendToProgram("force\n", engineOpponent); // suppress reply
7793                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7794                              }
7795                              GameEnds( result, details, GE_XBOARD );
7796                              return 1;
7797                         }
7798                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7799                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7800                     }
7801                 }
7802
7803                 /* Now we test for 50-move draws. Determine ply count */
7804                 count = forwardMostMove;
7805                 /* look for last irreversble move */
7806                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7807                     count--;
7808                 /* if we hit starting position, add initial plies */
7809                 if( count == backwardMostMove )
7810                     count -= initialRulePlies;
7811                 count = forwardMostMove - count;
7812                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7813                         // adjust reversible move counter for checks in Xiangqi
7814                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7815                         if(i < backwardMostMove) i = backwardMostMove;
7816                         while(i <= forwardMostMove) {
7817                                 lastCheck = inCheck; // check evasion does not count
7818                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7819                                 if(inCheck || lastCheck) count--; // check does not count
7820                                 i++;
7821                         }
7822                 }
7823                 if( count >= 100)
7824                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7825                          /* this is used to judge if draw claims are legal */
7826                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7827                          if(engineOpponent) {
7828                            SendToProgram("force\n", engineOpponent); // suppress reply
7829                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7830                          }
7831                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7832                          return 1;
7833                 }
7834
7835                 /* if draw offer is pending, treat it as a draw claim
7836                  * when draw condition present, to allow engines a way to
7837                  * claim draws before making their move to avoid a race
7838                  * condition occurring after their move
7839                  */
7840                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7841                          char *p = NULL;
7842                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7843                              p = "Draw claim: 50-move rule";
7844                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7845                              p = "Draw claim: 3-fold repetition";
7846                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7847                              p = "Draw claim: insufficient mating material";
7848                          if( p != NULL && canAdjudicate) {
7849                              if(engineOpponent) {
7850                                SendToProgram("force\n", engineOpponent); // suppress reply
7851                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7852                              }
7853                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7854                              return 1;
7855                          }
7856                 }
7857
7858                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7859                     if(engineOpponent) {
7860                       SendToProgram("force\n", engineOpponent); // suppress reply
7861                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7862                     }
7863                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7864                     return 1;
7865                 }
7866         return 0;
7867 }
7868
7869 char *
7870 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7871 {   // [HGM] book: this routine intercepts moves to simulate book replies
7872     char *bookHit = NULL;
7873
7874     //first determine if the incoming move brings opponent into his book
7875     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7876         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7877     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7878     if(bookHit != NULL && !cps->bookSuspend) {
7879         // make sure opponent is not going to reply after receiving move to book position
7880         SendToProgram("force\n", cps);
7881         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7882     }
7883     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7884     // now arrange restart after book miss
7885     if(bookHit) {
7886         // after a book hit we never send 'go', and the code after the call to this routine
7887         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7888         char buf[MSG_SIZ], *move = bookHit;
7889         if(cps->useSAN) {
7890             int fromX, fromY, toX, toY;
7891             char promoChar;
7892             ChessMove moveType;
7893             move = buf + 30;
7894             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7895                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7896                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7897                                     PosFlags(forwardMostMove),
7898                                     fromY, fromX, toY, toX, promoChar, move);
7899             } else {
7900                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7901                 bookHit = NULL;
7902             }
7903         }
7904         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7905         SendToProgram(buf, cps);
7906         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7907     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7908         SendToProgram("go\n", cps);
7909         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7910     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7911         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7912             SendToProgram("go\n", cps);
7913         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7914     }
7915     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7916 }
7917
7918 char *savedMessage;
7919 ChessProgramState *savedState;
7920 void
7921 DeferredBookMove (void)
7922 {
7923         if(savedState->lastPing != savedState->lastPong)
7924                     ScheduleDelayedEvent(DeferredBookMove, 10);
7925         else
7926         HandleMachineMove(savedMessage, savedState);
7927 }
7928
7929 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7930
7931 void
7932 HandleMachineMove (char *message, ChessProgramState *cps)
7933 {
7934     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7935     char realname[MSG_SIZ];
7936     int fromX, fromY, toX, toY;
7937     ChessMove moveType;
7938     char promoChar;
7939     char *p, *pv=buf1;
7940     int machineWhite;
7941     char *bookHit;
7942
7943     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7944         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7945         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7946             DisplayError(_("Invalid pairing from pairing engine"), 0);
7947             return;
7948         }
7949         pairingReceived = 1;
7950         NextMatchGame();
7951         return; // Skim the pairing messages here.
7952     }
7953
7954     cps->userError = 0;
7955
7956 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7957     /*
7958      * Kludge to ignore BEL characters
7959      */
7960     while (*message == '\007') message++;
7961
7962     /*
7963      * [HGM] engine debug message: ignore lines starting with '#' character
7964      */
7965     if(cps->debug && *message == '#') return;
7966
7967     /*
7968      * Look for book output
7969      */
7970     if (cps == &first && bookRequested) {
7971         if (message[0] == '\t' || message[0] == ' ') {
7972             /* Part of the book output is here; append it */
7973             strcat(bookOutput, message);
7974             strcat(bookOutput, "  \n");
7975             return;
7976         } else if (bookOutput[0] != NULLCHAR) {
7977             /* All of book output has arrived; display it */
7978             char *p = bookOutput;
7979             while (*p != NULLCHAR) {
7980                 if (*p == '\t') *p = ' ';
7981                 p++;
7982             }
7983             DisplayInformation(bookOutput);
7984             bookRequested = FALSE;
7985             /* Fall through to parse the current output */
7986         }
7987     }
7988
7989     /*
7990      * Look for machine move.
7991      */
7992     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7993         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7994     {
7995         /* This method is only useful on engines that support ping */
7996         if (cps->lastPing != cps->lastPong) {
7997           if (gameMode == BeginningOfGame) {
7998             /* Extra move from before last new; ignore */
7999             if (appData.debugMode) {
8000                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8001             }
8002           } else {
8003             if (appData.debugMode) {
8004                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8005                         cps->which, gameMode);
8006             }
8007
8008             SendToProgram("undo\n", cps);
8009           }
8010           return;
8011         }
8012
8013         switch (gameMode) {
8014           case BeginningOfGame:
8015             /* Extra move from before last reset; ignore */
8016             if (appData.debugMode) {
8017                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8018             }
8019             return;
8020
8021           case EndOfGame:
8022           case IcsIdle:
8023           default:
8024             /* Extra move after we tried to stop.  The mode test is
8025                not a reliable way of detecting this problem, but it's
8026                the best we can do on engines that don't support ping.
8027             */
8028             if (appData.debugMode) {
8029                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8030                         cps->which, gameMode);
8031             }
8032             SendToProgram("undo\n", cps);
8033             return;
8034
8035           case MachinePlaysWhite:
8036           case IcsPlayingWhite:
8037             machineWhite = TRUE;
8038             break;
8039
8040           case MachinePlaysBlack:
8041           case IcsPlayingBlack:
8042             machineWhite = FALSE;
8043             break;
8044
8045           case TwoMachinesPlay:
8046             machineWhite = (cps->twoMachinesColor[0] == 'w');
8047             break;
8048         }
8049         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8050             if (appData.debugMode) {
8051                 fprintf(debugFP,
8052                         "Ignoring move out of turn by %s, gameMode %d"
8053                         ", forwardMost %d\n",
8054                         cps->which, gameMode, forwardMostMove);
8055             }
8056             return;
8057         }
8058
8059         if(cps->alphaRank) AlphaRank(machineMove, 4);
8060         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8061                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8062             /* Machine move could not be parsed; ignore it. */
8063           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8064                     machineMove, _(cps->which));
8065             DisplayError(buf1, 0);
8066             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
8067                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
8068             if (gameMode == TwoMachinesPlay) {
8069               GameEnds(machineWhite ? BlackWins : WhiteWins,
8070                        buf1, GE_XBOARD);
8071             }
8072             return;
8073         }
8074
8075         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8076         /* So we have to redo legality test with true e.p. status here,  */
8077         /* to make sure an illegal e.p. capture does not slip through,   */
8078         /* to cause a forfeit on a justified illegal-move complaint      */
8079         /* of the opponent.                                              */
8080         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8081            ChessMove moveType;
8082            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8083                              fromY, fromX, toY, toX, promoChar);
8084             if(moveType == IllegalMove) {
8085               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8086                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8087                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8088                            buf1, GE_XBOARD);
8089                 return;
8090            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
8091            /* [HGM] Kludge to handle engines that send FRC-style castling
8092               when they shouldn't (like TSCP-Gothic) */
8093            switch(moveType) {
8094              case WhiteASideCastleFR:
8095              case BlackASideCastleFR:
8096                toX+=2;
8097                currentMoveString[2]++;
8098                break;
8099              case WhiteHSideCastleFR:
8100              case BlackHSideCastleFR:
8101                toX--;
8102                currentMoveString[2]--;
8103                break;
8104              default: ; // nothing to do, but suppresses warning of pedantic compilers
8105            }
8106         }
8107         hintRequested = FALSE;
8108         lastHint[0] = NULLCHAR;
8109         bookRequested = FALSE;
8110         /* Program may be pondering now */
8111         cps->maybeThinking = TRUE;
8112         if (cps->sendTime == 2) cps->sendTime = 1;
8113         if (cps->offeredDraw) cps->offeredDraw--;
8114
8115         /* [AS] Save move info*/
8116         pvInfoList[ forwardMostMove ].score = programStats.score;
8117         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8118         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8119
8120         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8121
8122         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8123         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8124             int count = 0;
8125
8126             while( count < adjudicateLossPlies ) {
8127                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8128
8129                 if( count & 1 ) {
8130                     score = -score; /* Flip score for winning side */
8131                 }
8132
8133                 if( score > adjudicateLossThreshold ) {
8134                     break;
8135                 }
8136
8137                 count++;
8138             }
8139
8140             if( count >= adjudicateLossPlies ) {
8141                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8142
8143                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8144                     "Xboard adjudication",
8145                     GE_XBOARD );
8146
8147                 return;
8148             }
8149         }
8150
8151         if(Adjudicate(cps)) {
8152             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8153             return; // [HGM] adjudicate: for all automatic game ends
8154         }
8155
8156 #if ZIPPY
8157         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8158             first.initDone) {
8159           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8160                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8161                 SendToICS("draw ");
8162                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8163           }
8164           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8165           ics_user_moved = 1;
8166           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8167                 char buf[3*MSG_SIZ];
8168
8169                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8170                         programStats.score / 100.,
8171                         programStats.depth,
8172                         programStats.time / 100.,
8173                         (unsigned int)programStats.nodes,
8174                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8175                         programStats.movelist);
8176                 SendToICS(buf);
8177 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8178           }
8179         }
8180 #endif
8181
8182         /* [AS] Clear stats for next move */
8183         ClearProgramStats();
8184         thinkOutput[0] = NULLCHAR;
8185         hiddenThinkOutputState = 0;
8186
8187         bookHit = NULL;
8188         if (gameMode == TwoMachinesPlay) {
8189             /* [HGM] relaying draw offers moved to after reception of move */
8190             /* and interpreting offer as claim if it brings draw condition */
8191             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8192                 SendToProgram("draw\n", cps->other);
8193             }
8194             if (cps->other->sendTime) {
8195                 SendTimeRemaining(cps->other,
8196                                   cps->other->twoMachinesColor[0] == 'w');
8197             }
8198             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8199             if (firstMove && !bookHit) {
8200                 firstMove = FALSE;
8201                 if (cps->other->useColors) {
8202                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8203                 }
8204                 SendToProgram("go\n", cps->other);
8205             }
8206             cps->other->maybeThinking = TRUE;
8207         }
8208
8209         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8210
8211         if (!pausing && appData.ringBellAfterMoves) {
8212             RingBell();
8213         }
8214
8215         /*
8216          * Reenable menu items that were disabled while
8217          * machine was thinking
8218          */
8219         if (gameMode != TwoMachinesPlay)
8220             SetUserThinkingEnables();
8221
8222         // [HGM] book: after book hit opponent has received move and is now in force mode
8223         // force the book reply into it, and then fake that it outputted this move by jumping
8224         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8225         if(bookHit) {
8226                 static char bookMove[MSG_SIZ]; // a bit generous?
8227
8228                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8229                 strcat(bookMove, bookHit);
8230                 message = bookMove;
8231                 cps = cps->other;
8232                 programStats.nodes = programStats.depth = programStats.time =
8233                 programStats.score = programStats.got_only_move = 0;
8234                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8235
8236                 if(cps->lastPing != cps->lastPong) {
8237                     savedMessage = message; // args for deferred call
8238                     savedState = cps;
8239                     ScheduleDelayedEvent(DeferredBookMove, 10);
8240                     return;
8241                 }
8242                 goto FakeBookMove;
8243         }
8244
8245         return;
8246     }
8247
8248     /* Set special modes for chess engines.  Later something general
8249      *  could be added here; for now there is just one kludge feature,
8250      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8251      *  when "xboard" is given as an interactive command.
8252      */
8253     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8254         cps->useSigint = FALSE;
8255         cps->useSigterm = FALSE;
8256     }
8257     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8258       ParseFeatures(message+8, cps);
8259       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8260     }
8261
8262     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8263                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8264       int dummy, s=6; char buf[MSG_SIZ];
8265       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8266       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8267       if(startedFromSetupPosition) return;
8268       ParseFEN(boards[0], &dummy, message+s);
8269       DrawPosition(TRUE, boards[0]);
8270       startedFromSetupPosition = TRUE;
8271       return;
8272     }
8273     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8274      * want this, I was asked to put it in, and obliged.
8275      */
8276     if (!strncmp(message, "setboard ", 9)) {
8277         Board initial_position;
8278
8279         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8280
8281         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8282             DisplayError(_("Bad FEN received from engine"), 0);
8283             return ;
8284         } else {
8285            Reset(TRUE, FALSE);
8286            CopyBoard(boards[0], initial_position);
8287            initialRulePlies = FENrulePlies;
8288            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8289            else gameMode = MachinePlaysBlack;
8290            DrawPosition(FALSE, boards[currentMove]);
8291         }
8292         return;
8293     }
8294
8295     /*
8296      * Look for communication commands
8297      */
8298     if (!strncmp(message, "telluser ", 9)) {
8299         if(message[9] == '\\' && message[10] == '\\')
8300             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8301         PlayTellSound();
8302         DisplayNote(message + 9);
8303         return;
8304     }
8305     if (!strncmp(message, "tellusererror ", 14)) {
8306         cps->userError = 1;
8307         if(message[14] == '\\' && message[15] == '\\')
8308             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8309         PlayTellSound();
8310         DisplayError(message + 14, 0);
8311         return;
8312     }
8313     if (!strncmp(message, "tellopponent ", 13)) {
8314       if (appData.icsActive) {
8315         if (loggedOn) {
8316           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8317           SendToICS(buf1);
8318         }
8319       } else {
8320         DisplayNote(message + 13);
8321       }
8322       return;
8323     }
8324     if (!strncmp(message, "tellothers ", 11)) {
8325       if (appData.icsActive) {
8326         if (loggedOn) {
8327           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8328           SendToICS(buf1);
8329         }
8330       }
8331       return;
8332     }
8333     if (!strncmp(message, "tellall ", 8)) {
8334       if (appData.icsActive) {
8335         if (loggedOn) {
8336           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8337           SendToICS(buf1);
8338         }
8339       } else {
8340         DisplayNote(message + 8);
8341       }
8342       return;
8343     }
8344     if (strncmp(message, "warning", 7) == 0) {
8345         /* Undocumented feature, use tellusererror in new code */
8346         DisplayError(message, 0);
8347         return;
8348     }
8349     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8350         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8351         strcat(realname, " query");
8352         AskQuestion(realname, buf2, buf1, cps->pr);
8353         return;
8354     }
8355     /* Commands from the engine directly to ICS.  We don't allow these to be
8356      *  sent until we are logged on. Crafty kibitzes have been known to
8357      *  interfere with the login process.
8358      */
8359     if (loggedOn) {
8360         if (!strncmp(message, "tellics ", 8)) {
8361             SendToICS(message + 8);
8362             SendToICS("\n");
8363             return;
8364         }
8365         if (!strncmp(message, "tellicsnoalias ", 15)) {
8366             SendToICS(ics_prefix);
8367             SendToICS(message + 15);
8368             SendToICS("\n");
8369             return;
8370         }
8371         /* The following are for backward compatibility only */
8372         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8373             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8374             SendToICS(ics_prefix);
8375             SendToICS(message);
8376             SendToICS("\n");
8377             return;
8378         }
8379     }
8380     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8381         return;
8382     }
8383     /*
8384      * If the move is illegal, cancel it and redraw the board.
8385      * Also deal with other error cases.  Matching is rather loose
8386      * here to accommodate engines written before the spec.
8387      */
8388     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8389         strncmp(message, "Error", 5) == 0) {
8390         if (StrStr(message, "name") ||
8391             StrStr(message, "rating") || StrStr(message, "?") ||
8392             StrStr(message, "result") || StrStr(message, "board") ||
8393             StrStr(message, "bk") || StrStr(message, "computer") ||
8394             StrStr(message, "variant") || StrStr(message, "hint") ||
8395             StrStr(message, "random") || StrStr(message, "depth") ||
8396             StrStr(message, "accepted")) {
8397             return;
8398         }
8399         if (StrStr(message, "protover")) {
8400           /* Program is responding to input, so it's apparently done
8401              initializing, and this error message indicates it is
8402              protocol version 1.  So we don't need to wait any longer
8403              for it to initialize and send feature commands. */
8404           FeatureDone(cps, 1);
8405           cps->protocolVersion = 1;
8406           return;
8407         }
8408         cps->maybeThinking = FALSE;
8409
8410         if (StrStr(message, "draw")) {
8411             /* Program doesn't have "draw" command */
8412             cps->sendDrawOffers = 0;
8413             return;
8414         }
8415         if (cps->sendTime != 1 &&
8416             (StrStr(message, "time") || StrStr(message, "otim"))) {
8417           /* Program apparently doesn't have "time" or "otim" command */
8418           cps->sendTime = 0;
8419           return;
8420         }
8421         if (StrStr(message, "analyze")) {
8422             cps->analysisSupport = FALSE;
8423             cps->analyzing = FALSE;
8424 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8425             EditGameEvent(); // [HGM] try to preserve loaded game
8426             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8427             DisplayError(buf2, 0);
8428             return;
8429         }
8430         if (StrStr(message, "(no matching move)st")) {
8431           /* Special kludge for GNU Chess 4 only */
8432           cps->stKludge = TRUE;
8433           SendTimeControl(cps, movesPerSession, timeControl,
8434                           timeIncrement, appData.searchDepth,
8435                           searchTime);
8436           return;
8437         }
8438         if (StrStr(message, "(no matching move)sd")) {
8439           /* Special kludge for GNU Chess 4 only */
8440           cps->sdKludge = TRUE;
8441           SendTimeControl(cps, movesPerSession, timeControl,
8442                           timeIncrement, appData.searchDepth,
8443                           searchTime);
8444           return;
8445         }
8446         if (!StrStr(message, "llegal")) {
8447             return;
8448         }
8449         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8450             gameMode == IcsIdle) return;
8451         if (forwardMostMove <= backwardMostMove) return;
8452         if (pausing) PauseEvent();
8453       if(appData.forceIllegal) {
8454             // [HGM] illegal: machine refused move; force position after move into it
8455           SendToProgram("force\n", cps);
8456           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8457                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8458                 // when black is to move, while there might be nothing on a2 or black
8459                 // might already have the move. So send the board as if white has the move.
8460                 // But first we must change the stm of the engine, as it refused the last move
8461                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8462                 if(WhiteOnMove(forwardMostMove)) {
8463                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8464                     SendBoard(cps, forwardMostMove); // kludgeless board
8465                 } else {
8466                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8467                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8468                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8469                 }
8470           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8471             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8472                  gameMode == TwoMachinesPlay)
8473               SendToProgram("go\n", cps);
8474             return;
8475       } else
8476         if (gameMode == PlayFromGameFile) {
8477             /* Stop reading this game file */
8478             gameMode = EditGame;
8479             ModeHighlight();
8480         }
8481         /* [HGM] illegal-move claim should forfeit game when Xboard */
8482         /* only passes fully legal moves                            */
8483         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8484             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8485                                 "False illegal-move claim", GE_XBOARD );
8486             return; // do not take back move we tested as valid
8487         }
8488         currentMove = forwardMostMove-1;
8489         DisplayMove(currentMove-1); /* before DisplayMoveError */
8490         SwitchClocks(forwardMostMove-1); // [HGM] race
8491         DisplayBothClocks();
8492         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8493                 parseList[currentMove], _(cps->which));
8494         DisplayMoveError(buf1);
8495         DrawPosition(FALSE, boards[currentMove]);
8496
8497         SetUserThinkingEnables();
8498         return;
8499     }
8500     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8501         /* Program has a broken "time" command that
8502            outputs a string not ending in newline.
8503            Don't use it. */
8504         cps->sendTime = 0;
8505     }
8506
8507     /*
8508      * If chess program startup fails, exit with an error message.
8509      * Attempts to recover here are futile. [HGM] Well, we try anyway
8510      */
8511     if ((StrStr(message, "unknown host") != NULL)
8512         || (StrStr(message, "No remote directory") != NULL)
8513         || (StrStr(message, "not found") != NULL)
8514         || (StrStr(message, "No such file") != NULL)
8515         || (StrStr(message, "can't alloc") != NULL)
8516         || (StrStr(message, "Permission denied") != NULL)) {
8517
8518         cps->maybeThinking = FALSE;
8519         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8520                 _(cps->which), cps->program, cps->host, message);
8521         RemoveInputSource(cps->isr);
8522         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8523             cps->isr = NULL;
8524             DestroyChildProcess(cps->pr, 9 ); // just to be sure
8525             cps->pr = NoProc; 
8526             if(cps == &first) {
8527                 appData.noChessProgram = TRUE;
8528                 gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8529                 gameMode = BeginningOfGame; ModeHighlight();
8530                 SetNCPMode();
8531             }
8532             if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8533             DisplayMessage("", ""); // erase waiting message
8534             DisplayError(buf1, 0);
8535         }
8536         return;
8537     }
8538
8539     /*
8540      * Look for hint output
8541      */
8542     if (sscanf(message, "Hint: %s", buf1) == 1) {
8543         if (cps == &first && hintRequested) {
8544             hintRequested = FALSE;
8545             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8546                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8547                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8548                                     PosFlags(forwardMostMove),
8549                                     fromY, fromX, toY, toX, promoChar, buf1);
8550                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8551                 DisplayInformation(buf2);
8552             } else {
8553                 /* Hint move could not be parsed!? */
8554               snprintf(buf2, sizeof(buf2),
8555                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8556                         buf1, _(cps->which));
8557                 DisplayError(buf2, 0);
8558             }
8559         } else {
8560           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8561         }
8562         return;
8563     }
8564
8565     /*
8566      * Ignore other messages if game is not in progress
8567      */
8568     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8569         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8570
8571     /*
8572      * look for win, lose, draw, or draw offer
8573      */
8574     if (strncmp(message, "1-0", 3) == 0) {
8575         char *p, *q, *r = "";
8576         p = strchr(message, '{');
8577         if (p) {
8578             q = strchr(p, '}');
8579             if (q) {
8580                 *q = NULLCHAR;
8581                 r = p + 1;
8582             }
8583         }
8584         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8585         return;
8586     } else if (strncmp(message, "0-1", 3) == 0) {
8587         char *p, *q, *r = "";
8588         p = strchr(message, '{');
8589         if (p) {
8590             q = strchr(p, '}');
8591             if (q) {
8592                 *q = NULLCHAR;
8593                 r = p + 1;
8594             }
8595         }
8596         /* Kludge for Arasan 4.1 bug */
8597         if (strcmp(r, "Black resigns") == 0) {
8598             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8599             return;
8600         }
8601         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8602         return;
8603     } else if (strncmp(message, "1/2", 3) == 0) {
8604         char *p, *q, *r = "";
8605         p = strchr(message, '{');
8606         if (p) {
8607             q = strchr(p, '}');
8608             if (q) {
8609                 *q = NULLCHAR;
8610                 r = p + 1;
8611             }
8612         }
8613
8614         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8615         return;
8616
8617     } else if (strncmp(message, "White resign", 12) == 0) {
8618         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8619         return;
8620     } else if (strncmp(message, "Black resign", 12) == 0) {
8621         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8622         return;
8623     } else if (strncmp(message, "White matches", 13) == 0 ||
8624                strncmp(message, "Black matches", 13) == 0   ) {
8625         /* [HGM] ignore GNUShogi noises */
8626         return;
8627     } else if (strncmp(message, "White", 5) == 0 &&
8628                message[5] != '(' &&
8629                StrStr(message, "Black") == NULL) {
8630         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8631         return;
8632     } else if (strncmp(message, "Black", 5) == 0 &&
8633                message[5] != '(') {
8634         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8635         return;
8636     } else if (strcmp(message, "resign") == 0 ||
8637                strcmp(message, "computer resigns") == 0) {
8638         switch (gameMode) {
8639           case MachinePlaysBlack:
8640           case IcsPlayingBlack:
8641             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8642             break;
8643           case MachinePlaysWhite:
8644           case IcsPlayingWhite:
8645             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8646             break;
8647           case TwoMachinesPlay:
8648             if (cps->twoMachinesColor[0] == 'w')
8649               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8650             else
8651               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8652             break;
8653           default:
8654             /* can't happen */
8655             break;
8656         }
8657         return;
8658     } else if (strncmp(message, "opponent mates", 14) == 0) {
8659         switch (gameMode) {
8660           case MachinePlaysBlack:
8661           case IcsPlayingBlack:
8662             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8663             break;
8664           case MachinePlaysWhite:
8665           case IcsPlayingWhite:
8666             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8667             break;
8668           case TwoMachinesPlay:
8669             if (cps->twoMachinesColor[0] == 'w')
8670               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8671             else
8672               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8673             break;
8674           default:
8675             /* can't happen */
8676             break;
8677         }
8678         return;
8679     } else if (strncmp(message, "computer mates", 14) == 0) {
8680         switch (gameMode) {
8681           case MachinePlaysBlack:
8682           case IcsPlayingBlack:
8683             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8684             break;
8685           case MachinePlaysWhite:
8686           case IcsPlayingWhite:
8687             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8688             break;
8689           case TwoMachinesPlay:
8690             if (cps->twoMachinesColor[0] == 'w')
8691               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8692             else
8693               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8694             break;
8695           default:
8696             /* can't happen */
8697             break;
8698         }
8699         return;
8700     } else if (strncmp(message, "checkmate", 9) == 0) {
8701         if (WhiteOnMove(forwardMostMove)) {
8702             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8703         } else {
8704             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8705         }
8706         return;
8707     } else if (strstr(message, "Draw") != NULL ||
8708                strstr(message, "game is a draw") != NULL) {
8709         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8710         return;
8711     } else if (strstr(message, "offer") != NULL &&
8712                strstr(message, "draw") != NULL) {
8713 #if ZIPPY
8714         if (appData.zippyPlay && first.initDone) {
8715             /* Relay offer to ICS */
8716             SendToICS(ics_prefix);
8717             SendToICS("draw\n");
8718         }
8719 #endif
8720         cps->offeredDraw = 2; /* valid until this engine moves twice */
8721         if (gameMode == TwoMachinesPlay) {
8722             if (cps->other->offeredDraw) {
8723                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8724             /* [HGM] in two-machine mode we delay relaying draw offer      */
8725             /* until after we also have move, to see if it is really claim */
8726             }
8727         } else if (gameMode == MachinePlaysWhite ||
8728                    gameMode == MachinePlaysBlack) {
8729           if (userOfferedDraw) {
8730             DisplayInformation(_("Machine accepts your draw offer"));
8731             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8732           } else {
8733             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8734           }
8735         }
8736     }
8737
8738
8739     /*
8740      * Look for thinking output
8741      */
8742     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8743           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8744                                 ) {
8745         int plylev, mvleft, mvtot, curscore, time;
8746         char mvname[MOVE_LEN];
8747         u64 nodes; // [DM]
8748         char plyext;
8749         int ignore = FALSE;
8750         int prefixHint = FALSE;
8751         mvname[0] = NULLCHAR;
8752
8753         switch (gameMode) {
8754           case MachinePlaysBlack:
8755           case IcsPlayingBlack:
8756             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8757             break;
8758           case MachinePlaysWhite:
8759           case IcsPlayingWhite:
8760             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8761             break;
8762           case AnalyzeMode:
8763           case AnalyzeFile:
8764             break;
8765           case IcsObserving: /* [DM] icsEngineAnalyze */
8766             if (!appData.icsEngineAnalyze) ignore = TRUE;
8767             break;
8768           case TwoMachinesPlay:
8769             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8770                 ignore = TRUE;
8771             }
8772             break;
8773           default:
8774             ignore = TRUE;
8775             break;
8776         }
8777
8778         if (!ignore) {
8779             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8780             buf1[0] = NULLCHAR;
8781             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8782                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8783
8784                 if (plyext != ' ' && plyext != '\t') {
8785                     time *= 100;
8786                 }
8787
8788                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8789                 if( cps->scoreIsAbsolute &&
8790                     ( gameMode == MachinePlaysBlack ||
8791                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8792                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8793                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8794                      !WhiteOnMove(currentMove)
8795                     ) )
8796                 {
8797                     curscore = -curscore;
8798                 }
8799
8800                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8801
8802                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8803                         char buf[MSG_SIZ];
8804                         FILE *f;
8805                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8806                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8807                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8808                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8809                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8810                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8811                                 fclose(f);
8812                         } else DisplayError(_("failed writing PV"), 0);
8813                 }
8814
8815                 tempStats.depth = plylev;
8816                 tempStats.nodes = nodes;
8817                 tempStats.time = time;
8818                 tempStats.score = curscore;
8819                 tempStats.got_only_move = 0;
8820
8821                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8822                         int ticklen;
8823
8824                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8825                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8826                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8827                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8828                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8829                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8830                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8831                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8832                 }
8833
8834                 /* Buffer overflow protection */
8835                 if (pv[0] != NULLCHAR) {
8836                     if (strlen(pv) >= sizeof(tempStats.movelist)
8837                         && appData.debugMode) {
8838                         fprintf(debugFP,
8839                                 "PV is too long; using the first %u bytes.\n",
8840                                 (unsigned) sizeof(tempStats.movelist) - 1);
8841                     }
8842
8843                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8844                 } else {
8845                     sprintf(tempStats.movelist, " no PV\n");
8846                 }
8847
8848                 if (tempStats.seen_stat) {
8849                     tempStats.ok_to_send = 1;
8850                 }
8851
8852                 if (strchr(tempStats.movelist, '(') != NULL) {
8853                     tempStats.line_is_book = 1;
8854                     tempStats.nr_moves = 0;
8855                     tempStats.moves_left = 0;
8856                 } else {
8857                     tempStats.line_is_book = 0;
8858                 }
8859
8860                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8861                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8862
8863                 SendProgramStatsToFrontend( cps, &tempStats );
8864
8865                 /*
8866                     [AS] Protect the thinkOutput buffer from overflow... this
8867                     is only useful if buf1 hasn't overflowed first!
8868                 */
8869                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8870                          plylev,
8871                          (gameMode == TwoMachinesPlay ?
8872                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8873                          ((double) curscore) / 100.0,
8874                          prefixHint ? lastHint : "",
8875                          prefixHint ? " " : "" );
8876
8877                 if( buf1[0] != NULLCHAR ) {
8878                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8879
8880                     if( strlen(pv) > max_len ) {
8881                         if( appData.debugMode) {
8882                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8883                         }
8884                         pv[max_len+1] = '\0';
8885                     }
8886
8887                     strcat( thinkOutput, pv);
8888                 }
8889
8890                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8891                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8892                     DisplayMove(currentMove - 1);
8893                 }
8894                 return;
8895
8896             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8897                 /* crafty (9.25+) says "(only move) <move>"
8898                  * if there is only 1 legal move
8899                  */
8900                 sscanf(p, "(only move) %s", buf1);
8901                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8902                 sprintf(programStats.movelist, "%s (only move)", buf1);
8903                 programStats.depth = 1;
8904                 programStats.nr_moves = 1;
8905                 programStats.moves_left = 1;
8906                 programStats.nodes = 1;
8907                 programStats.time = 1;
8908                 programStats.got_only_move = 1;
8909
8910                 /* Not really, but we also use this member to
8911                    mean "line isn't going to change" (Crafty
8912                    isn't searching, so stats won't change) */
8913                 programStats.line_is_book = 1;
8914
8915                 SendProgramStatsToFrontend( cps, &programStats );
8916
8917                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8918                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8919                     DisplayMove(currentMove - 1);
8920                 }
8921                 return;
8922             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8923                               &time, &nodes, &plylev, &mvleft,
8924                               &mvtot, mvname) >= 5) {
8925                 /* The stat01: line is from Crafty (9.29+) in response
8926                    to the "." command */
8927                 programStats.seen_stat = 1;
8928                 cps->maybeThinking = TRUE;
8929
8930                 if (programStats.got_only_move || !appData.periodicUpdates)
8931                   return;
8932
8933                 programStats.depth = plylev;
8934                 programStats.time = time;
8935                 programStats.nodes = nodes;
8936                 programStats.moves_left = mvleft;
8937                 programStats.nr_moves = mvtot;
8938                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8939                 programStats.ok_to_send = 1;
8940                 programStats.movelist[0] = '\0';
8941
8942                 SendProgramStatsToFrontend( cps, &programStats );
8943
8944                 return;
8945
8946             } else if (strncmp(message,"++",2) == 0) {
8947                 /* Crafty 9.29+ outputs this */
8948                 programStats.got_fail = 2;
8949                 return;
8950
8951             } else if (strncmp(message,"--",2) == 0) {
8952                 /* Crafty 9.29+ outputs this */
8953                 programStats.got_fail = 1;
8954                 return;
8955
8956             } else if (thinkOutput[0] != NULLCHAR &&
8957                        strncmp(message, "    ", 4) == 0) {
8958                 unsigned message_len;
8959
8960                 p = message;
8961                 while (*p && *p == ' ') p++;
8962
8963                 message_len = strlen( p );
8964
8965                 /* [AS] Avoid buffer overflow */
8966                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8967                     strcat(thinkOutput, " ");
8968                     strcat(thinkOutput, p);
8969                 }
8970
8971                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8972                     strcat(programStats.movelist, " ");
8973                     strcat(programStats.movelist, p);
8974                 }
8975
8976                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8977                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8978                     DisplayMove(currentMove - 1);
8979                 }
8980                 return;
8981             }
8982         }
8983         else {
8984             buf1[0] = NULLCHAR;
8985
8986             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8987                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8988             {
8989                 ChessProgramStats cpstats;
8990
8991                 if (plyext != ' ' && plyext != '\t') {
8992                     time *= 100;
8993                 }
8994
8995                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8996                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8997                     curscore = -curscore;
8998                 }
8999
9000                 cpstats.depth = plylev;
9001                 cpstats.nodes = nodes;
9002                 cpstats.time = time;
9003                 cpstats.score = curscore;
9004                 cpstats.got_only_move = 0;
9005                 cpstats.movelist[0] = '\0';
9006
9007                 if (buf1[0] != NULLCHAR) {
9008                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9009                 }
9010
9011                 cpstats.ok_to_send = 0;
9012                 cpstats.line_is_book = 0;
9013                 cpstats.nr_moves = 0;
9014                 cpstats.moves_left = 0;
9015
9016                 SendProgramStatsToFrontend( cps, &cpstats );
9017             }
9018         }
9019     }
9020 }
9021
9022
9023 /* Parse a game score from the character string "game", and
9024    record it as the history of the current game.  The game
9025    score is NOT assumed to start from the standard position.
9026    The display is not updated in any way.
9027    */
9028 void
9029 ParseGameHistory (char *game)
9030 {
9031     ChessMove moveType;
9032     int fromX, fromY, toX, toY, boardIndex;
9033     char promoChar;
9034     char *p, *q;
9035     char buf[MSG_SIZ];
9036
9037     if (appData.debugMode)
9038       fprintf(debugFP, "Parsing game history: %s\n", game);
9039
9040     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9041     gameInfo.site = StrSave(appData.icsHost);
9042     gameInfo.date = PGNDate();
9043     gameInfo.round = StrSave("-");
9044
9045     /* Parse out names of players */
9046     while (*game == ' ') game++;
9047     p = buf;
9048     while (*game != ' ') *p++ = *game++;
9049     *p = NULLCHAR;
9050     gameInfo.white = StrSave(buf);
9051     while (*game == ' ') game++;
9052     p = buf;
9053     while (*game != ' ' && *game != '\n') *p++ = *game++;
9054     *p = NULLCHAR;
9055     gameInfo.black = StrSave(buf);
9056
9057     /* Parse moves */
9058     boardIndex = blackPlaysFirst ? 1 : 0;
9059     yynewstr(game);
9060     for (;;) {
9061         yyboardindex = boardIndex;
9062         moveType = (ChessMove) Myylex();
9063         switch (moveType) {
9064           case IllegalMove:             /* maybe suicide chess, etc. */
9065   if (appData.debugMode) {
9066     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9067     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9068     setbuf(debugFP, NULL);
9069   }
9070           case WhitePromotion:
9071           case BlackPromotion:
9072           case WhiteNonPromotion:
9073           case BlackNonPromotion:
9074           case NormalMove:
9075           case WhiteCapturesEnPassant:
9076           case BlackCapturesEnPassant:
9077           case WhiteKingSideCastle:
9078           case WhiteQueenSideCastle:
9079           case BlackKingSideCastle:
9080           case BlackQueenSideCastle:
9081           case WhiteKingSideCastleWild:
9082           case WhiteQueenSideCastleWild:
9083           case BlackKingSideCastleWild:
9084           case BlackQueenSideCastleWild:
9085           /* PUSH Fabien */
9086           case WhiteHSideCastleFR:
9087           case WhiteASideCastleFR:
9088           case BlackHSideCastleFR:
9089           case BlackASideCastleFR:
9090           /* POP Fabien */
9091             fromX = currentMoveString[0] - AAA;
9092             fromY = currentMoveString[1] - ONE;
9093             toX = currentMoveString[2] - AAA;
9094             toY = currentMoveString[3] - ONE;
9095             promoChar = currentMoveString[4];
9096             break;
9097           case WhiteDrop:
9098           case BlackDrop:
9099             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9100             fromX = moveType == WhiteDrop ?
9101               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9102             (int) CharToPiece(ToLower(currentMoveString[0]));
9103             fromY = DROP_RANK;
9104             toX = currentMoveString[2] - AAA;
9105             toY = currentMoveString[3] - ONE;
9106             promoChar = NULLCHAR;
9107             break;
9108           case AmbiguousMove:
9109             /* bug? */
9110             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9111   if (appData.debugMode) {
9112     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9113     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9114     setbuf(debugFP, NULL);
9115   }
9116             DisplayError(buf, 0);
9117             return;
9118           case ImpossibleMove:
9119             /* bug? */
9120             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9121   if (appData.debugMode) {
9122     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9123     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9124     setbuf(debugFP, NULL);
9125   }
9126             DisplayError(buf, 0);
9127             return;
9128           case EndOfFile:
9129             if (boardIndex < backwardMostMove) {
9130                 /* Oops, gap.  How did that happen? */
9131                 DisplayError(_("Gap in move list"), 0);
9132                 return;
9133             }
9134             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9135             if (boardIndex > forwardMostMove) {
9136                 forwardMostMove = boardIndex;
9137             }
9138             return;
9139           case ElapsedTime:
9140             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9141                 strcat(parseList[boardIndex-1], " ");
9142                 strcat(parseList[boardIndex-1], yy_text);
9143             }
9144             continue;
9145           case Comment:
9146           case PGNTag:
9147           case NAG:
9148           default:
9149             /* ignore */
9150             continue;
9151           case WhiteWins:
9152           case BlackWins:
9153           case GameIsDrawn:
9154           case GameUnfinished:
9155             if (gameMode == IcsExamining) {
9156                 if (boardIndex < backwardMostMove) {
9157                     /* Oops, gap.  How did that happen? */
9158                     return;
9159                 }
9160                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9161                 return;
9162             }
9163             gameInfo.result = moveType;
9164             p = strchr(yy_text, '{');
9165             if (p == NULL) p = strchr(yy_text, '(');
9166             if (p == NULL) {
9167                 p = yy_text;
9168                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9169             } else {
9170                 q = strchr(p, *p == '{' ? '}' : ')');
9171                 if (q != NULL) *q = NULLCHAR;
9172                 p++;
9173             }
9174             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9175             gameInfo.resultDetails = StrSave(p);
9176             continue;
9177         }
9178         if (boardIndex >= forwardMostMove &&
9179             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9180             backwardMostMove = blackPlaysFirst ? 1 : 0;
9181             return;
9182         }
9183         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9184                                  fromY, fromX, toY, toX, promoChar,
9185                                  parseList[boardIndex]);
9186         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9187         /* currentMoveString is set as a side-effect of yylex */
9188         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9189         strcat(moveList[boardIndex], "\n");
9190         boardIndex++;
9191         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9192         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9193           case MT_NONE:
9194           case MT_STALEMATE:
9195           default:
9196             break;
9197           case MT_CHECK:
9198             if(gameInfo.variant != VariantShogi)
9199                 strcat(parseList[boardIndex - 1], "+");
9200             break;
9201           case MT_CHECKMATE:
9202           case MT_STAINMATE:
9203             strcat(parseList[boardIndex - 1], "#");
9204             break;
9205         }
9206     }
9207 }
9208
9209
9210 /* Apply a move to the given board  */
9211 void
9212 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9213 {
9214   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9215   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9216
9217     /* [HGM] compute & store e.p. status and castling rights for new position */
9218     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9219
9220       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9221       oldEP = (signed char)board[EP_STATUS];
9222       board[EP_STATUS] = EP_NONE;
9223
9224   if (fromY == DROP_RANK) {
9225         /* must be first */
9226         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9227             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9228             return;
9229         }
9230         piece = board[toY][toX] = (ChessSquare) fromX;
9231   } else {
9232       int i;
9233
9234       if( board[toY][toX] != EmptySquare )
9235            board[EP_STATUS] = EP_CAPTURE;
9236
9237       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9238            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9239                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9240       } else
9241       if( board[fromY][fromX] == WhitePawn ) {
9242            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9243                board[EP_STATUS] = EP_PAWN_MOVE;
9244            if( toY-fromY==2) {
9245                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9246                         gameInfo.variant != VariantBerolina || toX < fromX)
9247                       board[EP_STATUS] = toX | berolina;
9248                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9249                         gameInfo.variant != VariantBerolina || toX > fromX)
9250                       board[EP_STATUS] = toX;
9251            }
9252       } else
9253       if( board[fromY][fromX] == BlackPawn ) {
9254            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9255                board[EP_STATUS] = EP_PAWN_MOVE;
9256            if( toY-fromY== -2) {
9257                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9258                         gameInfo.variant != VariantBerolina || toX < fromX)
9259                       board[EP_STATUS] = toX | berolina;
9260                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9261                         gameInfo.variant != VariantBerolina || toX > fromX)
9262                       board[EP_STATUS] = toX;
9263            }
9264        }
9265
9266        for(i=0; i<nrCastlingRights; i++) {
9267            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9268               board[CASTLING][i] == toX   && castlingRank[i] == toY
9269              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9270        }
9271
9272      if (fromX == toX && fromY == toY) return;
9273
9274      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9275      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9276      if(gameInfo.variant == VariantKnightmate)
9277          king += (int) WhiteUnicorn - (int) WhiteKing;
9278
9279     /* Code added by Tord: */
9280     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9281     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9282         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9283       board[fromY][fromX] = EmptySquare;
9284       board[toY][toX] = EmptySquare;
9285       if((toX > fromX) != (piece == WhiteRook)) {
9286         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9287       } else {
9288         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9289       }
9290     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9291                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9292       board[fromY][fromX] = EmptySquare;
9293       board[toY][toX] = EmptySquare;
9294       if((toX > fromX) != (piece == BlackRook)) {
9295         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9296       } else {
9297         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9298       }
9299     /* End of code added by Tord */
9300
9301     } else if (board[fromY][fromX] == king
9302         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9303         && toY == fromY && toX > fromX+1) {
9304         board[fromY][fromX] = EmptySquare;
9305         board[toY][toX] = king;
9306         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9307         board[fromY][BOARD_RGHT-1] = EmptySquare;
9308     } else if (board[fromY][fromX] == king
9309         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9310                && toY == fromY && toX < fromX-1) {
9311         board[fromY][fromX] = EmptySquare;
9312         board[toY][toX] = king;
9313         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9314         board[fromY][BOARD_LEFT] = EmptySquare;
9315     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9316                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9317                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9318                ) {
9319         /* white pawn promotion */
9320         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9321         if(gameInfo.variant==VariantBughouse ||
9322            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9323             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9324         board[fromY][fromX] = EmptySquare;
9325     } else if ((fromY >= BOARD_HEIGHT>>1)
9326                && (toX != fromX)
9327                && gameInfo.variant != VariantXiangqi
9328                && gameInfo.variant != VariantBerolina
9329                && (board[fromY][fromX] == WhitePawn)
9330                && (board[toY][toX] == EmptySquare)) {
9331         board[fromY][fromX] = EmptySquare;
9332         board[toY][toX] = WhitePawn;
9333         captured = board[toY - 1][toX];
9334         board[toY - 1][toX] = EmptySquare;
9335     } else if ((fromY == BOARD_HEIGHT-4)
9336                && (toX == fromX)
9337                && gameInfo.variant == VariantBerolina
9338                && (board[fromY][fromX] == WhitePawn)
9339                && (board[toY][toX] == EmptySquare)) {
9340         board[fromY][fromX] = EmptySquare;
9341         board[toY][toX] = WhitePawn;
9342         if(oldEP & EP_BEROLIN_A) {
9343                 captured = board[fromY][fromX-1];
9344                 board[fromY][fromX-1] = EmptySquare;
9345         }else{  captured = board[fromY][fromX+1];
9346                 board[fromY][fromX+1] = EmptySquare;
9347         }
9348     } else if (board[fromY][fromX] == king
9349         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9350                && toY == fromY && toX > fromX+1) {
9351         board[fromY][fromX] = EmptySquare;
9352         board[toY][toX] = king;
9353         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9354         board[fromY][BOARD_RGHT-1] = EmptySquare;
9355     } else if (board[fromY][fromX] == king
9356         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9357                && toY == fromY && toX < fromX-1) {
9358         board[fromY][fromX] = EmptySquare;
9359         board[toY][toX] = king;
9360         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9361         board[fromY][BOARD_LEFT] = EmptySquare;
9362     } else if (fromY == 7 && fromX == 3
9363                && board[fromY][fromX] == BlackKing
9364                && toY == 7 && toX == 5) {
9365         board[fromY][fromX] = EmptySquare;
9366         board[toY][toX] = BlackKing;
9367         board[fromY][7] = EmptySquare;
9368         board[toY][4] = BlackRook;
9369     } else if (fromY == 7 && fromX == 3
9370                && board[fromY][fromX] == BlackKing
9371                && toY == 7 && toX == 1) {
9372         board[fromY][fromX] = EmptySquare;
9373         board[toY][toX] = BlackKing;
9374         board[fromY][0] = EmptySquare;
9375         board[toY][2] = BlackRook;
9376     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9377                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9378                && toY < promoRank && promoChar
9379                ) {
9380         /* black pawn promotion */
9381         board[toY][toX] = CharToPiece(ToLower(promoChar));
9382         if(gameInfo.variant==VariantBughouse ||
9383            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9384             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9385         board[fromY][fromX] = EmptySquare;
9386     } else if ((fromY < BOARD_HEIGHT>>1)
9387                && (toX != fromX)
9388                && gameInfo.variant != VariantXiangqi
9389                && gameInfo.variant != VariantBerolina
9390                && (board[fromY][fromX] == BlackPawn)
9391                && (board[toY][toX] == EmptySquare)) {
9392         board[fromY][fromX] = EmptySquare;
9393         board[toY][toX] = BlackPawn;
9394         captured = board[toY + 1][toX];
9395         board[toY + 1][toX] = EmptySquare;
9396     } else if ((fromY == 3)
9397                && (toX == fromX)
9398                && gameInfo.variant == VariantBerolina
9399                && (board[fromY][fromX] == BlackPawn)
9400                && (board[toY][toX] == EmptySquare)) {
9401         board[fromY][fromX] = EmptySquare;
9402         board[toY][toX] = BlackPawn;
9403         if(oldEP & EP_BEROLIN_A) {
9404                 captured = board[fromY][fromX-1];
9405                 board[fromY][fromX-1] = EmptySquare;
9406         }else{  captured = board[fromY][fromX+1];
9407                 board[fromY][fromX+1] = EmptySquare;
9408         }
9409     } else {
9410         board[toY][toX] = board[fromY][fromX];
9411         board[fromY][fromX] = EmptySquare;
9412     }
9413   }
9414
9415     if (gameInfo.holdingsWidth != 0) {
9416
9417       /* !!A lot more code needs to be written to support holdings  */
9418       /* [HGM] OK, so I have written it. Holdings are stored in the */
9419       /* penultimate board files, so they are automaticlly stored   */
9420       /* in the game history.                                       */
9421       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9422                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9423         /* Delete from holdings, by decreasing count */
9424         /* and erasing image if necessary            */
9425         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9426         if(p < (int) BlackPawn) { /* white drop */
9427              p -= (int)WhitePawn;
9428                  p = PieceToNumber((ChessSquare)p);
9429              if(p >= gameInfo.holdingsSize) p = 0;
9430              if(--board[p][BOARD_WIDTH-2] <= 0)
9431                   board[p][BOARD_WIDTH-1] = EmptySquare;
9432              if((int)board[p][BOARD_WIDTH-2] < 0)
9433                         board[p][BOARD_WIDTH-2] = 0;
9434         } else {                  /* black drop */
9435              p -= (int)BlackPawn;
9436                  p = PieceToNumber((ChessSquare)p);
9437              if(p >= gameInfo.holdingsSize) p = 0;
9438              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9439                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9440              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9441                         board[BOARD_HEIGHT-1-p][1] = 0;
9442         }
9443       }
9444       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9445           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9446         /* [HGM] holdings: Add to holdings, if holdings exist */
9447         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9448                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9449                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9450         }
9451         p = (int) captured;
9452         if (p >= (int) BlackPawn) {
9453           p -= (int)BlackPawn;
9454           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9455                   /* in Shogi restore piece to its original  first */
9456                   captured = (ChessSquare) (DEMOTED captured);
9457                   p = DEMOTED p;
9458           }
9459           p = PieceToNumber((ChessSquare)p);
9460           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9461           board[p][BOARD_WIDTH-2]++;
9462           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9463         } else {
9464           p -= (int)WhitePawn;
9465           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9466                   captured = (ChessSquare) (DEMOTED captured);
9467                   p = DEMOTED p;
9468           }
9469           p = PieceToNumber((ChessSquare)p);
9470           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9471           board[BOARD_HEIGHT-1-p][1]++;
9472           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9473         }
9474       }
9475     } else if (gameInfo.variant == VariantAtomic) {
9476       if (captured != EmptySquare) {
9477         int y, x;
9478         for (y = toY-1; y <= toY+1; y++) {
9479           for (x = toX-1; x <= toX+1; x++) {
9480             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9481                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9482               board[y][x] = EmptySquare;
9483             }
9484           }
9485         }
9486         board[toY][toX] = EmptySquare;
9487       }
9488     }
9489     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9490         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9491     } else
9492     if(promoChar == '+') {
9493         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
9494         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9495     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9496         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9497         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
9498            && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
9499         board[toY][toX] = newPiece;
9500     }
9501     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9502                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9503         // [HGM] superchess: take promotion piece out of holdings
9504         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9505         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9506             if(!--board[k][BOARD_WIDTH-2])
9507                 board[k][BOARD_WIDTH-1] = EmptySquare;
9508         } else {
9509             if(!--board[BOARD_HEIGHT-1-k][1])
9510                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9511         }
9512     }
9513
9514 }
9515
9516 /* Updates forwardMostMove */
9517 void
9518 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9519 {
9520 //    forwardMostMove++; // [HGM] bare: moved downstream
9521
9522     (void) CoordsToAlgebraic(boards[forwardMostMove],
9523                              PosFlags(forwardMostMove),
9524                              fromY, fromX, toY, toX, promoChar,
9525                              parseList[forwardMostMove]);
9526
9527     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9528         int timeLeft; static int lastLoadFlag=0; int king, piece;
9529         piece = boards[forwardMostMove][fromY][fromX];
9530         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9531         if(gameInfo.variant == VariantKnightmate)
9532             king += (int) WhiteUnicorn - (int) WhiteKing;
9533         if(forwardMostMove == 0) {
9534             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9535                 fprintf(serverMoves, "%s;", UserName());
9536             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9537                 fprintf(serverMoves, "%s;", second.tidy);
9538             fprintf(serverMoves, "%s;", first.tidy);
9539             if(gameMode == MachinePlaysWhite)
9540                 fprintf(serverMoves, "%s;", UserName());
9541             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9542                 fprintf(serverMoves, "%s;", second.tidy);
9543         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9544         lastLoadFlag = loadFlag;
9545         // print base move
9546         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9547         // print castling suffix
9548         if( toY == fromY && piece == king ) {
9549             if(toX-fromX > 1)
9550                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9551             if(fromX-toX >1)
9552                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9553         }
9554         // e.p. suffix
9555         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9556              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9557              boards[forwardMostMove][toY][toX] == EmptySquare
9558              && fromX != toX && fromY != toY)
9559                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9560         // promotion suffix
9561         if(promoChar != NULLCHAR)
9562                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9563         if(!loadFlag) {
9564                 char buf[MOVE_LEN*2], *p; int len;
9565             fprintf(serverMoves, "/%d/%d",
9566                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9567             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9568             else                      timeLeft = blackTimeRemaining/1000;
9569             fprintf(serverMoves, "/%d", timeLeft);
9570                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9571                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9572                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9573             fprintf(serverMoves, "/%s", buf);
9574         }
9575         fflush(serverMoves);
9576     }
9577
9578     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9579         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9580       return;
9581     }
9582     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9583     if (commentList[forwardMostMove+1] != NULL) {
9584         free(commentList[forwardMostMove+1]);
9585         commentList[forwardMostMove+1] = NULL;
9586     }
9587     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9588     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9589     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9590     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9591     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9592     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9593     adjustedClock = FALSE;
9594     gameInfo.result = GameUnfinished;
9595     if (gameInfo.resultDetails != NULL) {
9596         free(gameInfo.resultDetails);
9597         gameInfo.resultDetails = NULL;
9598     }
9599     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9600                               moveList[forwardMostMove - 1]);
9601     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9602       case MT_NONE:
9603       case MT_STALEMATE:
9604       default:
9605         break;
9606       case MT_CHECK:
9607         if(gameInfo.variant != VariantShogi)
9608             strcat(parseList[forwardMostMove - 1], "+");
9609         break;
9610       case MT_CHECKMATE:
9611       case MT_STAINMATE:
9612         strcat(parseList[forwardMostMove - 1], "#");
9613         break;
9614     }
9615
9616 }
9617
9618 /* Updates currentMove if not pausing */
9619 void
9620 ShowMove (int fromX, int fromY, int toX, int toY)
9621 {
9622     int instant = (gameMode == PlayFromGameFile) ?
9623         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9624     if(appData.noGUI) return;
9625     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9626         if (!instant) {
9627             if (forwardMostMove == currentMove + 1) {
9628                 AnimateMove(boards[forwardMostMove - 1],
9629                             fromX, fromY, toX, toY);
9630             }
9631             if (appData.highlightLastMove) {
9632                 SetHighlights(fromX, fromY, toX, toY);
9633             }
9634         }
9635         currentMove = forwardMostMove;
9636     }
9637
9638     if (instant) return;
9639
9640     DisplayMove(currentMove - 1);
9641     DrawPosition(FALSE, boards[currentMove]);
9642     DisplayBothClocks();
9643     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9644 }
9645
9646 void
9647 SendEgtPath (ChessProgramState *cps)
9648 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9649         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9650
9651         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9652
9653         while(*p) {
9654             char c, *q = name+1, *r, *s;
9655
9656             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9657             while(*p && *p != ',') *q++ = *p++;
9658             *q++ = ':'; *q = 0;
9659             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9660                 strcmp(name, ",nalimov:") == 0 ) {
9661                 // take nalimov path from the menu-changeable option first, if it is defined
9662               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9663                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9664             } else
9665             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9666                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9667                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9668                 s = r = StrStr(s, ":") + 1; // beginning of path info
9669                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9670                 c = *r; *r = 0;             // temporarily null-terminate path info
9671                     *--q = 0;               // strip of trailig ':' from name
9672                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9673                 *r = c;
9674                 SendToProgram(buf,cps);     // send egtbpath command for this format
9675             }
9676             if(*p == ',') p++; // read away comma to position for next format name
9677         }
9678 }
9679
9680 void
9681 InitChessProgram (ChessProgramState *cps, int setup)
9682 /* setup needed to setup FRC opening position */
9683 {
9684     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9685     if (appData.noChessProgram) return;
9686     hintRequested = FALSE;
9687     bookRequested = FALSE;
9688
9689     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9690     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9691     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9692     if(cps->memSize) { /* [HGM] memory */
9693       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9694         SendToProgram(buf, cps);
9695     }
9696     SendEgtPath(cps); /* [HGM] EGT */
9697     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9698       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9699         SendToProgram(buf, cps);
9700     }
9701
9702     SendToProgram(cps->initString, cps);
9703     if (gameInfo.variant != VariantNormal &&
9704         gameInfo.variant != VariantLoadable
9705         /* [HGM] also send variant if board size non-standard */
9706         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9707                                             ) {
9708       char *v = VariantName(gameInfo.variant);
9709       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9710         /* [HGM] in protocol 1 we have to assume all variants valid */
9711         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9712         DisplayFatalError(buf, 0, 1);
9713         return;
9714       }
9715
9716       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9717       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9718       if( gameInfo.variant == VariantXiangqi )
9719            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9720       if( gameInfo.variant == VariantShogi )
9721            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9722       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9723            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9724       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9725           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9726            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9727       if( gameInfo.variant == VariantCourier )
9728            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9729       if( gameInfo.variant == VariantSuper )
9730            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9731       if( gameInfo.variant == VariantGreat )
9732            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9733       if( gameInfo.variant == VariantSChess )
9734            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9735       if( gameInfo.variant == VariantGrand )
9736            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9737
9738       if(overruled) {
9739         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9740                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9741            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9742            if(StrStr(cps->variants, b) == NULL) {
9743                // specific sized variant not known, check if general sizing allowed
9744                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9745                    if(StrStr(cps->variants, "boardsize") == NULL) {
9746                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9747                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9748                        DisplayFatalError(buf, 0, 1);
9749                        return;
9750                    }
9751                    /* [HGM] here we really should compare with the maximum supported board size */
9752                }
9753            }
9754       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9755       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9756       SendToProgram(buf, cps);
9757     }
9758     currentlyInitializedVariant = gameInfo.variant;
9759
9760     /* [HGM] send opening position in FRC to first engine */
9761     if(setup) {
9762           SendToProgram("force\n", cps);
9763           SendBoard(cps, 0);
9764           /* engine is now in force mode! Set flag to wake it up after first move. */
9765           setboardSpoiledMachineBlack = 1;
9766     }
9767
9768     if (cps->sendICS) {
9769       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9770       SendToProgram(buf, cps);
9771     }
9772     cps->maybeThinking = FALSE;
9773     cps->offeredDraw = 0;
9774     if (!appData.icsActive) {
9775         SendTimeControl(cps, movesPerSession, timeControl,
9776                         timeIncrement, appData.searchDepth,
9777                         searchTime);
9778     }
9779     if (appData.showThinking
9780         // [HGM] thinking: four options require thinking output to be sent
9781         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9782                                 ) {
9783         SendToProgram("post\n", cps);
9784     }
9785     SendToProgram("hard\n", cps);
9786     if (!appData.ponderNextMove) {
9787         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9788            it without being sure what state we are in first.  "hard"
9789            is not a toggle, so that one is OK.
9790          */
9791         SendToProgram("easy\n", cps);
9792     }
9793     if (cps->usePing) {
9794       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9795       SendToProgram(buf, cps);
9796     }
9797     cps->initDone = TRUE;
9798     ClearEngineOutputPane(cps == &second);
9799 }
9800
9801
9802 void
9803 StartChessProgram (ChessProgramState *cps)
9804 {
9805     char buf[MSG_SIZ];
9806     int err;
9807
9808     if (appData.noChessProgram) return;
9809     cps->initDone = FALSE;
9810
9811     if (strcmp(cps->host, "localhost") == 0) {
9812         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9813     } else if (*appData.remoteShell == NULLCHAR) {
9814         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9815     } else {
9816         if (*appData.remoteUser == NULLCHAR) {
9817           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9818                     cps->program);
9819         } else {
9820           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9821                     cps->host, appData.remoteUser, cps->program);
9822         }
9823         err = StartChildProcess(buf, "", &cps->pr);
9824     }
9825
9826     if (err != 0) {
9827       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9828         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9829         if(cps != &first) return;
9830         appData.noChessProgram = TRUE;
9831         ThawUI();
9832         SetNCPMode();
9833 //      DisplayFatalError(buf, err, 1);
9834 //      cps->pr = NoProc;
9835 //      cps->isr = NULL;
9836         return;
9837     }
9838
9839     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9840     if (cps->protocolVersion > 1) {
9841       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9842       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9843       cps->comboCnt = 0;  //                and values of combo boxes
9844       SendToProgram(buf, cps);
9845     } else {
9846       SendToProgram("xboard\n", cps);
9847     }
9848 }
9849
9850 void
9851 TwoMachinesEventIfReady P((void))
9852 {
9853   static int curMess = 0;
9854   if (first.lastPing != first.lastPong) {
9855     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9856     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9857     return;
9858   }
9859   if (second.lastPing != second.lastPong) {
9860     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9861     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9862     return;
9863   }
9864   DisplayMessage("", ""); curMess = 0;
9865   ThawUI();
9866   TwoMachinesEvent();
9867 }
9868
9869 char *
9870 MakeName (char *template)
9871 {
9872     time_t clock;
9873     struct tm *tm;
9874     static char buf[MSG_SIZ];
9875     char *p = buf;
9876     int i;
9877
9878     clock = time((time_t *)NULL);
9879     tm = localtime(&clock);
9880
9881     while(*p++ = *template++) if(p[-1] == '%') {
9882         switch(*template++) {
9883           case 0:   *p = 0; return buf;
9884           case 'Y': i = tm->tm_year+1900; break;
9885           case 'y': i = tm->tm_year-100; break;
9886           case 'M': i = tm->tm_mon+1; break;
9887           case 'd': i = tm->tm_mday; break;
9888           case 'h': i = tm->tm_hour; break;
9889           case 'm': i = tm->tm_min; break;
9890           case 's': i = tm->tm_sec; break;
9891           default:  i = 0;
9892         }
9893         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9894     }
9895     return buf;
9896 }
9897
9898 int
9899 CountPlayers (char *p)
9900 {
9901     int n = 0;
9902     while(p = strchr(p, '\n')) p++, n++; // count participants
9903     return n;
9904 }
9905
9906 FILE *
9907 WriteTourneyFile (char *results, FILE *f)
9908 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9909     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9910     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9911         // create a file with tournament description
9912         fprintf(f, "-participants {%s}\n", appData.participants);
9913         fprintf(f, "-seedBase %d\n", appData.seedBase);
9914         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9915         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9916         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9917         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9918         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9919         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9920         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9921         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9922         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9923         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9924         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9925         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9926         if(searchTime > 0)
9927                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9928         else {
9929                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9930                 fprintf(f, "-tc %s\n", appData.timeControl);
9931                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9932         }
9933         fprintf(f, "-results \"%s\"\n", results);
9934     }
9935     return f;
9936 }
9937
9938 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9939
9940 void
9941 Substitute (char *participants, int expunge)
9942 {
9943     int i, changed, changes=0, nPlayers=0;
9944     char *p, *q, *r, buf[MSG_SIZ];
9945     if(participants == NULL) return;
9946     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9947     r = p = participants; q = appData.participants;
9948     while(*p && *p == *q) {
9949         if(*p == '\n') r = p+1, nPlayers++;
9950         p++; q++;
9951     }
9952     if(*p) { // difference
9953         while(*p && *p++ != '\n');
9954         while(*q && *q++ != '\n');
9955       changed = nPlayers;
9956         changes = 1 + (strcmp(p, q) != 0);
9957     }
9958     if(changes == 1) { // a single engine mnemonic was changed
9959         q = r; while(*q) nPlayers += (*q++ == '\n');
9960         p = buf; while(*r && (*p = *r++) != '\n') p++;
9961         *p = NULLCHAR;
9962         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9963         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9964         if(mnemonic[i]) { // The substitute is valid
9965             FILE *f;
9966             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9967                 flock(fileno(f), LOCK_EX);
9968                 ParseArgsFromFile(f);
9969                 fseek(f, 0, SEEK_SET);
9970                 FREE(appData.participants); appData.participants = participants;
9971                 if(expunge) { // erase results of replaced engine
9972                     int len = strlen(appData.results), w, b, dummy;
9973                     for(i=0; i<len; i++) {
9974                         Pairing(i, nPlayers, &w, &b, &dummy);
9975                         if((w == changed || b == changed) && appData.results[i] == '*') {
9976                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9977                             fclose(f);
9978                             return;
9979                         }
9980                     }
9981                     for(i=0; i<len; i++) {
9982                         Pairing(i, nPlayers, &w, &b, &dummy);
9983                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9984                     }
9985                 }
9986                 WriteTourneyFile(appData.results, f);
9987                 fclose(f); // release lock
9988                 return;
9989             }
9990         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9991     }
9992     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9993     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9994     free(participants);
9995     return;
9996 }
9997
9998 int
9999 CreateTourney (char *name)
10000 {
10001         FILE *f;
10002         if(matchMode && strcmp(name, appData.tourneyFile)) {
10003              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
10004         }
10005         if(name[0] == NULLCHAR) {
10006             if(appData.participants[0])
10007                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
10008             return 0;
10009         }
10010         f = fopen(name, "r");
10011         if(f) { // file exists
10012             ASSIGN(appData.tourneyFile, name);
10013             ParseArgsFromFile(f); // parse it
10014         } else {
10015             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
10016             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
10017                 DisplayError(_("Not enough participants"), 0);
10018                 return 0;
10019             }
10020             ASSIGN(appData.tourneyFile, name);
10021             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
10022             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
10023         }
10024         fclose(f);
10025         appData.noChessProgram = FALSE;
10026         appData.clockMode = TRUE;
10027         SetGNUMode();
10028         return 1;
10029 }
10030
10031 int
10032 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
10033 {
10034     char buf[MSG_SIZ], *p, *q;
10035     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
10036     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
10037     skip = !all && group[0]; // if group requested, we start in skip mode
10038     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
10039         p = names; q = buf; header = 0;
10040         while(*p && *p != '\n') *q++ = *p++;
10041         *q = 0;
10042         if(*p == '\n') p++;
10043         if(buf[0] == '#') {
10044             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
10045             depth++; // we must be entering a new group
10046             if(all) continue; // suppress printing group headers when complete list requested
10047             header = 1;
10048             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
10049         }
10050         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
10051         if(engineList[i]) free(engineList[i]);
10052         engineList[i] = strdup(buf);
10053         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
10054         if(engineMnemonic[i]) free(engineMnemonic[i]);
10055         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
10056             strcat(buf, " (");
10057             sscanf(q + 8, "%s", buf + strlen(buf));
10058             strcat(buf, ")");
10059         }
10060         engineMnemonic[i] = strdup(buf);
10061         i++;
10062     }
10063     engineList[i] = engineMnemonic[i] = NULL;
10064     return i;
10065 }
10066
10067 // following implemented as macro to avoid type limitations
10068 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
10069
10070 void
10071 SwapEngines (int n)
10072 {   // swap settings for first engine and other engine (so far only some selected options)
10073     int h;
10074     char *p;
10075     if(n == 0) return;
10076     SWAP(directory, p)
10077     SWAP(chessProgram, p)
10078     SWAP(isUCI, h)
10079     SWAP(hasOwnBookUCI, h)
10080     SWAP(protocolVersion, h)
10081     SWAP(reuse, h)
10082     SWAP(scoreIsAbsolute, h)
10083     SWAP(timeOdds, h)
10084     SWAP(logo, p)
10085     SWAP(pgnName, p)
10086     SWAP(pvSAN, h)
10087     SWAP(engOptions, p)
10088     SWAP(engInitString, p)
10089     SWAP(computerString, p)
10090     SWAP(features, p)
10091     SWAP(fenOverride, p)
10092     SWAP(NPS, h)
10093     SWAP(accumulateTC, h)
10094     SWAP(host, p)
10095 }
10096
10097 int
10098 SetPlayer (int player, char *p)
10099 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
10100     int i;
10101     char buf[MSG_SIZ], *engineName;
10102     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
10103     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
10104     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
10105     if(mnemonic[i]) {
10106         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
10107         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
10108         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
10109         ParseArgsFromString(buf);
10110     }
10111     free(engineName);
10112     return i;
10113 }
10114
10115 char *recentEngines;
10116
10117 void
10118 RecentEngineEvent (int nr)
10119 {
10120     int n;
10121 //    SwapEngines(1); // bump first to second
10122 //    ReplaceEngine(&second, 1); // and load it there
10123     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10124     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
10125     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
10126         ReplaceEngine(&first, 0);
10127         FloatToFront(&appData.recentEngineList, command[n]);
10128     }
10129 }
10130
10131 int
10132 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
10133 {   // determine players from game number
10134     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
10135
10136     if(appData.tourneyType == 0) {
10137         roundsPerCycle = (nPlayers - 1) | 1;
10138         pairingsPerRound = nPlayers / 2;
10139     } else if(appData.tourneyType > 0) {
10140         roundsPerCycle = nPlayers - appData.tourneyType;
10141         pairingsPerRound = appData.tourneyType;
10142     }
10143     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
10144     gamesPerCycle = gamesPerRound * roundsPerCycle;
10145     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
10146     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
10147     curRound = nr / gamesPerRound; nr %= gamesPerRound;
10148     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
10149     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
10150     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
10151
10152     if(appData.cycleSync) *syncInterval = gamesPerCycle;
10153     if(appData.roundSync) *syncInterval = gamesPerRound;
10154
10155     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
10156
10157     if(appData.tourneyType == 0) {
10158         if(curPairing == (nPlayers-1)/2 ) {
10159             *whitePlayer = curRound;
10160             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
10161         } else {
10162             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
10163             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
10164             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
10165             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10166         }
10167     } else if(appData.tourneyType > 1) {
10168         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
10169         *whitePlayer = curRound + appData.tourneyType;
10170     } else if(appData.tourneyType > 0) {
10171         *whitePlayer = curPairing;
10172         *blackPlayer = curRound + appData.tourneyType;
10173     }
10174
10175     // take care of white/black alternation per round. 
10176     // For cycles and games this is already taken care of by default, derived from matchGame!
10177     return curRound & 1;
10178 }
10179
10180 int
10181 NextTourneyGame (int nr, int *swapColors)
10182 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10183     char *p, *q;
10184     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10185     FILE *tf;
10186     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10187     tf = fopen(appData.tourneyFile, "r");
10188     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10189     ParseArgsFromFile(tf); fclose(tf);
10190     InitTimeControls(); // TC might be altered from tourney file
10191
10192     nPlayers = CountPlayers(appData.participants); // count participants
10193     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10194     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10195
10196     if(syncInterval) {
10197         p = q = appData.results;
10198         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10199         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10200             DisplayMessage(_("Waiting for other game(s)"),"");
10201             waitingForGame = TRUE;
10202             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10203             return 0;
10204         }
10205         waitingForGame = FALSE;
10206     }
10207
10208     if(appData.tourneyType < 0) {
10209         if(nr>=0 && !pairingReceived) {
10210             char buf[1<<16];
10211             if(pairing.pr == NoProc) {
10212                 if(!appData.pairingEngine[0]) {
10213                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10214                     return 0;
10215                 }
10216                 StartChessProgram(&pairing); // starts the pairing engine
10217             }
10218             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10219             SendToProgram(buf, &pairing);
10220             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10221             SendToProgram(buf, &pairing);
10222             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10223         }
10224         pairingReceived = 0;                              // ... so we continue here 
10225         *swapColors = 0;
10226         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10227         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10228         matchGame = 1; roundNr = nr / syncInterval + 1;
10229     }
10230
10231     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10232
10233     // redefine engines, engine dir, etc.
10234     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10235     if(first.pr == NoProc) {
10236       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10237       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10238     }
10239     if(second.pr == NoProc) {
10240       SwapEngines(1);
10241       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10242       SwapEngines(1);         // and make that valid for second engine by swapping
10243       InitEngine(&second, 1);
10244     }
10245     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10246     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10247     return 1;
10248 }
10249
10250 void
10251 NextMatchGame ()
10252 {   // performs game initialization that does not invoke engines, and then tries to start the game
10253     int res, firstWhite, swapColors = 0;
10254     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10255     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
10256         char buf[MSG_SIZ];
10257         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
10258         if(strcmp(buf, currentDebugFile)) { // name has changed
10259             FILE *f = fopen(buf, "w");
10260             if(f) { // if opening the new file failed, just keep using the old one
10261                 ASSIGN(currentDebugFile, buf);
10262                 fclose(debugFP);
10263                 debugFP = f;
10264             }
10265             if(appData.serverFileName) {
10266                 if(serverFP) fclose(serverFP);
10267                 serverFP = fopen(appData.serverFileName, "w");
10268                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
10269                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
10270             }
10271         }
10272     }
10273     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10274     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10275     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10276     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10277     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10278     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10279     Reset(FALSE, first.pr != NoProc);
10280     res = LoadGameOrPosition(matchGame); // setup game
10281     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10282     if(!res) return; // abort when bad game/pos file
10283     TwoMachinesEvent();
10284 }
10285
10286 void
10287 UserAdjudicationEvent (int result)
10288 {
10289     ChessMove gameResult = GameIsDrawn;
10290
10291     if( result > 0 ) {
10292         gameResult = WhiteWins;
10293     }
10294     else if( result < 0 ) {
10295         gameResult = BlackWins;
10296     }
10297
10298     if( gameMode == TwoMachinesPlay ) {
10299         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10300     }
10301 }
10302
10303
10304 // [HGM] save: calculate checksum of game to make games easily identifiable
10305 int
10306 StringCheckSum (char *s)
10307 {
10308         int i = 0;
10309         if(s==NULL) return 0;
10310         while(*s) i = i*259 + *s++;
10311         return i;
10312 }
10313
10314 int
10315 GameCheckSum ()
10316 {
10317         int i, sum=0;
10318         for(i=backwardMostMove; i<forwardMostMove; i++) {
10319                 sum += pvInfoList[i].depth;
10320                 sum += StringCheckSum(parseList[i]);
10321                 sum += StringCheckSum(commentList[i]);
10322                 sum *= 261;
10323         }
10324         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10325         return sum + StringCheckSum(commentList[i]);
10326 } // end of save patch
10327
10328 void
10329 GameEnds (ChessMove result, char *resultDetails, int whosays)
10330 {
10331     GameMode nextGameMode;
10332     int isIcsGame;
10333     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10334
10335     if(endingGame) return; /* [HGM] crash: forbid recursion */
10336     endingGame = 1;
10337     if(twoBoards) { // [HGM] dual: switch back to one board
10338         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10339         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10340     }
10341     if (appData.debugMode) {
10342       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10343               result, resultDetails ? resultDetails : "(null)", whosays);
10344     }
10345
10346     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10347
10348     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10349         /* If we are playing on ICS, the server decides when the
10350            game is over, but the engine can offer to draw, claim
10351            a draw, or resign.
10352          */
10353 #if ZIPPY
10354         if (appData.zippyPlay && first.initDone) {
10355             if (result == GameIsDrawn) {
10356                 /* In case draw still needs to be claimed */
10357                 SendToICS(ics_prefix);
10358                 SendToICS("draw\n");
10359             } else if (StrCaseStr(resultDetails, "resign")) {
10360                 SendToICS(ics_prefix);
10361                 SendToICS("resign\n");
10362             }
10363         }
10364 #endif
10365         endingGame = 0; /* [HGM] crash */
10366         return;
10367     }
10368
10369     /* If we're loading the game from a file, stop */
10370     if (whosays == GE_FILE) {
10371       (void) StopLoadGameTimer();
10372       gameFileFP = NULL;
10373     }
10374
10375     /* Cancel draw offers */
10376     first.offeredDraw = second.offeredDraw = 0;
10377
10378     /* If this is an ICS game, only ICS can really say it's done;
10379        if not, anyone can. */
10380     isIcsGame = (gameMode == IcsPlayingWhite ||
10381                  gameMode == IcsPlayingBlack ||
10382                  gameMode == IcsObserving    ||
10383                  gameMode == IcsExamining);
10384
10385     if (!isIcsGame || whosays == GE_ICS) {
10386         /* OK -- not an ICS game, or ICS said it was done */
10387         StopClocks();
10388         if (!isIcsGame && !appData.noChessProgram)
10389           SetUserThinkingEnables();
10390
10391         /* [HGM] if a machine claims the game end we verify this claim */
10392         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10393             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10394                 char claimer;
10395                 ChessMove trueResult = (ChessMove) -1;
10396
10397                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10398                                             first.twoMachinesColor[0] :
10399                                             second.twoMachinesColor[0] ;
10400
10401                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10402                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10403                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10404                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10405                 } else
10406                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10407                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10408                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10409                 } else
10410                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10411                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10412                 }
10413
10414                 // now verify win claims, but not in drop games, as we don't understand those yet
10415                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10416                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10417                     (result == WhiteWins && claimer == 'w' ||
10418                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10419                       if (appData.debugMode) {
10420                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10421                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10422                       }
10423                       if(result != trueResult) {
10424                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10425                               result = claimer == 'w' ? BlackWins : WhiteWins;
10426                               resultDetails = buf;
10427                       }
10428                 } else
10429                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10430                     && (forwardMostMove <= backwardMostMove ||
10431                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10432                         (claimer=='b')==(forwardMostMove&1))
10433                                                                                   ) {
10434                       /* [HGM] verify: draws that were not flagged are false claims */
10435                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10436                       result = claimer == 'w' ? BlackWins : WhiteWins;
10437                       resultDetails = buf;
10438                 }
10439                 /* (Claiming a loss is accepted no questions asked!) */
10440             }
10441             /* [HGM] bare: don't allow bare King to win */
10442             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10443                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10444                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10445                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10446                && result != GameIsDrawn)
10447             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10448                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10449                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10450                         if(p >= 0 && p <= (int)WhiteKing) k++;
10451                 }
10452                 if (appData.debugMode) {
10453                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10454                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10455                 }
10456                 if(k <= 1) {
10457                         result = GameIsDrawn;
10458                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10459                         resultDetails = buf;
10460                 }
10461             }
10462         }
10463
10464
10465         if(serverMoves != NULL && !loadFlag) { char c = '=';
10466             if(result==WhiteWins) c = '+';
10467             if(result==BlackWins) c = '-';
10468             if(resultDetails != NULL)
10469                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10470         }
10471         if (resultDetails != NULL) {
10472             gameInfo.result = result;
10473             gameInfo.resultDetails = StrSave(resultDetails);
10474
10475             /* display last move only if game was not loaded from file */
10476             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10477                 DisplayMove(currentMove - 1);
10478
10479             if (forwardMostMove != 0) {
10480                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10481                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10482                                                                 ) {
10483                     if (*appData.saveGameFile != NULLCHAR) {
10484                         SaveGameToFile(appData.saveGameFile, TRUE);
10485                     } else if (appData.autoSaveGames) {
10486                         AutoSaveGame();
10487                     }
10488                     if (*appData.savePositionFile != NULLCHAR) {
10489                         SavePositionToFile(appData.savePositionFile);
10490                     }
10491                 }
10492             }
10493
10494             /* Tell program how game ended in case it is learning */
10495             /* [HGM] Moved this to after saving the PGN, just in case */
10496             /* engine died and we got here through time loss. In that */
10497             /* case we will get a fatal error writing the pipe, which */
10498             /* would otherwise lose us the PGN.                       */
10499             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10500             /* output during GameEnds should never be fatal anymore   */
10501             if (gameMode == MachinePlaysWhite ||
10502                 gameMode == MachinePlaysBlack ||
10503                 gameMode == TwoMachinesPlay ||
10504                 gameMode == IcsPlayingWhite ||
10505                 gameMode == IcsPlayingBlack ||
10506                 gameMode == BeginningOfGame) {
10507                 char buf[MSG_SIZ];
10508                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10509                         resultDetails);
10510                 if (first.pr != NoProc) {
10511                     SendToProgram(buf, &first);
10512                 }
10513                 if (second.pr != NoProc &&
10514                     gameMode == TwoMachinesPlay) {
10515                     SendToProgram(buf, &second);
10516                 }
10517             }
10518         }
10519
10520         if (appData.icsActive) {
10521             if (appData.quietPlay &&
10522                 (gameMode == IcsPlayingWhite ||
10523                  gameMode == IcsPlayingBlack)) {
10524                 SendToICS(ics_prefix);
10525                 SendToICS("set shout 1\n");
10526             }
10527             nextGameMode = IcsIdle;
10528             ics_user_moved = FALSE;
10529             /* clean up premove.  It's ugly when the game has ended and the
10530              * premove highlights are still on the board.
10531              */
10532             if (gotPremove) {
10533               gotPremove = FALSE;
10534               ClearPremoveHighlights();
10535               DrawPosition(FALSE, boards[currentMove]);
10536             }
10537             if (whosays == GE_ICS) {
10538                 switch (result) {
10539                 case WhiteWins:
10540                     if (gameMode == IcsPlayingWhite)
10541                         PlayIcsWinSound();
10542                     else if(gameMode == IcsPlayingBlack)
10543                         PlayIcsLossSound();
10544                     break;
10545                 case BlackWins:
10546                     if (gameMode == IcsPlayingBlack)
10547                         PlayIcsWinSound();
10548                     else if(gameMode == IcsPlayingWhite)
10549                         PlayIcsLossSound();
10550                     break;
10551                 case GameIsDrawn:
10552                     PlayIcsDrawSound();
10553                     break;
10554                 default:
10555                     PlayIcsUnfinishedSound();
10556                 }
10557             }
10558         } else if (gameMode == EditGame ||
10559                    gameMode == PlayFromGameFile ||
10560                    gameMode == AnalyzeMode ||
10561                    gameMode == AnalyzeFile) {
10562             nextGameMode = gameMode;
10563         } else {
10564             nextGameMode = EndOfGame;
10565         }
10566         pausing = FALSE;
10567         ModeHighlight();
10568     } else {
10569         nextGameMode = gameMode;
10570     }
10571
10572     if (appData.noChessProgram) {
10573         gameMode = nextGameMode;
10574         ModeHighlight();
10575         endingGame = 0; /* [HGM] crash */
10576         return;
10577     }
10578
10579     if (first.reuse) {
10580         /* Put first chess program into idle state */
10581         if (first.pr != NoProc &&
10582             (gameMode == MachinePlaysWhite ||
10583              gameMode == MachinePlaysBlack ||
10584              gameMode == TwoMachinesPlay ||
10585              gameMode == IcsPlayingWhite ||
10586              gameMode == IcsPlayingBlack ||
10587              gameMode == BeginningOfGame)) {
10588             SendToProgram("force\n", &first);
10589             if (first.usePing) {
10590               char buf[MSG_SIZ];
10591               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10592               SendToProgram(buf, &first);
10593             }
10594         }
10595     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10596         /* Kill off first chess program */
10597         if (first.isr != NULL)
10598           RemoveInputSource(first.isr);
10599         first.isr = NULL;
10600
10601         if (first.pr != NoProc) {
10602             ExitAnalyzeMode();
10603             DoSleep( appData.delayBeforeQuit );
10604             SendToProgram("quit\n", &first);
10605             DoSleep( appData.delayAfterQuit );
10606             DestroyChildProcess(first.pr, first.useSigterm);
10607         }
10608         first.pr = NoProc;
10609     }
10610     if (second.reuse) {
10611         /* Put second chess program into idle state */
10612         if (second.pr != NoProc &&
10613             gameMode == TwoMachinesPlay) {
10614             SendToProgram("force\n", &second);
10615             if (second.usePing) {
10616               char buf[MSG_SIZ];
10617               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10618               SendToProgram(buf, &second);
10619             }
10620         }
10621     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10622         /* Kill off second chess program */
10623         if (second.isr != NULL)
10624           RemoveInputSource(second.isr);
10625         second.isr = NULL;
10626
10627         if (second.pr != NoProc) {
10628             DoSleep( appData.delayBeforeQuit );
10629             SendToProgram("quit\n", &second);
10630             DoSleep( appData.delayAfterQuit );
10631             DestroyChildProcess(second.pr, second.useSigterm);
10632         }
10633         second.pr = NoProc;
10634     }
10635
10636     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10637         char resChar = '=';
10638         switch (result) {
10639         case WhiteWins:
10640           resChar = '+';
10641           if (first.twoMachinesColor[0] == 'w') {
10642             first.matchWins++;
10643           } else {
10644             second.matchWins++;
10645           }
10646           break;
10647         case BlackWins:
10648           resChar = '-';
10649           if (first.twoMachinesColor[0] == 'b') {
10650             first.matchWins++;
10651           } else {
10652             second.matchWins++;
10653           }
10654           break;
10655         case GameUnfinished:
10656           resChar = ' ';
10657         default:
10658           break;
10659         }
10660
10661         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10662         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10663             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10664             ReserveGame(nextGame, resChar); // sets nextGame
10665             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10666             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10667         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10668
10669         if (nextGame <= appData.matchGames && !abortMatch) {
10670             gameMode = nextGameMode;
10671             matchGame = nextGame; // this will be overruled in tourney mode!
10672             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10673             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10674             endingGame = 0; /* [HGM] crash */
10675             return;
10676         } else {
10677             gameMode = nextGameMode;
10678             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10679                      first.tidy, second.tidy,
10680                      first.matchWins, second.matchWins,
10681                      appData.matchGames - (first.matchWins + second.matchWins));
10682             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10683             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10684             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10685             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10686                 first.twoMachinesColor = "black\n";
10687                 second.twoMachinesColor = "white\n";
10688             } else {
10689                 first.twoMachinesColor = "white\n";
10690                 second.twoMachinesColor = "black\n";
10691             }
10692         }
10693     }
10694     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10695         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10696       ExitAnalyzeMode();
10697     gameMode = nextGameMode;
10698     ModeHighlight();
10699     endingGame = 0;  /* [HGM] crash */
10700     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10701         if(matchMode == TRUE) { // match through command line: exit with or without popup
10702             if(ranking) {
10703                 ToNrEvent(forwardMostMove);
10704                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10705                 else ExitEvent(0);
10706             } else DisplayFatalError(buf, 0, 0);
10707         } else { // match through menu; just stop, with or without popup
10708             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10709             ModeHighlight();
10710             if(ranking){
10711                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10712             } else DisplayNote(buf);
10713       }
10714       if(ranking) free(ranking);
10715     }
10716 }
10717
10718 /* Assumes program was just initialized (initString sent).
10719    Leaves program in force mode. */
10720 void
10721 FeedMovesToProgram (ChessProgramState *cps, int upto)
10722 {
10723     int i;
10724
10725     if (appData.debugMode)
10726       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10727               startedFromSetupPosition ? "position and " : "",
10728               backwardMostMove, upto, cps->which);
10729     if(currentlyInitializedVariant != gameInfo.variant) {
10730       char buf[MSG_SIZ];
10731         // [HGM] variantswitch: make engine aware of new variant
10732         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10733                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10734         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10735         SendToProgram(buf, cps);
10736         currentlyInitializedVariant = gameInfo.variant;
10737     }
10738     SendToProgram("force\n", cps);
10739     if (startedFromSetupPosition) {
10740         SendBoard(cps, backwardMostMove);
10741     if (appData.debugMode) {
10742         fprintf(debugFP, "feedMoves\n");
10743     }
10744     }
10745     for (i = backwardMostMove; i < upto; i++) {
10746         SendMoveToProgram(i, cps);
10747     }
10748 }
10749
10750
10751 int
10752 ResurrectChessProgram ()
10753 {
10754      /* The chess program may have exited.
10755         If so, restart it and feed it all the moves made so far. */
10756     static int doInit = 0;
10757
10758     if (appData.noChessProgram) return 1;
10759
10760     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10761         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10762         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10763         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10764     } else {
10765         if (first.pr != NoProc) return 1;
10766         StartChessProgram(&first);
10767     }
10768     InitChessProgram(&first, FALSE);
10769     FeedMovesToProgram(&first, currentMove);
10770
10771     if (!first.sendTime) {
10772         /* can't tell gnuchess what its clock should read,
10773            so we bow to its notion. */
10774         ResetClocks();
10775         timeRemaining[0][currentMove] = whiteTimeRemaining;
10776         timeRemaining[1][currentMove] = blackTimeRemaining;
10777     }
10778
10779     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10780                 appData.icsEngineAnalyze) && first.analysisSupport) {
10781       SendToProgram("analyze\n", &first);
10782       first.analyzing = TRUE;
10783     }
10784     return 1;
10785 }
10786
10787 /*
10788  * Button procedures
10789  */
10790 void
10791 Reset (int redraw, int init)
10792 {
10793     int i;
10794
10795     if (appData.debugMode) {
10796         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10797                 redraw, init, gameMode);
10798     }
10799     CleanupTail(); // [HGM] vari: delete any stored variations
10800     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10801     pausing = pauseExamInvalid = FALSE;
10802     startedFromSetupPosition = blackPlaysFirst = FALSE;
10803     firstMove = TRUE;
10804     whiteFlag = blackFlag = FALSE;
10805     userOfferedDraw = FALSE;
10806     hintRequested = bookRequested = FALSE;
10807     first.maybeThinking = FALSE;
10808     second.maybeThinking = FALSE;
10809     first.bookSuspend = FALSE; // [HGM] book
10810     second.bookSuspend = FALSE;
10811     thinkOutput[0] = NULLCHAR;
10812     lastHint[0] = NULLCHAR;
10813     ClearGameInfo(&gameInfo);
10814     gameInfo.variant = StringToVariant(appData.variant);
10815     ics_user_moved = ics_clock_paused = FALSE;
10816     ics_getting_history = H_FALSE;
10817     ics_gamenum = -1;
10818     white_holding[0] = black_holding[0] = NULLCHAR;
10819     ClearProgramStats();
10820     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10821
10822     ResetFrontEnd();
10823     ClearHighlights();
10824     flipView = appData.flipView;
10825     ClearPremoveHighlights();
10826     gotPremove = FALSE;
10827     alarmSounded = FALSE;
10828
10829     GameEnds(EndOfFile, NULL, GE_PLAYER);
10830     if(appData.serverMovesName != NULL) {
10831         /* [HGM] prepare to make moves file for broadcasting */
10832         clock_t t = clock();
10833         if(serverMoves != NULL) fclose(serverMoves);
10834         serverMoves = fopen(appData.serverMovesName, "r");
10835         if(serverMoves != NULL) {
10836             fclose(serverMoves);
10837             /* delay 15 sec before overwriting, so all clients can see end */
10838             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10839         }
10840         serverMoves = fopen(appData.serverMovesName, "w");
10841     }
10842
10843     ExitAnalyzeMode();
10844     gameMode = BeginningOfGame;
10845     ModeHighlight();
10846     if(appData.icsActive) gameInfo.variant = VariantNormal;
10847     currentMove = forwardMostMove = backwardMostMove = 0;
10848     MarkTargetSquares(1);
10849     InitPosition(redraw);
10850     for (i = 0; i < MAX_MOVES; i++) {
10851         if (commentList[i] != NULL) {
10852             free(commentList[i]);
10853             commentList[i] = NULL;
10854         }
10855     }
10856     ResetClocks();
10857     timeRemaining[0][0] = whiteTimeRemaining;
10858     timeRemaining[1][0] = blackTimeRemaining;
10859
10860     if (first.pr == NoProc) {
10861         StartChessProgram(&first);
10862     }
10863     if (init) {
10864             InitChessProgram(&first, startedFromSetupPosition);
10865     }
10866     DisplayTitle("");
10867     DisplayMessage("", "");
10868     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10869     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10870     ClearMap();        // [HGM] exclude: invalidate map
10871 }
10872
10873 void
10874 AutoPlayGameLoop ()
10875 {
10876     for (;;) {
10877         if (!AutoPlayOneMove())
10878           return;
10879         if (matchMode || appData.timeDelay == 0)
10880           continue;
10881         if (appData.timeDelay < 0)
10882           return;
10883         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10884         break;
10885     }
10886 }
10887
10888
10889 int
10890 AutoPlayOneMove ()
10891 {
10892     int fromX, fromY, toX, toY;
10893
10894     if (appData.debugMode) {
10895       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10896     }
10897
10898     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10899       return FALSE;
10900
10901     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10902       pvInfoList[currentMove].depth = programStats.depth;
10903       pvInfoList[currentMove].score = programStats.score;
10904       pvInfoList[currentMove].time  = 0;
10905       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10906     }
10907
10908     if (currentMove >= forwardMostMove) {
10909       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10910 //      gameMode = EndOfGame;
10911 //      ModeHighlight();
10912
10913       /* [AS] Clear current move marker at the end of a game */
10914       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10915
10916       return FALSE;
10917     }
10918
10919     toX = moveList[currentMove][2] - AAA;
10920     toY = moveList[currentMove][3] - ONE;
10921
10922     if (moveList[currentMove][1] == '@') {
10923         if (appData.highlightLastMove) {
10924             SetHighlights(-1, -1, toX, toY);
10925         }
10926     } else {
10927         fromX = moveList[currentMove][0] - AAA;
10928         fromY = moveList[currentMove][1] - ONE;
10929
10930         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10931
10932         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10933
10934         if (appData.highlightLastMove) {
10935             SetHighlights(fromX, fromY, toX, toY);
10936         }
10937     }
10938     DisplayMove(currentMove);
10939     SendMoveToProgram(currentMove++, &first);
10940     DisplayBothClocks();
10941     DrawPosition(FALSE, boards[currentMove]);
10942     // [HGM] PV info: always display, routine tests if empty
10943     DisplayComment(currentMove - 1, commentList[currentMove]);
10944     return TRUE;
10945 }
10946
10947
10948 int
10949 LoadGameOneMove (ChessMove readAhead)
10950 {
10951     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10952     char promoChar = NULLCHAR;
10953     ChessMove moveType;
10954     char move[MSG_SIZ];
10955     char *p, *q;
10956
10957     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10958         gameMode != AnalyzeMode && gameMode != Training) {
10959         gameFileFP = NULL;
10960         return FALSE;
10961     }
10962
10963     yyboardindex = forwardMostMove;
10964     if (readAhead != EndOfFile) {
10965       moveType = readAhead;
10966     } else {
10967       if (gameFileFP == NULL)
10968           return FALSE;
10969       moveType = (ChessMove) Myylex();
10970     }
10971
10972     done = FALSE;
10973     switch (moveType) {
10974       case Comment:
10975         if (appData.debugMode)
10976           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10977         p = yy_text;
10978
10979         /* append the comment but don't display it */
10980         AppendComment(currentMove, p, FALSE);
10981         return TRUE;
10982
10983       case WhiteCapturesEnPassant:
10984       case BlackCapturesEnPassant:
10985       case WhitePromotion:
10986       case BlackPromotion:
10987       case WhiteNonPromotion:
10988       case BlackNonPromotion:
10989       case NormalMove:
10990       case WhiteKingSideCastle:
10991       case WhiteQueenSideCastle:
10992       case BlackKingSideCastle:
10993       case BlackQueenSideCastle:
10994       case WhiteKingSideCastleWild:
10995       case WhiteQueenSideCastleWild:
10996       case BlackKingSideCastleWild:
10997       case BlackQueenSideCastleWild:
10998       /* PUSH Fabien */
10999       case WhiteHSideCastleFR:
11000       case WhiteASideCastleFR:
11001       case BlackHSideCastleFR:
11002       case BlackASideCastleFR:
11003       /* POP Fabien */
11004         if (appData.debugMode)
11005           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11006         fromX = currentMoveString[0] - AAA;
11007         fromY = currentMoveString[1] - ONE;
11008         toX = currentMoveString[2] - AAA;
11009         toY = currentMoveString[3] - ONE;
11010         promoChar = currentMoveString[4];
11011         break;
11012
11013       case WhiteDrop:
11014       case BlackDrop:
11015         if (appData.debugMode)
11016           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
11017         fromX = moveType == WhiteDrop ?
11018           (int) CharToPiece(ToUpper(currentMoveString[0])) :
11019         (int) CharToPiece(ToLower(currentMoveString[0]));
11020         fromY = DROP_RANK;
11021         toX = currentMoveString[2] - AAA;
11022         toY = currentMoveString[3] - ONE;
11023         break;
11024
11025       case WhiteWins:
11026       case BlackWins:
11027       case GameIsDrawn:
11028       case GameUnfinished:
11029         if (appData.debugMode)
11030           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
11031         p = strchr(yy_text, '{');
11032         if (p == NULL) p = strchr(yy_text, '(');
11033         if (p == NULL) {
11034             p = yy_text;
11035             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
11036         } else {
11037             q = strchr(p, *p == '{' ? '}' : ')');
11038             if (q != NULL) *q = NULLCHAR;
11039             p++;
11040         }
11041         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
11042         GameEnds(moveType, p, GE_FILE);
11043         done = TRUE;
11044         if (cmailMsgLoaded) {
11045             ClearHighlights();
11046             flipView = WhiteOnMove(currentMove);
11047             if (moveType == GameUnfinished) flipView = !flipView;
11048             if (appData.debugMode)
11049               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
11050         }
11051         break;
11052
11053       case EndOfFile:
11054         if (appData.debugMode)
11055           fprintf(debugFP, "Parser hit end of file\n");
11056         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11057           case MT_NONE:
11058           case MT_CHECK:
11059             break;
11060           case MT_CHECKMATE:
11061           case MT_STAINMATE:
11062             if (WhiteOnMove(currentMove)) {
11063                 GameEnds(BlackWins, "Black mates", GE_FILE);
11064             } else {
11065                 GameEnds(WhiteWins, "White mates", GE_FILE);
11066             }
11067             break;
11068           case MT_STALEMATE:
11069             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11070             break;
11071         }
11072         done = TRUE;
11073         break;
11074
11075       case MoveNumberOne:
11076         if (lastLoadGameStart == GNUChessGame) {
11077             /* GNUChessGames have numbers, but they aren't move numbers */
11078             if (appData.debugMode)
11079               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11080                       yy_text, (int) moveType);
11081             return LoadGameOneMove(EndOfFile); /* tail recursion */
11082         }
11083         /* else fall thru */
11084
11085       case XBoardGame:
11086       case GNUChessGame:
11087       case PGNTag:
11088         /* Reached start of next game in file */
11089         if (appData.debugMode)
11090           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
11091         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11092           case MT_NONE:
11093           case MT_CHECK:
11094             break;
11095           case MT_CHECKMATE:
11096           case MT_STAINMATE:
11097             if (WhiteOnMove(currentMove)) {
11098                 GameEnds(BlackWins, "Black mates", GE_FILE);
11099             } else {
11100                 GameEnds(WhiteWins, "White mates", GE_FILE);
11101             }
11102             break;
11103           case MT_STALEMATE:
11104             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
11105             break;
11106         }
11107         done = TRUE;
11108         break;
11109
11110       case PositionDiagram:     /* should not happen; ignore */
11111       case ElapsedTime:         /* ignore */
11112       case NAG:                 /* ignore */
11113         if (appData.debugMode)
11114           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
11115                   yy_text, (int) moveType);
11116         return LoadGameOneMove(EndOfFile); /* tail recursion */
11117
11118       case IllegalMove:
11119         if (appData.testLegality) {
11120             if (appData.debugMode)
11121               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
11122             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11123                     (forwardMostMove / 2) + 1,
11124                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11125             DisplayError(move, 0);
11126             done = TRUE;
11127         } else {
11128             if (appData.debugMode)
11129               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
11130                       yy_text, currentMoveString);
11131             fromX = currentMoveString[0] - AAA;
11132             fromY = currentMoveString[1] - ONE;
11133             toX = currentMoveString[2] - AAA;
11134             toY = currentMoveString[3] - ONE;
11135             promoChar = currentMoveString[4];
11136         }
11137         break;
11138
11139       case AmbiguousMove:
11140         if (appData.debugMode)
11141           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
11142         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
11143                 (forwardMostMove / 2) + 1,
11144                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11145         DisplayError(move, 0);
11146         done = TRUE;
11147         break;
11148
11149       default:
11150       case ImpossibleMove:
11151         if (appData.debugMode)
11152           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
11153         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
11154                 (forwardMostMove / 2) + 1,
11155                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
11156         DisplayError(move, 0);
11157         done = TRUE;
11158         break;
11159     }
11160
11161     if (done) {
11162         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
11163             DrawPosition(FALSE, boards[currentMove]);
11164             DisplayBothClocks();
11165             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
11166               DisplayComment(currentMove - 1, commentList[currentMove]);
11167         }
11168         (void) StopLoadGameTimer();
11169         gameFileFP = NULL;
11170         cmailOldMove = forwardMostMove;
11171         return FALSE;
11172     } else {
11173         /* currentMoveString is set as a side-effect of yylex */
11174
11175         thinkOutput[0] = NULLCHAR;
11176         MakeMove(fromX, fromY, toX, toY, promoChar);
11177         currentMove = forwardMostMove;
11178         return TRUE;
11179     }
11180 }
11181
11182 /* Load the nth game from the given file */
11183 int
11184 LoadGameFromFile (char *filename, int n, char *title, int useList)
11185 {
11186     FILE *f;
11187     char buf[MSG_SIZ];
11188
11189     if (strcmp(filename, "-") == 0) {
11190         f = stdin;
11191         title = "stdin";
11192     } else {
11193         f = fopen(filename, "rb");
11194         if (f == NULL) {
11195           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11196             DisplayError(buf, errno);
11197             return FALSE;
11198         }
11199     }
11200     if (fseek(f, 0, 0) == -1) {
11201         /* f is not seekable; probably a pipe */
11202         useList = FALSE;
11203     }
11204     if (useList && n == 0) {
11205         int error = GameListBuild(f);
11206         if (error) {
11207             DisplayError(_("Cannot build game list"), error);
11208         } else if (!ListEmpty(&gameList) &&
11209                    ((ListGame *) gameList.tailPred)->number > 1) {
11210             GameListPopUp(f, title);
11211             return TRUE;
11212         }
11213         GameListDestroy();
11214         n = 1;
11215     }
11216     if (n == 0) n = 1;
11217     return LoadGame(f, n, title, FALSE);
11218 }
11219
11220
11221 void
11222 MakeRegisteredMove ()
11223 {
11224     int fromX, fromY, toX, toY;
11225     char promoChar;
11226     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11227         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11228           case CMAIL_MOVE:
11229           case CMAIL_DRAW:
11230             if (appData.debugMode)
11231               fprintf(debugFP, "Restoring %s for game %d\n",
11232                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11233
11234             thinkOutput[0] = NULLCHAR;
11235             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11236             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11237             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11238             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11239             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11240             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11241             MakeMove(fromX, fromY, toX, toY, promoChar);
11242             ShowMove(fromX, fromY, toX, toY);
11243
11244             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11245               case MT_NONE:
11246               case MT_CHECK:
11247                 break;
11248
11249               case MT_CHECKMATE:
11250               case MT_STAINMATE:
11251                 if (WhiteOnMove(currentMove)) {
11252                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11253                 } else {
11254                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11255                 }
11256                 break;
11257
11258               case MT_STALEMATE:
11259                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11260                 break;
11261             }
11262
11263             break;
11264
11265           case CMAIL_RESIGN:
11266             if (WhiteOnMove(currentMove)) {
11267                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11268             } else {
11269                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11270             }
11271             break;
11272
11273           case CMAIL_ACCEPT:
11274             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11275             break;
11276
11277           default:
11278             break;
11279         }
11280     }
11281
11282     return;
11283 }
11284
11285 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11286 int
11287 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11288 {
11289     int retVal;
11290
11291     if (gameNumber > nCmailGames) {
11292         DisplayError(_("No more games in this message"), 0);
11293         return FALSE;
11294     }
11295     if (f == lastLoadGameFP) {
11296         int offset = gameNumber - lastLoadGameNumber;
11297         if (offset == 0) {
11298             cmailMsg[0] = NULLCHAR;
11299             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11300                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11301                 nCmailMovesRegistered--;
11302             }
11303             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11304             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11305                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11306             }
11307         } else {
11308             if (! RegisterMove()) return FALSE;
11309         }
11310     }
11311
11312     retVal = LoadGame(f, gameNumber, title, useList);
11313
11314     /* Make move registered during previous look at this game, if any */
11315     MakeRegisteredMove();
11316
11317     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11318         commentList[currentMove]
11319           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11320         DisplayComment(currentMove - 1, commentList[currentMove]);
11321     }
11322
11323     return retVal;
11324 }
11325
11326 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11327 int
11328 ReloadGame (int offset)
11329 {
11330     int gameNumber = lastLoadGameNumber + offset;
11331     if (lastLoadGameFP == NULL) {
11332         DisplayError(_("No game has been loaded yet"), 0);
11333         return FALSE;
11334     }
11335     if (gameNumber <= 0) {
11336         DisplayError(_("Can't back up any further"), 0);
11337         return FALSE;
11338     }
11339     if (cmailMsgLoaded) {
11340         return CmailLoadGame(lastLoadGameFP, gameNumber,
11341                              lastLoadGameTitle, lastLoadGameUseList);
11342     } else {
11343         return LoadGame(lastLoadGameFP, gameNumber,
11344                         lastLoadGameTitle, lastLoadGameUseList);
11345     }
11346 }
11347
11348 int keys[EmptySquare+1];
11349
11350 int
11351 PositionMatches (Board b1, Board b2)
11352 {
11353     int r, f, sum=0;
11354     switch(appData.searchMode) {
11355         case 1: return CompareWithRights(b1, b2);
11356         case 2:
11357             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11358                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11359             }
11360             return TRUE;
11361         case 3:
11362             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11363               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11364                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11365             }
11366             return sum==0;
11367         case 4:
11368             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11369                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11370             }
11371             return sum==0;
11372     }
11373     return TRUE;
11374 }
11375
11376 #define Q_PROMO  4
11377 #define Q_EP     3
11378 #define Q_BCASTL 2
11379 #define Q_WCASTL 1
11380
11381 int pieceList[256], quickBoard[256];
11382 ChessSquare pieceType[256] = { EmptySquare };
11383 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11384 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11385 int soughtTotal, turn;
11386 Boolean epOK, flipSearch;
11387
11388 typedef struct {
11389     unsigned char piece, to;
11390 } Move;
11391
11392 #define DSIZE (250000)
11393
11394 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11395 Move *moveDatabase = initialSpace;
11396 unsigned int movePtr, dataSize = DSIZE;
11397
11398 int
11399 MakePieceList (Board board, int *counts)
11400 {
11401     int r, f, n=Q_PROMO, total=0;
11402     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11403     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11404         int sq = f + (r<<4);
11405         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11406             quickBoard[sq] = ++n;
11407             pieceList[n] = sq;
11408             pieceType[n] = board[r][f];
11409             counts[board[r][f]]++;
11410             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11411             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11412             total++;
11413         }
11414     }
11415     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11416     return total;
11417 }
11418
11419 void
11420 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11421 {
11422     int sq = fromX + (fromY<<4);
11423     int piece = quickBoard[sq];
11424     quickBoard[sq] = 0;
11425     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11426     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11427         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11428         moveDatabase[movePtr++].piece = Q_WCASTL;
11429         quickBoard[sq] = piece;
11430         piece = quickBoard[from]; quickBoard[from] = 0;
11431         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11432     } else
11433     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11434         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11435         moveDatabase[movePtr++].piece = Q_BCASTL;
11436         quickBoard[sq] = piece;
11437         piece = quickBoard[from]; quickBoard[from] = 0;
11438         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11439     } else
11440     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11441         quickBoard[(fromY<<4)+toX] = 0;
11442         moveDatabase[movePtr].piece = Q_EP;
11443         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11444         moveDatabase[movePtr].to = sq;
11445     } else
11446     if(promoPiece != pieceType[piece]) {
11447         moveDatabase[movePtr++].piece = Q_PROMO;
11448         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11449     }
11450     moveDatabase[movePtr].piece = piece;
11451     quickBoard[sq] = piece;
11452     movePtr++;
11453 }
11454
11455 int
11456 PackGame (Board board)
11457 {
11458     Move *newSpace = NULL;
11459     moveDatabase[movePtr].piece = 0; // terminate previous game
11460     if(movePtr > dataSize) {
11461         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11462         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11463         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11464         if(newSpace) {
11465             int i;
11466             Move *p = moveDatabase, *q = newSpace;
11467             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11468             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11469             moveDatabase = newSpace;
11470         } else { // calloc failed, we must be out of memory. Too bad...
11471             dataSize = 0; // prevent calloc events for all subsequent games
11472             return 0;     // and signal this one isn't cached
11473         }
11474     }
11475     movePtr++;
11476     MakePieceList(board, counts);
11477     return movePtr;
11478 }
11479
11480 int
11481 QuickCompare (Board board, int *minCounts, int *maxCounts)
11482 {   // compare according to search mode
11483     int r, f;
11484     switch(appData.searchMode)
11485     {
11486       case 1: // exact position match
11487         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11488         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11489             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11490         }
11491         break;
11492       case 2: // can have extra material on empty squares
11493         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11494             if(board[r][f] == EmptySquare) continue;
11495             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11496         }
11497         break;
11498       case 3: // material with exact Pawn structure
11499         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11500             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11501             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11502         } // fall through to material comparison
11503       case 4: // exact material
11504         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11505         break;
11506       case 6: // material range with given imbalance
11507         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11508         // fall through to range comparison
11509       case 5: // material range
11510         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11511     }
11512     return TRUE;
11513 }
11514
11515 int
11516 QuickScan (Board board, Move *move)
11517 {   // reconstruct game,and compare all positions in it
11518     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11519     do {
11520         int piece = move->piece;
11521         int to = move->to, from = pieceList[piece];
11522         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11523           if(!piece) return -1;
11524           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11525             piece = (++move)->piece;
11526             from = pieceList[piece];
11527             counts[pieceType[piece]]--;
11528             pieceType[piece] = (ChessSquare) move->to;
11529             counts[move->to]++;
11530           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11531             counts[pieceType[quickBoard[to]]]--;
11532             quickBoard[to] = 0; total--;
11533             move++;
11534             continue;
11535           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11536             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11537             from  = pieceList[piece]; // so this must be King
11538             quickBoard[from] = 0;
11539             quickBoard[to] = piece;
11540             pieceList[piece] = to;
11541             move++;
11542             continue;
11543           }
11544         }
11545         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11546         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11547         quickBoard[from] = 0;
11548         quickBoard[to] = piece;
11549         pieceList[piece] = to;
11550         cnt++; turn ^= 3;
11551         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11552            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11553            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11554                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11555           ) {
11556             static int lastCounts[EmptySquare+1];
11557             int i;
11558             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11559             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11560         } else stretch = 0;
11561         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11562         move++;
11563     } while(1);
11564 }
11565
11566 void
11567 InitSearch ()
11568 {
11569     int r, f;
11570     flipSearch = FALSE;
11571     CopyBoard(soughtBoard, boards[currentMove]);
11572     soughtTotal = MakePieceList(soughtBoard, maxSought);
11573     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11574     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11575     CopyBoard(reverseBoard, boards[currentMove]);
11576     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11577         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11578         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11579         reverseBoard[r][f] = piece;
11580     }
11581     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11582     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11583     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11584                  || (boards[currentMove][CASTLING][2] == NoRights || 
11585                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11586                  && (boards[currentMove][CASTLING][5] == NoRights || 
11587                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11588       ) {
11589         flipSearch = TRUE;
11590         CopyBoard(flipBoard, soughtBoard);
11591         CopyBoard(rotateBoard, reverseBoard);
11592         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11593             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11594             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11595         }
11596     }
11597     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11598     if(appData.searchMode >= 5) {
11599         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11600         MakePieceList(soughtBoard, minSought);
11601         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11602     }
11603     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11604         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11605 }
11606
11607 GameInfo dummyInfo;
11608
11609 int
11610 GameContainsPosition (FILE *f, ListGame *lg)
11611 {
11612     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11613     int fromX, fromY, toX, toY;
11614     char promoChar;
11615     static int initDone=FALSE;
11616
11617     // weed out games based on numerical tag comparison
11618     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11619     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11620     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11621     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11622     if(!initDone) {
11623         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11624         initDone = TRUE;
11625     }
11626     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11627     else CopyBoard(boards[scratch], initialPosition); // default start position
11628     if(lg->moves) {
11629         turn = btm + 1;
11630         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11631         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11632     }
11633     if(btm) plyNr++;
11634     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11635     fseek(f, lg->offset, 0);
11636     yynewfile(f);
11637     while(1) {
11638         yyboardindex = scratch;
11639         quickFlag = plyNr+1;
11640         next = Myylex();
11641         quickFlag = 0;
11642         switch(next) {
11643             case PGNTag:
11644                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11645             default:
11646                 continue;
11647
11648             case XBoardGame:
11649             case GNUChessGame:
11650                 if(plyNr) return -1; // after we have seen moves, this is for new game
11651               continue;
11652
11653             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11654             case ImpossibleMove:
11655             case WhiteWins: // game ends here with these four
11656             case BlackWins:
11657             case GameIsDrawn:
11658             case GameUnfinished:
11659                 return -1;
11660
11661             case IllegalMove:
11662                 if(appData.testLegality) return -1;
11663             case WhiteCapturesEnPassant:
11664             case BlackCapturesEnPassant:
11665             case WhitePromotion:
11666             case BlackPromotion:
11667             case WhiteNonPromotion:
11668             case BlackNonPromotion:
11669             case NormalMove:
11670             case WhiteKingSideCastle:
11671             case WhiteQueenSideCastle:
11672             case BlackKingSideCastle:
11673             case BlackQueenSideCastle:
11674             case WhiteKingSideCastleWild:
11675             case WhiteQueenSideCastleWild:
11676             case BlackKingSideCastleWild:
11677             case BlackQueenSideCastleWild:
11678             case WhiteHSideCastleFR:
11679             case WhiteASideCastleFR:
11680             case BlackHSideCastleFR:
11681             case BlackASideCastleFR:
11682                 fromX = currentMoveString[0] - AAA;
11683                 fromY = currentMoveString[1] - ONE;
11684                 toX = currentMoveString[2] - AAA;
11685                 toY = currentMoveString[3] - ONE;
11686                 promoChar = currentMoveString[4];
11687                 break;
11688             case WhiteDrop:
11689             case BlackDrop:
11690                 fromX = next == WhiteDrop ?
11691                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11692                   (int) CharToPiece(ToLower(currentMoveString[0]));
11693                 fromY = DROP_RANK;
11694                 toX = currentMoveString[2] - AAA;
11695                 toY = currentMoveString[3] - ONE;
11696                 promoChar = 0;
11697                 break;
11698         }
11699         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11700         plyNr++;
11701         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11702         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11703         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11704         if(appData.findMirror) {
11705             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11706             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11707         }
11708     }
11709 }
11710
11711 /* Load the nth game from open file f */
11712 int
11713 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11714 {
11715     ChessMove cm;
11716     char buf[MSG_SIZ];
11717     int gn = gameNumber;
11718     ListGame *lg = NULL;
11719     int numPGNTags = 0;
11720     int err, pos = -1;
11721     GameMode oldGameMode;
11722     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11723
11724     if (appData.debugMode)
11725         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11726
11727     if (gameMode == Training )
11728         SetTrainingModeOff();
11729
11730     oldGameMode = gameMode;
11731     if (gameMode != BeginningOfGame) {
11732       Reset(FALSE, TRUE);
11733     }
11734
11735     gameFileFP = f;
11736     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11737         fclose(lastLoadGameFP);
11738     }
11739
11740     if (useList) {
11741         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11742
11743         if (lg) {
11744             fseek(f, lg->offset, 0);
11745             GameListHighlight(gameNumber);
11746             pos = lg->position;
11747             gn = 1;
11748         }
11749         else {
11750             DisplayError(_("Game number out of range"), 0);
11751             return FALSE;
11752         }
11753     } else {
11754         GameListDestroy();
11755         if (fseek(f, 0, 0) == -1) {
11756             if (f == lastLoadGameFP ?
11757                 gameNumber == lastLoadGameNumber + 1 :
11758                 gameNumber == 1) {
11759                 gn = 1;
11760             } else {
11761                 DisplayError(_("Can't seek on game file"), 0);
11762                 return FALSE;
11763             }
11764         }
11765     }
11766     lastLoadGameFP = f;
11767     lastLoadGameNumber = gameNumber;
11768     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11769     lastLoadGameUseList = useList;
11770
11771     yynewfile(f);
11772
11773     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11774       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11775                 lg->gameInfo.black);
11776             DisplayTitle(buf);
11777     } else if (*title != NULLCHAR) {
11778         if (gameNumber > 1) {
11779           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11780             DisplayTitle(buf);
11781         } else {
11782             DisplayTitle(title);
11783         }
11784     }
11785
11786     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11787         gameMode = PlayFromGameFile;
11788         ModeHighlight();
11789     }
11790
11791     currentMove = forwardMostMove = backwardMostMove = 0;
11792     CopyBoard(boards[0], initialPosition);
11793     StopClocks();
11794
11795     /*
11796      * Skip the first gn-1 games in the file.
11797      * Also skip over anything that precedes an identifiable
11798      * start of game marker, to avoid being confused by
11799      * garbage at the start of the file.  Currently
11800      * recognized start of game markers are the move number "1",
11801      * the pattern "gnuchess .* game", the pattern
11802      * "^[#;%] [^ ]* game file", and a PGN tag block.
11803      * A game that starts with one of the latter two patterns
11804      * will also have a move number 1, possibly
11805      * following a position diagram.
11806      * 5-4-02: Let's try being more lenient and allowing a game to
11807      * start with an unnumbered move.  Does that break anything?
11808      */
11809     cm = lastLoadGameStart = EndOfFile;
11810     while (gn > 0) {
11811         yyboardindex = forwardMostMove;
11812         cm = (ChessMove) Myylex();
11813         switch (cm) {
11814           case EndOfFile:
11815             if (cmailMsgLoaded) {
11816                 nCmailGames = CMAIL_MAX_GAMES - gn;
11817             } else {
11818                 Reset(TRUE, TRUE);
11819                 DisplayError(_("Game not found in file"), 0);
11820             }
11821             return FALSE;
11822
11823           case GNUChessGame:
11824           case XBoardGame:
11825             gn--;
11826             lastLoadGameStart = cm;
11827             break;
11828
11829           case MoveNumberOne:
11830             switch (lastLoadGameStart) {
11831               case GNUChessGame:
11832               case XBoardGame:
11833               case PGNTag:
11834                 break;
11835               case MoveNumberOne:
11836               case EndOfFile:
11837                 gn--;           /* count this game */
11838                 lastLoadGameStart = cm;
11839                 break;
11840               default:
11841                 /* impossible */
11842                 break;
11843             }
11844             break;
11845
11846           case PGNTag:
11847             switch (lastLoadGameStart) {
11848               case GNUChessGame:
11849               case PGNTag:
11850               case MoveNumberOne:
11851               case EndOfFile:
11852                 gn--;           /* count this game */
11853                 lastLoadGameStart = cm;
11854                 break;
11855               case XBoardGame:
11856                 lastLoadGameStart = cm; /* game counted already */
11857                 break;
11858               default:
11859                 /* impossible */
11860                 break;
11861             }
11862             if (gn > 0) {
11863                 do {
11864                     yyboardindex = forwardMostMove;
11865                     cm = (ChessMove) Myylex();
11866                 } while (cm == PGNTag || cm == Comment);
11867             }
11868             break;
11869
11870           case WhiteWins:
11871           case BlackWins:
11872           case GameIsDrawn:
11873             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11874                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11875                     != CMAIL_OLD_RESULT) {
11876                     nCmailResults ++ ;
11877                     cmailResult[  CMAIL_MAX_GAMES
11878                                 - gn - 1] = CMAIL_OLD_RESULT;
11879                 }
11880             }
11881             break;
11882
11883           case NormalMove:
11884             /* Only a NormalMove can be at the start of a game
11885              * without a position diagram. */
11886             if (lastLoadGameStart == EndOfFile ) {
11887               gn--;
11888               lastLoadGameStart = MoveNumberOne;
11889             }
11890             break;
11891
11892           default:
11893             break;
11894         }
11895     }
11896
11897     if (appData.debugMode)
11898       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11899
11900     if (cm == XBoardGame) {
11901         /* Skip any header junk before position diagram and/or move 1 */
11902         for (;;) {
11903             yyboardindex = forwardMostMove;
11904             cm = (ChessMove) Myylex();
11905
11906             if (cm == EndOfFile ||
11907                 cm == GNUChessGame || cm == XBoardGame) {
11908                 /* Empty game; pretend end-of-file and handle later */
11909                 cm = EndOfFile;
11910                 break;
11911             }
11912
11913             if (cm == MoveNumberOne || cm == PositionDiagram ||
11914                 cm == PGNTag || cm == Comment)
11915               break;
11916         }
11917     } else if (cm == GNUChessGame) {
11918         if (gameInfo.event != NULL) {
11919             free(gameInfo.event);
11920         }
11921         gameInfo.event = StrSave(yy_text);
11922     }
11923
11924     startedFromSetupPosition = FALSE;
11925     while (cm == PGNTag) {
11926         if (appData.debugMode)
11927           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11928         err = ParsePGNTag(yy_text, &gameInfo);
11929         if (!err) numPGNTags++;
11930
11931         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11932         if(gameInfo.variant != oldVariant) {
11933             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11934             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11935             InitPosition(TRUE);
11936             oldVariant = gameInfo.variant;
11937             if (appData.debugMode)
11938               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11939         }
11940
11941
11942         if (gameInfo.fen != NULL) {
11943           Board initial_position;
11944           startedFromSetupPosition = TRUE;
11945           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11946             Reset(TRUE, TRUE);
11947             DisplayError(_("Bad FEN position in file"), 0);
11948             return FALSE;
11949           }
11950           CopyBoard(boards[0], initial_position);
11951           if (blackPlaysFirst) {
11952             currentMove = forwardMostMove = backwardMostMove = 1;
11953             CopyBoard(boards[1], initial_position);
11954             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11955             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11956             timeRemaining[0][1] = whiteTimeRemaining;
11957             timeRemaining[1][1] = blackTimeRemaining;
11958             if (commentList[0] != NULL) {
11959               commentList[1] = commentList[0];
11960               commentList[0] = NULL;
11961             }
11962           } else {
11963             currentMove = forwardMostMove = backwardMostMove = 0;
11964           }
11965           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11966           {   int i;
11967               initialRulePlies = FENrulePlies;
11968               for( i=0; i< nrCastlingRights; i++ )
11969                   initialRights[i] = initial_position[CASTLING][i];
11970           }
11971           yyboardindex = forwardMostMove;
11972           free(gameInfo.fen);
11973           gameInfo.fen = NULL;
11974         }
11975
11976         yyboardindex = forwardMostMove;
11977         cm = (ChessMove) Myylex();
11978
11979         /* Handle comments interspersed among the tags */
11980         while (cm == Comment) {
11981             char *p;
11982             if (appData.debugMode)
11983               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11984             p = yy_text;
11985             AppendComment(currentMove, p, FALSE);
11986             yyboardindex = forwardMostMove;
11987             cm = (ChessMove) Myylex();
11988         }
11989     }
11990
11991     /* don't rely on existence of Event tag since if game was
11992      * pasted from clipboard the Event tag may not exist
11993      */
11994     if (numPGNTags > 0){
11995         char *tags;
11996         if (gameInfo.variant == VariantNormal) {
11997           VariantClass v = StringToVariant(gameInfo.event);
11998           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11999           if(v < VariantShogi) gameInfo.variant = v;
12000         }
12001         if (!matchMode) {
12002           if( appData.autoDisplayTags ) {
12003             tags = PGNTags(&gameInfo);
12004             TagsPopUp(tags, CmailMsg());
12005             free(tags);
12006           }
12007         }
12008     } else {
12009         /* Make something up, but don't display it now */
12010         SetGameInfo();
12011         TagsPopDown();
12012     }
12013
12014     if (cm == PositionDiagram) {
12015         int i, j;
12016         char *p;
12017         Board initial_position;
12018
12019         if (appData.debugMode)
12020           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
12021
12022         if (!startedFromSetupPosition) {
12023             p = yy_text;
12024             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
12025               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
12026                 switch (*p) {
12027                   case '{':
12028                   case '[':
12029                   case '-':
12030                   case ' ':
12031                   case '\t':
12032                   case '\n':
12033                   case '\r':
12034                     break;
12035                   default:
12036                     initial_position[i][j++] = CharToPiece(*p);
12037                     break;
12038                 }
12039             while (*p == ' ' || *p == '\t' ||
12040                    *p == '\n' || *p == '\r') p++;
12041
12042             if (strncmp(p, "black", strlen("black"))==0)
12043               blackPlaysFirst = TRUE;
12044             else
12045               blackPlaysFirst = FALSE;
12046             startedFromSetupPosition = TRUE;
12047
12048             CopyBoard(boards[0], initial_position);
12049             if (blackPlaysFirst) {
12050                 currentMove = forwardMostMove = backwardMostMove = 1;
12051                 CopyBoard(boards[1], initial_position);
12052                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12053                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12054                 timeRemaining[0][1] = whiteTimeRemaining;
12055                 timeRemaining[1][1] = blackTimeRemaining;
12056                 if (commentList[0] != NULL) {
12057                     commentList[1] = commentList[0];
12058                     commentList[0] = NULL;
12059                 }
12060             } else {
12061                 currentMove = forwardMostMove = backwardMostMove = 0;
12062             }
12063         }
12064         yyboardindex = forwardMostMove;
12065         cm = (ChessMove) Myylex();
12066     }
12067
12068     if (first.pr == NoProc) {
12069         StartChessProgram(&first);
12070     }
12071     InitChessProgram(&first, FALSE);
12072     SendToProgram("force\n", &first);
12073     if (startedFromSetupPosition) {
12074         SendBoard(&first, forwardMostMove);
12075     if (appData.debugMode) {
12076         fprintf(debugFP, "Load Game\n");
12077     }
12078         DisplayBothClocks();
12079     }
12080
12081     /* [HGM] server: flag to write setup moves in broadcast file as one */
12082     loadFlag = appData.suppressLoadMoves;
12083
12084     while (cm == Comment) {
12085         char *p;
12086         if (appData.debugMode)
12087           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12088         p = yy_text;
12089         AppendComment(currentMove, p, FALSE);
12090         yyboardindex = forwardMostMove;
12091         cm = (ChessMove) Myylex();
12092     }
12093
12094     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
12095         cm == WhiteWins || cm == BlackWins ||
12096         cm == GameIsDrawn || cm == GameUnfinished) {
12097         DisplayMessage("", _("No moves in game"));
12098         if (cmailMsgLoaded) {
12099             if (appData.debugMode)
12100               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
12101             ClearHighlights();
12102             flipView = FALSE;
12103         }
12104         DrawPosition(FALSE, boards[currentMove]);
12105         DisplayBothClocks();
12106         gameMode = EditGame;
12107         ModeHighlight();
12108         gameFileFP = NULL;
12109         cmailOldMove = 0;
12110         return TRUE;
12111     }
12112
12113     // [HGM] PV info: routine tests if comment empty
12114     if (!matchMode && (pausing || appData.timeDelay != 0)) {
12115         DisplayComment(currentMove - 1, commentList[currentMove]);
12116     }
12117     if (!matchMode && appData.timeDelay != 0)
12118       DrawPosition(FALSE, boards[currentMove]);
12119
12120     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
12121       programStats.ok_to_send = 1;
12122     }
12123
12124     /* if the first token after the PGN tags is a move
12125      * and not move number 1, retrieve it from the parser
12126      */
12127     if (cm != MoveNumberOne)
12128         LoadGameOneMove(cm);
12129
12130     /* load the remaining moves from the file */
12131     while (LoadGameOneMove(EndOfFile)) {
12132       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12133       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12134     }
12135
12136     /* rewind to the start of the game */
12137     currentMove = backwardMostMove;
12138
12139     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12140
12141     if (oldGameMode == AnalyzeFile ||
12142         oldGameMode == AnalyzeMode) {
12143       AnalyzeFileEvent();
12144     }
12145
12146     if (!matchMode && pos >= 0) {
12147         ToNrEvent(pos); // [HGM] no autoplay if selected on position
12148     } else
12149     if (matchMode || appData.timeDelay == 0) {
12150       ToEndEvent();
12151     } else if (appData.timeDelay > 0) {
12152       AutoPlayGameLoop();
12153     }
12154
12155     if (appData.debugMode)
12156         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
12157
12158     loadFlag = 0; /* [HGM] true game starts */
12159     return TRUE;
12160 }
12161
12162 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
12163 int
12164 ReloadPosition (int offset)
12165 {
12166     int positionNumber = lastLoadPositionNumber + offset;
12167     if (lastLoadPositionFP == NULL) {
12168         DisplayError(_("No position has been loaded yet"), 0);
12169         return FALSE;
12170     }
12171     if (positionNumber <= 0) {
12172         DisplayError(_("Can't back up any further"), 0);
12173         return FALSE;
12174     }
12175     return LoadPosition(lastLoadPositionFP, positionNumber,
12176                         lastLoadPositionTitle);
12177 }
12178
12179 /* Load the nth position from the given file */
12180 int
12181 LoadPositionFromFile (char *filename, int n, char *title)
12182 {
12183     FILE *f;
12184     char buf[MSG_SIZ];
12185
12186     if (strcmp(filename, "-") == 0) {
12187         return LoadPosition(stdin, n, "stdin");
12188     } else {
12189         f = fopen(filename, "rb");
12190         if (f == NULL) {
12191             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12192             DisplayError(buf, errno);
12193             return FALSE;
12194         } else {
12195             return LoadPosition(f, n, title);
12196         }
12197     }
12198 }
12199
12200 /* Load the nth position from the given open file, and close it */
12201 int
12202 LoadPosition (FILE *f, int positionNumber, char *title)
12203 {
12204     char *p, line[MSG_SIZ];
12205     Board initial_position;
12206     int i, j, fenMode, pn;
12207
12208     if (gameMode == Training )
12209         SetTrainingModeOff();
12210
12211     if (gameMode != BeginningOfGame) {
12212         Reset(FALSE, TRUE);
12213     }
12214     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12215         fclose(lastLoadPositionFP);
12216     }
12217     if (positionNumber == 0) positionNumber = 1;
12218     lastLoadPositionFP = f;
12219     lastLoadPositionNumber = positionNumber;
12220     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12221     if (first.pr == NoProc && !appData.noChessProgram) {
12222       StartChessProgram(&first);
12223       InitChessProgram(&first, FALSE);
12224     }
12225     pn = positionNumber;
12226     if (positionNumber < 0) {
12227         /* Negative position number means to seek to that byte offset */
12228         if (fseek(f, -positionNumber, 0) == -1) {
12229             DisplayError(_("Can't seek on position file"), 0);
12230             return FALSE;
12231         };
12232         pn = 1;
12233     } else {
12234         if (fseek(f, 0, 0) == -1) {
12235             if (f == lastLoadPositionFP ?
12236                 positionNumber == lastLoadPositionNumber + 1 :
12237                 positionNumber == 1) {
12238                 pn = 1;
12239             } else {
12240                 DisplayError(_("Can't seek on position file"), 0);
12241                 return FALSE;
12242             }
12243         }
12244     }
12245     /* See if this file is FEN or old-style xboard */
12246     if (fgets(line, MSG_SIZ, f) == NULL) {
12247         DisplayError(_("Position not found in file"), 0);
12248         return FALSE;
12249     }
12250     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12251     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12252
12253     if (pn >= 2) {
12254         if (fenMode || line[0] == '#') pn--;
12255         while (pn > 0) {
12256             /* skip positions before number pn */
12257             if (fgets(line, MSG_SIZ, f) == NULL) {
12258                 Reset(TRUE, TRUE);
12259                 DisplayError(_("Position not found in file"), 0);
12260                 return FALSE;
12261             }
12262             if (fenMode || line[0] == '#') pn--;
12263         }
12264     }
12265
12266     if (fenMode) {
12267         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12268             DisplayError(_("Bad FEN position in file"), 0);
12269             return FALSE;
12270         }
12271     } else {
12272         (void) fgets(line, MSG_SIZ, f);
12273         (void) fgets(line, MSG_SIZ, f);
12274
12275         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12276             (void) fgets(line, MSG_SIZ, f);
12277             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12278                 if (*p == ' ')
12279                   continue;
12280                 initial_position[i][j++] = CharToPiece(*p);
12281             }
12282         }
12283
12284         blackPlaysFirst = FALSE;
12285         if (!feof(f)) {
12286             (void) fgets(line, MSG_SIZ, f);
12287             if (strncmp(line, "black", strlen("black"))==0)
12288               blackPlaysFirst = TRUE;
12289         }
12290     }
12291     startedFromSetupPosition = TRUE;
12292
12293     CopyBoard(boards[0], initial_position);
12294     if (blackPlaysFirst) {
12295         currentMove = forwardMostMove = backwardMostMove = 1;
12296         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12297         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12298         CopyBoard(boards[1], initial_position);
12299         DisplayMessage("", _("Black to play"));
12300     } else {
12301         currentMove = forwardMostMove = backwardMostMove = 0;
12302         DisplayMessage("", _("White to play"));
12303     }
12304     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12305     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12306         SendToProgram("force\n", &first);
12307         SendBoard(&first, forwardMostMove);
12308     }
12309     if (appData.debugMode) {
12310 int i, j;
12311   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12312   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12313         fprintf(debugFP, "Load Position\n");
12314     }
12315
12316     if (positionNumber > 1) {
12317       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12318         DisplayTitle(line);
12319     } else {
12320         DisplayTitle(title);
12321     }
12322     gameMode = EditGame;
12323     ModeHighlight();
12324     ResetClocks();
12325     timeRemaining[0][1] = whiteTimeRemaining;
12326     timeRemaining[1][1] = blackTimeRemaining;
12327     DrawPosition(FALSE, boards[currentMove]);
12328
12329     return TRUE;
12330 }
12331
12332
12333 void
12334 CopyPlayerNameIntoFileName (char **dest, char *src)
12335 {
12336     while (*src != NULLCHAR && *src != ',') {
12337         if (*src == ' ') {
12338             *(*dest)++ = '_';
12339             src++;
12340         } else {
12341             *(*dest)++ = *src++;
12342         }
12343     }
12344 }
12345
12346 char *
12347 DefaultFileName (char *ext)
12348 {
12349     static char def[MSG_SIZ];
12350     char *p;
12351
12352     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12353         p = def;
12354         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12355         *p++ = '-';
12356         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12357         *p++ = '.';
12358         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12359     } else {
12360         def[0] = NULLCHAR;
12361     }
12362     return def;
12363 }
12364
12365 /* Save the current game to the given file */
12366 int
12367 SaveGameToFile (char *filename, int append)
12368 {
12369     FILE *f;
12370     char buf[MSG_SIZ];
12371     int result, i, t,tot=0;
12372
12373     if (strcmp(filename, "-") == 0) {
12374         return SaveGame(stdout, 0, NULL);
12375     } else {
12376         for(i=0; i<10; i++) { // upto 10 tries
12377              f = fopen(filename, append ? "a" : "w");
12378              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12379              if(f || errno != 13) break;
12380              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12381              tot += t;
12382         }
12383         if (f == NULL) {
12384             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12385             DisplayError(buf, errno);
12386             return FALSE;
12387         } else {
12388             safeStrCpy(buf, lastMsg, MSG_SIZ);
12389             DisplayMessage(_("Waiting for access to save file"), "");
12390             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12391             DisplayMessage(_("Saving game"), "");
12392             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12393             result = SaveGame(f, 0, NULL);
12394             DisplayMessage(buf, "");
12395             return result;
12396         }
12397     }
12398 }
12399
12400 char *
12401 SavePart (char *str)
12402 {
12403     static char buf[MSG_SIZ];
12404     char *p;
12405
12406     p = strchr(str, ' ');
12407     if (p == NULL) return str;
12408     strncpy(buf, str, p - str);
12409     buf[p - str] = NULLCHAR;
12410     return buf;
12411 }
12412
12413 #define PGN_MAX_LINE 75
12414
12415 #define PGN_SIDE_WHITE  0
12416 #define PGN_SIDE_BLACK  1
12417
12418 static int
12419 FindFirstMoveOutOfBook (int side)
12420 {
12421     int result = -1;
12422
12423     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12424         int index = backwardMostMove;
12425         int has_book_hit = 0;
12426
12427         if( (index % 2) != side ) {
12428             index++;
12429         }
12430
12431         while( index < forwardMostMove ) {
12432             /* Check to see if engine is in book */
12433             int depth = pvInfoList[index].depth;
12434             int score = pvInfoList[index].score;
12435             int in_book = 0;
12436
12437             if( depth <= 2 ) {
12438                 in_book = 1;
12439             }
12440             else if( score == 0 && depth == 63 ) {
12441                 in_book = 1; /* Zappa */
12442             }
12443             else if( score == 2 && depth == 99 ) {
12444                 in_book = 1; /* Abrok */
12445             }
12446
12447             has_book_hit += in_book;
12448
12449             if( ! in_book ) {
12450                 result = index;
12451
12452                 break;
12453             }
12454
12455             index += 2;
12456         }
12457     }
12458
12459     return result;
12460 }
12461
12462 void
12463 GetOutOfBookInfo (char * buf)
12464 {
12465     int oob[2];
12466     int i;
12467     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12468
12469     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12470     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12471
12472     *buf = '\0';
12473
12474     if( oob[0] >= 0 || oob[1] >= 0 ) {
12475         for( i=0; i<2; i++ ) {
12476             int idx = oob[i];
12477
12478             if( idx >= 0 ) {
12479                 if( i > 0 && oob[0] >= 0 ) {
12480                     strcat( buf, "   " );
12481                 }
12482
12483                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12484                 sprintf( buf+strlen(buf), "%s%.2f",
12485                     pvInfoList[idx].score >= 0 ? "+" : "",
12486                     pvInfoList[idx].score / 100.0 );
12487             }
12488         }
12489     }
12490 }
12491
12492 /* Save game in PGN style and close the file */
12493 int
12494 SaveGamePGN (FILE *f)
12495 {
12496     int i, offset, linelen, newblock;
12497     time_t tm;
12498 //    char *movetext;
12499     char numtext[32];
12500     int movelen, numlen, blank;
12501     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12502
12503     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12504
12505     tm = time((time_t *) NULL);
12506
12507     PrintPGNTags(f, &gameInfo);
12508
12509     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12510
12511     if (backwardMostMove > 0 || startedFromSetupPosition) {
12512         char *fen = PositionToFEN(backwardMostMove, NULL);
12513         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12514         fprintf(f, "\n{--------------\n");
12515         PrintPosition(f, backwardMostMove);
12516         fprintf(f, "--------------}\n");
12517         free(fen);
12518     }
12519     else {
12520         /* [AS] Out of book annotation */
12521         if( appData.saveOutOfBookInfo ) {
12522             char buf[64];
12523
12524             GetOutOfBookInfo( buf );
12525
12526             if( buf[0] != '\0' ) {
12527                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12528             }
12529         }
12530
12531         fprintf(f, "\n");
12532     }
12533
12534     i = backwardMostMove;
12535     linelen = 0;
12536     newblock = TRUE;
12537
12538     while (i < forwardMostMove) {
12539         /* Print comments preceding this move */
12540         if (commentList[i] != NULL) {
12541             if (linelen > 0) fprintf(f, "\n");
12542             fprintf(f, "%s", commentList[i]);
12543             linelen = 0;
12544             newblock = TRUE;
12545         }
12546
12547         /* Format move number */
12548         if ((i % 2) == 0)
12549           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12550         else
12551           if (newblock)
12552             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12553           else
12554             numtext[0] = NULLCHAR;
12555
12556         numlen = strlen(numtext);
12557         newblock = FALSE;
12558
12559         /* Print move number */
12560         blank = linelen > 0 && numlen > 0;
12561         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12562             fprintf(f, "\n");
12563             linelen = 0;
12564             blank = 0;
12565         }
12566         if (blank) {
12567             fprintf(f, " ");
12568             linelen++;
12569         }
12570         fprintf(f, "%s", numtext);
12571         linelen += numlen;
12572
12573         /* Get move */
12574         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12575         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12576
12577         /* Print move */
12578         blank = linelen > 0 && movelen > 0;
12579         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12580             fprintf(f, "\n");
12581             linelen = 0;
12582             blank = 0;
12583         }
12584         if (blank) {
12585             fprintf(f, " ");
12586             linelen++;
12587         }
12588         fprintf(f, "%s", move_buffer);
12589         linelen += movelen;
12590
12591         /* [AS] Add PV info if present */
12592         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12593             /* [HGM] add time */
12594             char buf[MSG_SIZ]; int seconds;
12595
12596             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12597
12598             if( seconds <= 0)
12599               buf[0] = 0;
12600             else
12601               if( seconds < 30 )
12602                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12603               else
12604                 {
12605                   seconds = (seconds + 4)/10; // round to full seconds
12606                   if( seconds < 60 )
12607                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12608                   else
12609                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12610                 }
12611
12612             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12613                       pvInfoList[i].score >= 0 ? "+" : "",
12614                       pvInfoList[i].score / 100.0,
12615                       pvInfoList[i].depth,
12616                       buf );
12617
12618             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12619
12620             /* Print score/depth */
12621             blank = linelen > 0 && movelen > 0;
12622             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12623                 fprintf(f, "\n");
12624                 linelen = 0;
12625                 blank = 0;
12626             }
12627             if (blank) {
12628                 fprintf(f, " ");
12629                 linelen++;
12630             }
12631             fprintf(f, "%s", move_buffer);
12632             linelen += movelen;
12633         }
12634
12635         i++;
12636     }
12637
12638     /* Start a new line */
12639     if (linelen > 0) fprintf(f, "\n");
12640
12641     /* Print comments after last move */
12642     if (commentList[i] != NULL) {
12643         fprintf(f, "%s\n", commentList[i]);
12644     }
12645
12646     /* Print result */
12647     if (gameInfo.resultDetails != NULL &&
12648         gameInfo.resultDetails[0] != NULLCHAR) {
12649         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12650                 PGNResult(gameInfo.result));
12651     } else {
12652         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12653     }
12654
12655     fclose(f);
12656     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12657     return TRUE;
12658 }
12659
12660 /* Save game in old style and close the file */
12661 int
12662 SaveGameOldStyle (FILE *f)
12663 {
12664     int i, offset;
12665     time_t tm;
12666
12667     tm = time((time_t *) NULL);
12668
12669     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12670     PrintOpponents(f);
12671
12672     if (backwardMostMove > 0 || startedFromSetupPosition) {
12673         fprintf(f, "\n[--------------\n");
12674         PrintPosition(f, backwardMostMove);
12675         fprintf(f, "--------------]\n");
12676     } else {
12677         fprintf(f, "\n");
12678     }
12679
12680     i = backwardMostMove;
12681     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12682
12683     while (i < forwardMostMove) {
12684         if (commentList[i] != NULL) {
12685             fprintf(f, "[%s]\n", commentList[i]);
12686         }
12687
12688         if ((i % 2) == 1) {
12689             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12690             i++;
12691         } else {
12692             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12693             i++;
12694             if (commentList[i] != NULL) {
12695                 fprintf(f, "\n");
12696                 continue;
12697             }
12698             if (i >= forwardMostMove) {
12699                 fprintf(f, "\n");
12700                 break;
12701             }
12702             fprintf(f, "%s\n", parseList[i]);
12703             i++;
12704         }
12705     }
12706
12707     if (commentList[i] != NULL) {
12708         fprintf(f, "[%s]\n", commentList[i]);
12709     }
12710
12711     /* This isn't really the old style, but it's close enough */
12712     if (gameInfo.resultDetails != NULL &&
12713         gameInfo.resultDetails[0] != NULLCHAR) {
12714         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12715                 gameInfo.resultDetails);
12716     } else {
12717         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12718     }
12719
12720     fclose(f);
12721     return TRUE;
12722 }
12723
12724 /* Save the current game to open file f and close the file */
12725 int
12726 SaveGame (FILE *f, int dummy, char *dummy2)
12727 {
12728     if (gameMode == EditPosition) EditPositionDone(TRUE);
12729     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12730     if (appData.oldSaveStyle)
12731       return SaveGameOldStyle(f);
12732     else
12733       return SaveGamePGN(f);
12734 }
12735
12736 /* Save the current position to the given file */
12737 int
12738 SavePositionToFile (char *filename)
12739 {
12740     FILE *f;
12741     char buf[MSG_SIZ];
12742
12743     if (strcmp(filename, "-") == 0) {
12744         return SavePosition(stdout, 0, NULL);
12745     } else {
12746         f = fopen(filename, "a");
12747         if (f == NULL) {
12748             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12749             DisplayError(buf, errno);
12750             return FALSE;
12751         } else {
12752             safeStrCpy(buf, lastMsg, MSG_SIZ);
12753             DisplayMessage(_("Waiting for access to save file"), "");
12754             flock(fileno(f), LOCK_EX); // [HGM] lock
12755             DisplayMessage(_("Saving position"), "");
12756             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12757             SavePosition(f, 0, NULL);
12758             DisplayMessage(buf, "");
12759             return TRUE;
12760         }
12761     }
12762 }
12763
12764 /* Save the current position to the given open file and close the file */
12765 int
12766 SavePosition (FILE *f, int dummy, char *dummy2)
12767 {
12768     time_t tm;
12769     char *fen;
12770
12771     if (gameMode == EditPosition) EditPositionDone(TRUE);
12772     if (appData.oldSaveStyle) {
12773         tm = time((time_t *) NULL);
12774
12775         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12776         PrintOpponents(f);
12777         fprintf(f, "[--------------\n");
12778         PrintPosition(f, currentMove);
12779         fprintf(f, "--------------]\n");
12780     } else {
12781         fen = PositionToFEN(currentMove, NULL);
12782         fprintf(f, "%s\n", fen);
12783         free(fen);
12784     }
12785     fclose(f);
12786     return TRUE;
12787 }
12788
12789 void
12790 ReloadCmailMsgEvent (int unregister)
12791 {
12792 #if !WIN32
12793     static char *inFilename = NULL;
12794     static char *outFilename;
12795     int i;
12796     struct stat inbuf, outbuf;
12797     int status;
12798
12799     /* Any registered moves are unregistered if unregister is set, */
12800     /* i.e. invoked by the signal handler */
12801     if (unregister) {
12802         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12803             cmailMoveRegistered[i] = FALSE;
12804             if (cmailCommentList[i] != NULL) {
12805                 free(cmailCommentList[i]);
12806                 cmailCommentList[i] = NULL;
12807             }
12808         }
12809         nCmailMovesRegistered = 0;
12810     }
12811
12812     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12813         cmailResult[i] = CMAIL_NOT_RESULT;
12814     }
12815     nCmailResults = 0;
12816
12817     if (inFilename == NULL) {
12818         /* Because the filenames are static they only get malloced once  */
12819         /* and they never get freed                                      */
12820         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12821         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12822
12823         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12824         sprintf(outFilename, "%s.out", appData.cmailGameName);
12825     }
12826
12827     status = stat(outFilename, &outbuf);
12828     if (status < 0) {
12829         cmailMailedMove = FALSE;
12830     } else {
12831         status = stat(inFilename, &inbuf);
12832         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12833     }
12834
12835     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12836        counts the games, notes how each one terminated, etc.
12837
12838        It would be nice to remove this kludge and instead gather all
12839        the information while building the game list.  (And to keep it
12840        in the game list nodes instead of having a bunch of fixed-size
12841        parallel arrays.)  Note this will require getting each game's
12842        termination from the PGN tags, as the game list builder does
12843        not process the game moves.  --mann
12844        */
12845     cmailMsgLoaded = TRUE;
12846     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12847
12848     /* Load first game in the file or popup game menu */
12849     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12850
12851 #endif /* !WIN32 */
12852     return;
12853 }
12854
12855 int
12856 RegisterMove ()
12857 {
12858     FILE *f;
12859     char string[MSG_SIZ];
12860
12861     if (   cmailMailedMove
12862         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12863         return TRUE;            /* Allow free viewing  */
12864     }
12865
12866     /* Unregister move to ensure that we don't leave RegisterMove        */
12867     /* with the move registered when the conditions for registering no   */
12868     /* longer hold                                                       */
12869     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12870         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12871         nCmailMovesRegistered --;
12872
12873         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12874           {
12875               free(cmailCommentList[lastLoadGameNumber - 1]);
12876               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12877           }
12878     }
12879
12880     if (cmailOldMove == -1) {
12881         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12882         return FALSE;
12883     }
12884
12885     if (currentMove > cmailOldMove + 1) {
12886         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12887         return FALSE;
12888     }
12889
12890     if (currentMove < cmailOldMove) {
12891         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12892         return FALSE;
12893     }
12894
12895     if (forwardMostMove > currentMove) {
12896         /* Silently truncate extra moves */
12897         TruncateGame();
12898     }
12899
12900     if (   (currentMove == cmailOldMove + 1)
12901         || (   (currentMove == cmailOldMove)
12902             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12903                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12904         if (gameInfo.result != GameUnfinished) {
12905             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12906         }
12907
12908         if (commentList[currentMove] != NULL) {
12909             cmailCommentList[lastLoadGameNumber - 1]
12910               = StrSave(commentList[currentMove]);
12911         }
12912         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12913
12914         if (appData.debugMode)
12915           fprintf(debugFP, "Saving %s for game %d\n",
12916                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12917
12918         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12919
12920         f = fopen(string, "w");
12921         if (appData.oldSaveStyle) {
12922             SaveGameOldStyle(f); /* also closes the file */
12923
12924             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12925             f = fopen(string, "w");
12926             SavePosition(f, 0, NULL); /* also closes the file */
12927         } else {
12928             fprintf(f, "{--------------\n");
12929             PrintPosition(f, currentMove);
12930             fprintf(f, "--------------}\n\n");
12931
12932             SaveGame(f, 0, NULL); /* also closes the file*/
12933         }
12934
12935         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12936         nCmailMovesRegistered ++;
12937     } else if (nCmailGames == 1) {
12938         DisplayError(_("You have not made a move yet"), 0);
12939         return FALSE;
12940     }
12941
12942     return TRUE;
12943 }
12944
12945 void
12946 MailMoveEvent ()
12947 {
12948 #if !WIN32
12949     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12950     FILE *commandOutput;
12951     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12952     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12953     int nBuffers;
12954     int i;
12955     int archived;
12956     char *arcDir;
12957
12958     if (! cmailMsgLoaded) {
12959         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12960         return;
12961     }
12962
12963     if (nCmailGames == nCmailResults) {
12964         DisplayError(_("No unfinished games"), 0);
12965         return;
12966     }
12967
12968 #if CMAIL_PROHIBIT_REMAIL
12969     if (cmailMailedMove) {
12970       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);
12971         DisplayError(msg, 0);
12972         return;
12973     }
12974 #endif
12975
12976     if (! (cmailMailedMove || RegisterMove())) return;
12977
12978     if (   cmailMailedMove
12979         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12980       snprintf(string, MSG_SIZ, partCommandString,
12981                appData.debugMode ? " -v" : "", appData.cmailGameName);
12982         commandOutput = popen(string, "r");
12983
12984         if (commandOutput == NULL) {
12985             DisplayError(_("Failed to invoke cmail"), 0);
12986         } else {
12987             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12988                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12989             }
12990             if (nBuffers > 1) {
12991                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12992                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12993                 nBytes = MSG_SIZ - 1;
12994             } else {
12995                 (void) memcpy(msg, buffer, nBytes);
12996             }
12997             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12998
12999             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
13000                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
13001
13002                 archived = TRUE;
13003                 for (i = 0; i < nCmailGames; i ++) {
13004                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
13005                         archived = FALSE;
13006                     }
13007                 }
13008                 if (   archived
13009                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
13010                         != NULL)) {
13011                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
13012                            arcDir,
13013                            appData.cmailGameName,
13014                            gameInfo.date);
13015                     LoadGameFromFile(buffer, 1, buffer, FALSE);
13016                     cmailMsgLoaded = FALSE;
13017                 }
13018             }
13019
13020             DisplayInformation(msg);
13021             pclose(commandOutput);
13022         }
13023     } else {
13024         if ((*cmailMsg) != '\0') {
13025             DisplayInformation(cmailMsg);
13026         }
13027     }
13028
13029     return;
13030 #endif /* !WIN32 */
13031 }
13032
13033 char *
13034 CmailMsg ()
13035 {
13036 #if WIN32
13037     return NULL;
13038 #else
13039     int  prependComma = 0;
13040     char number[5];
13041     char string[MSG_SIZ];       /* Space for game-list */
13042     int  i;
13043
13044     if (!cmailMsgLoaded) return "";
13045
13046     if (cmailMailedMove) {
13047       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
13048     } else {
13049         /* Create a list of games left */
13050       snprintf(string, MSG_SIZ, "[");
13051         for (i = 0; i < nCmailGames; i ++) {
13052             if (! (   cmailMoveRegistered[i]
13053                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
13054                 if (prependComma) {
13055                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
13056                 } else {
13057                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
13058                     prependComma = 1;
13059                 }
13060
13061                 strcat(string, number);
13062             }
13063         }
13064         strcat(string, "]");
13065
13066         if (nCmailMovesRegistered + nCmailResults == 0) {
13067             switch (nCmailGames) {
13068               case 1:
13069                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
13070                 break;
13071
13072               case 2:
13073                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
13074                 break;
13075
13076               default:
13077                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
13078                          nCmailGames);
13079                 break;
13080             }
13081         } else {
13082             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
13083               case 1:
13084                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
13085                          string);
13086                 break;
13087
13088               case 0:
13089                 if (nCmailResults == nCmailGames) {
13090                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
13091                 } else {
13092                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
13093                 }
13094                 break;
13095
13096               default:
13097                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
13098                          string);
13099             }
13100         }
13101     }
13102     return cmailMsg;
13103 #endif /* WIN32 */
13104 }
13105
13106 void
13107 ResetGameEvent ()
13108 {
13109     if (gameMode == Training)
13110       SetTrainingModeOff();
13111
13112     Reset(TRUE, TRUE);
13113     cmailMsgLoaded = FALSE;
13114     if (appData.icsActive) {
13115       SendToICS(ics_prefix);
13116       SendToICS("refresh\n");
13117     }
13118 }
13119
13120 void
13121 ExitEvent (int status)
13122 {
13123     exiting++;
13124     if (exiting > 2) {
13125       /* Give up on clean exit */
13126       exit(status);
13127     }
13128     if (exiting > 1) {
13129       /* Keep trying for clean exit */
13130       return;
13131     }
13132
13133     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
13134
13135     if (telnetISR != NULL) {
13136       RemoveInputSource(telnetISR);
13137     }
13138     if (icsPR != NoProc) {
13139       DestroyChildProcess(icsPR, TRUE);
13140     }
13141
13142     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
13143     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
13144
13145     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
13146     /* make sure this other one finishes before killing it!                  */
13147     if(endingGame) { int count = 0;
13148         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
13149         while(endingGame && count++ < 10) DoSleep(1);
13150         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
13151     }
13152
13153     /* Kill off chess programs */
13154     if (first.pr != NoProc) {
13155         ExitAnalyzeMode();
13156
13157         DoSleep( appData.delayBeforeQuit );
13158         SendToProgram("quit\n", &first);
13159         DoSleep( appData.delayAfterQuit );
13160         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
13161     }
13162     if (second.pr != NoProc) {
13163         DoSleep( appData.delayBeforeQuit );
13164         SendToProgram("quit\n", &second);
13165         DoSleep( appData.delayAfterQuit );
13166         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13167     }
13168     if (first.isr != NULL) {
13169         RemoveInputSource(first.isr);
13170     }
13171     if (second.isr != NULL) {
13172         RemoveInputSource(second.isr);
13173     }
13174
13175     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13176     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13177
13178     ShutDownFrontEnd();
13179     exit(status);
13180 }
13181
13182 void
13183 PauseEvent ()
13184 {
13185     if (appData.debugMode)
13186         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13187     if (pausing) {
13188         pausing = FALSE;
13189         ModeHighlight();
13190         if (gameMode == MachinePlaysWhite ||
13191             gameMode == MachinePlaysBlack) {
13192             StartClocks();
13193         } else {
13194             DisplayBothClocks();
13195         }
13196         if (gameMode == PlayFromGameFile) {
13197             if (appData.timeDelay >= 0)
13198                 AutoPlayGameLoop();
13199         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13200             Reset(FALSE, TRUE);
13201             SendToICS(ics_prefix);
13202             SendToICS("refresh\n");
13203         } else if (currentMove < forwardMostMove) {
13204             ForwardInner(forwardMostMove);
13205         }
13206         pauseExamInvalid = FALSE;
13207     } else {
13208         switch (gameMode) {
13209           default:
13210             return;
13211           case IcsExamining:
13212             pauseExamForwardMostMove = forwardMostMove;
13213             pauseExamInvalid = FALSE;
13214             /* fall through */
13215           case IcsObserving:
13216           case IcsPlayingWhite:
13217           case IcsPlayingBlack:
13218             pausing = TRUE;
13219             ModeHighlight();
13220             return;
13221           case PlayFromGameFile:
13222             (void) StopLoadGameTimer();
13223             pausing = TRUE;
13224             ModeHighlight();
13225             break;
13226           case BeginningOfGame:
13227             if (appData.icsActive) return;
13228             /* else fall through */
13229           case MachinePlaysWhite:
13230           case MachinePlaysBlack:
13231           case TwoMachinesPlay:
13232             if (forwardMostMove == 0)
13233               return;           /* don't pause if no one has moved */
13234             if ((gameMode == MachinePlaysWhite &&
13235                  !WhiteOnMove(forwardMostMove)) ||
13236                 (gameMode == MachinePlaysBlack &&
13237                  WhiteOnMove(forwardMostMove))) {
13238                 StopClocks();
13239             }
13240             pausing = TRUE;
13241             ModeHighlight();
13242             break;
13243         }
13244     }
13245 }
13246
13247 void
13248 EditCommentEvent ()
13249 {
13250     char title[MSG_SIZ];
13251
13252     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13253       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13254     } else {
13255       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13256                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13257                parseList[currentMove - 1]);
13258     }
13259
13260     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13261 }
13262
13263
13264 void
13265 EditTagsEvent ()
13266 {
13267     char *tags = PGNTags(&gameInfo);
13268     bookUp = FALSE;
13269     EditTagsPopUp(tags, NULL);
13270     free(tags);
13271 }
13272
13273 void
13274 AnalyzeModeEvent ()
13275 {
13276     if (appData.noChessProgram || gameMode == AnalyzeMode)
13277       return;
13278
13279     if (gameMode != AnalyzeFile) {
13280         if (!appData.icsEngineAnalyze) {
13281                EditGameEvent();
13282                if (gameMode != EditGame) return;
13283         }
13284         ResurrectChessProgram();
13285         SendToProgram("analyze\n", &first);
13286         first.analyzing = TRUE;
13287         /*first.maybeThinking = TRUE;*/
13288         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13289         EngineOutputPopUp();
13290     }
13291     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13292     pausing = FALSE;
13293     ModeHighlight();
13294     SetGameInfo();
13295
13296     StartAnalysisClock();
13297     GetTimeMark(&lastNodeCountTime);
13298     lastNodeCount = 0;
13299 }
13300
13301 void
13302 AnalyzeFileEvent ()
13303 {
13304     if (appData.noChessProgram || gameMode == AnalyzeFile)
13305       return;
13306
13307     if (gameMode != AnalyzeMode) {
13308         EditGameEvent();
13309         if (gameMode != EditGame) return;
13310         ResurrectChessProgram();
13311         SendToProgram("analyze\n", &first);
13312         first.analyzing = TRUE;
13313         /*first.maybeThinking = TRUE;*/
13314         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13315         EngineOutputPopUp();
13316     }
13317     gameMode = AnalyzeFile;
13318     pausing = FALSE;
13319     ModeHighlight();
13320     SetGameInfo();
13321
13322     StartAnalysisClock();
13323     GetTimeMark(&lastNodeCountTime);
13324     lastNodeCount = 0;
13325     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13326 }
13327
13328 void
13329 MachineWhiteEvent ()
13330 {
13331     char buf[MSG_SIZ];
13332     char *bookHit = NULL;
13333
13334     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13335       return;
13336
13337
13338     if (gameMode == PlayFromGameFile ||
13339         gameMode == TwoMachinesPlay  ||
13340         gameMode == Training         ||
13341         gameMode == AnalyzeMode      ||
13342         gameMode == EndOfGame)
13343         EditGameEvent();
13344
13345     if (gameMode == EditPosition)
13346         EditPositionDone(TRUE);
13347
13348     if (!WhiteOnMove(currentMove)) {
13349         DisplayError(_("It is not White's turn"), 0);
13350         return;
13351     }
13352
13353     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13354       ExitAnalyzeMode();
13355
13356     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13357         gameMode == AnalyzeFile)
13358         TruncateGame();
13359
13360     ResurrectChessProgram();    /* in case it isn't running */
13361     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13362         gameMode = MachinePlaysWhite;
13363         ResetClocks();
13364     } else
13365     gameMode = MachinePlaysWhite;
13366     pausing = FALSE;
13367     ModeHighlight();
13368     SetGameInfo();
13369     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13370     DisplayTitle(buf);
13371     if (first.sendName) {
13372       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13373       SendToProgram(buf, &first);
13374     }
13375     if (first.sendTime) {
13376       if (first.useColors) {
13377         SendToProgram("black\n", &first); /*gnu kludge*/
13378       }
13379       SendTimeRemaining(&first, TRUE);
13380     }
13381     if (first.useColors) {
13382       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13383     }
13384     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13385     SetMachineThinkingEnables();
13386     first.maybeThinking = TRUE;
13387     StartClocks();
13388     firstMove = FALSE;
13389
13390     if (appData.autoFlipView && !flipView) {
13391       flipView = !flipView;
13392       DrawPosition(FALSE, NULL);
13393       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13394     }
13395
13396     if(bookHit) { // [HGM] book: simulate book reply
13397         static char bookMove[MSG_SIZ]; // a bit generous?
13398
13399         programStats.nodes = programStats.depth = programStats.time =
13400         programStats.score = programStats.got_only_move = 0;
13401         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13402
13403         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13404         strcat(bookMove, bookHit);
13405         HandleMachineMove(bookMove, &first);
13406     }
13407 }
13408
13409 void
13410 MachineBlackEvent ()
13411 {
13412   char buf[MSG_SIZ];
13413   char *bookHit = NULL;
13414
13415     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13416         return;
13417
13418
13419     if (gameMode == PlayFromGameFile ||
13420         gameMode == TwoMachinesPlay  ||
13421         gameMode == Training         ||
13422         gameMode == AnalyzeMode      ||
13423         gameMode == EndOfGame)
13424         EditGameEvent();
13425
13426     if (gameMode == EditPosition)
13427         EditPositionDone(TRUE);
13428
13429     if (WhiteOnMove(currentMove)) {
13430         DisplayError(_("It is not Black's turn"), 0);
13431         return;
13432     }
13433
13434     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13435       ExitAnalyzeMode();
13436
13437     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13438         gameMode == AnalyzeFile)
13439         TruncateGame();
13440
13441     ResurrectChessProgram();    /* in case it isn't running */
13442     gameMode = MachinePlaysBlack;
13443     pausing = FALSE;
13444     ModeHighlight();
13445     SetGameInfo();
13446     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13447     DisplayTitle(buf);
13448     if (first.sendName) {
13449       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13450       SendToProgram(buf, &first);
13451     }
13452     if (first.sendTime) {
13453       if (first.useColors) {
13454         SendToProgram("white\n", &first); /*gnu kludge*/
13455       }
13456       SendTimeRemaining(&first, FALSE);
13457     }
13458     if (first.useColors) {
13459       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13460     }
13461     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13462     SetMachineThinkingEnables();
13463     first.maybeThinking = TRUE;
13464     StartClocks();
13465
13466     if (appData.autoFlipView && flipView) {
13467       flipView = !flipView;
13468       DrawPosition(FALSE, NULL);
13469       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13470     }
13471     if(bookHit) { // [HGM] book: simulate book reply
13472         static char bookMove[MSG_SIZ]; // a bit generous?
13473
13474         programStats.nodes = programStats.depth = programStats.time =
13475         programStats.score = programStats.got_only_move = 0;
13476         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13477
13478         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13479         strcat(bookMove, bookHit);
13480         HandleMachineMove(bookMove, &first);
13481     }
13482 }
13483
13484
13485 void
13486 DisplayTwoMachinesTitle ()
13487 {
13488     char buf[MSG_SIZ];
13489     if (appData.matchGames > 0) {
13490         if(appData.tourneyFile[0]) {
13491           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13492                    gameInfo.white, _("vs."), gameInfo.black,
13493                    nextGame+1, appData.matchGames+1,
13494                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13495         } else 
13496         if (first.twoMachinesColor[0] == 'w') {
13497           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13498                    gameInfo.white, _("vs."),  gameInfo.black,
13499                    first.matchWins, second.matchWins,
13500                    matchGame - 1 - (first.matchWins + second.matchWins));
13501         } else {
13502           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13503                    gameInfo.white, _("vs."), gameInfo.black,
13504                    second.matchWins, first.matchWins,
13505                    matchGame - 1 - (first.matchWins + second.matchWins));
13506         }
13507     } else {
13508       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13509     }
13510     DisplayTitle(buf);
13511 }
13512
13513 void
13514 SettingsMenuIfReady ()
13515 {
13516   if (second.lastPing != second.lastPong) {
13517     DisplayMessage("", _("Waiting for second chess program"));
13518     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13519     return;
13520   }
13521   ThawUI();
13522   DisplayMessage("", "");
13523   SettingsPopUp(&second);
13524 }
13525
13526 int
13527 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13528 {
13529     char buf[MSG_SIZ];
13530     if (cps->pr == NoProc) {
13531         StartChessProgram(cps);
13532         if (cps->protocolVersion == 1) {
13533           retry();
13534         } else {
13535           /* kludge: allow timeout for initial "feature" command */
13536           FreezeUI();
13537           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
13538           DisplayMessage("", buf);
13539           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13540         }
13541         return 1;
13542     }
13543     return 0;
13544 }
13545
13546 void
13547 TwoMachinesEvent P((void))
13548 {
13549     int i;
13550     char buf[MSG_SIZ];
13551     ChessProgramState *onmove;
13552     char *bookHit = NULL;
13553     static int stalling = 0;
13554     TimeMark now;
13555     long wait;
13556
13557     if (appData.noChessProgram) return;
13558
13559     if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
13560         DisplayError("second engine does not play this", 0);
13561         return;
13562     }
13563
13564     switch (gameMode) {
13565       case TwoMachinesPlay:
13566         return;
13567       case MachinePlaysWhite:
13568       case MachinePlaysBlack:
13569         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13570             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13571             return;
13572         }
13573         /* fall through */
13574       case BeginningOfGame:
13575       case PlayFromGameFile:
13576       case EndOfGame:
13577         EditGameEvent();
13578         if (gameMode != EditGame) return;
13579         break;
13580       case EditPosition:
13581         EditPositionDone(TRUE);
13582         break;
13583       case AnalyzeMode:
13584       case AnalyzeFile:
13585         ExitAnalyzeMode();
13586         break;
13587       case EditGame:
13588       default:
13589         break;
13590     }
13591
13592 //    forwardMostMove = currentMove;
13593     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13594
13595     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13596
13597     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13598     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13599       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13600       return;
13601     }
13602     if(!stalling) {
13603       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13604       SendToProgram("force\n", &second);
13605       stalling = 1;
13606       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13607       return;
13608     }
13609     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13610     if(appData.matchPause>10000 || appData.matchPause<10)
13611                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13612     wait = SubtractTimeMarks(&now, &pauseStart);
13613     if(wait < appData.matchPause) {
13614         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13615         return;
13616     }
13617     // we are now committed to starting the game
13618     stalling = 0;
13619     DisplayMessage("", "");
13620     if (startedFromSetupPosition) {
13621         SendBoard(&second, backwardMostMove);
13622     if (appData.debugMode) {
13623         fprintf(debugFP, "Two Machines\n");
13624     }
13625     }
13626     for (i = backwardMostMove; i < forwardMostMove; i++) {
13627         SendMoveToProgram(i, &second);
13628     }
13629
13630     gameMode = TwoMachinesPlay;
13631     pausing = FALSE;
13632     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13633     SetGameInfo();
13634     DisplayTwoMachinesTitle();
13635     firstMove = TRUE;
13636     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13637         onmove = &first;
13638     } else {
13639         onmove = &second;
13640     }
13641     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13642     SendToProgram(first.computerString, &first);
13643     if (first.sendName) {
13644       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13645       SendToProgram(buf, &first);
13646     }
13647     SendToProgram(second.computerString, &second);
13648     if (second.sendName) {
13649       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13650       SendToProgram(buf, &second);
13651     }
13652
13653     ResetClocks();
13654     if (!first.sendTime || !second.sendTime) {
13655         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13656         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13657     }
13658     if (onmove->sendTime) {
13659       if (onmove->useColors) {
13660         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13661       }
13662       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13663     }
13664     if (onmove->useColors) {
13665       SendToProgram(onmove->twoMachinesColor, onmove);
13666     }
13667     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13668 //    SendToProgram("go\n", onmove);
13669     onmove->maybeThinking = TRUE;
13670     SetMachineThinkingEnables();
13671
13672     StartClocks();
13673
13674     if(bookHit) { // [HGM] book: simulate book reply
13675         static char bookMove[MSG_SIZ]; // a bit generous?
13676
13677         programStats.nodes = programStats.depth = programStats.time =
13678         programStats.score = programStats.got_only_move = 0;
13679         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13680
13681         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13682         strcat(bookMove, bookHit);
13683         savedMessage = bookMove; // args for deferred call
13684         savedState = onmove;
13685         ScheduleDelayedEvent(DeferredBookMove, 1);
13686     }
13687 }
13688
13689 void
13690 TrainingEvent ()
13691 {
13692     if (gameMode == Training) {
13693       SetTrainingModeOff();
13694       gameMode = PlayFromGameFile;
13695       DisplayMessage("", _("Training mode off"));
13696     } else {
13697       gameMode = Training;
13698       animateTraining = appData.animate;
13699
13700       /* make sure we are not already at the end of the game */
13701       if (currentMove < forwardMostMove) {
13702         SetTrainingModeOn();
13703         DisplayMessage("", _("Training mode on"));
13704       } else {
13705         gameMode = PlayFromGameFile;
13706         DisplayError(_("Already at end of game"), 0);
13707       }
13708     }
13709     ModeHighlight();
13710 }
13711
13712 void
13713 IcsClientEvent ()
13714 {
13715     if (!appData.icsActive) return;
13716     switch (gameMode) {
13717       case IcsPlayingWhite:
13718       case IcsPlayingBlack:
13719       case IcsObserving:
13720       case IcsIdle:
13721       case BeginningOfGame:
13722       case IcsExamining:
13723         return;
13724
13725       case EditGame:
13726         break;
13727
13728       case EditPosition:
13729         EditPositionDone(TRUE);
13730         break;
13731
13732       case AnalyzeMode:
13733       case AnalyzeFile:
13734         ExitAnalyzeMode();
13735         break;
13736
13737       default:
13738         EditGameEvent();
13739         break;
13740     }
13741
13742     gameMode = IcsIdle;
13743     ModeHighlight();
13744     return;
13745 }
13746
13747 void
13748 EditGameEvent ()
13749 {
13750     int i;
13751
13752     switch (gameMode) {
13753       case Training:
13754         SetTrainingModeOff();
13755         break;
13756       case MachinePlaysWhite:
13757       case MachinePlaysBlack:
13758       case BeginningOfGame:
13759         SendToProgram("force\n", &first);
13760         SetUserThinkingEnables();
13761         break;
13762       case PlayFromGameFile:
13763         (void) StopLoadGameTimer();
13764         if (gameFileFP != NULL) {
13765             gameFileFP = NULL;
13766         }
13767         break;
13768       case EditPosition:
13769         EditPositionDone(TRUE);
13770         break;
13771       case AnalyzeMode:
13772       case AnalyzeFile:
13773         ExitAnalyzeMode();
13774         SendToProgram("force\n", &first);
13775         break;
13776       case TwoMachinesPlay:
13777         GameEnds(EndOfFile, NULL, GE_PLAYER);
13778         ResurrectChessProgram();
13779         SetUserThinkingEnables();
13780         break;
13781       case EndOfGame:
13782         ResurrectChessProgram();
13783         break;
13784       case IcsPlayingBlack:
13785       case IcsPlayingWhite:
13786         DisplayError(_("Warning: You are still playing a game"), 0);
13787         break;
13788       case IcsObserving:
13789         DisplayError(_("Warning: You are still observing a game"), 0);
13790         break;
13791       case IcsExamining:
13792         DisplayError(_("Warning: You are still examining a game"), 0);
13793         break;
13794       case IcsIdle:
13795         break;
13796       case EditGame:
13797       default:
13798         return;
13799     }
13800
13801     pausing = FALSE;
13802     StopClocks();
13803     first.offeredDraw = second.offeredDraw = 0;
13804
13805     if (gameMode == PlayFromGameFile) {
13806         whiteTimeRemaining = timeRemaining[0][currentMove];
13807         blackTimeRemaining = timeRemaining[1][currentMove];
13808         DisplayTitle("");
13809     }
13810
13811     if (gameMode == MachinePlaysWhite ||
13812         gameMode == MachinePlaysBlack ||
13813         gameMode == TwoMachinesPlay ||
13814         gameMode == EndOfGame) {
13815         i = forwardMostMove;
13816         while (i > currentMove) {
13817             SendToProgram("undo\n", &first);
13818             i--;
13819         }
13820         if(!adjustedClock) {
13821         whiteTimeRemaining = timeRemaining[0][currentMove];
13822         blackTimeRemaining = timeRemaining[1][currentMove];
13823         DisplayBothClocks();
13824         }
13825         if (whiteFlag || blackFlag) {
13826             whiteFlag = blackFlag = 0;
13827         }
13828         DisplayTitle("");
13829     }
13830
13831     gameMode = EditGame;
13832     ModeHighlight();
13833     SetGameInfo();
13834 }
13835
13836
13837 void
13838 EditPositionEvent ()
13839 {
13840     if (gameMode == EditPosition) {
13841         EditGameEvent();
13842         return;
13843     }
13844
13845     EditGameEvent();
13846     if (gameMode != EditGame) return;
13847
13848     gameMode = EditPosition;
13849     ModeHighlight();
13850     SetGameInfo();
13851     if (currentMove > 0)
13852       CopyBoard(boards[0], boards[currentMove]);
13853
13854     blackPlaysFirst = !WhiteOnMove(currentMove);
13855     ResetClocks();
13856     currentMove = forwardMostMove = backwardMostMove = 0;
13857     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13858     DisplayMove(-1);
13859     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
13860 }
13861
13862 void
13863 ExitAnalyzeMode ()
13864 {
13865     /* [DM] icsEngineAnalyze - possible call from other functions */
13866     if (appData.icsEngineAnalyze) {
13867         appData.icsEngineAnalyze = FALSE;
13868
13869         DisplayMessage("",_("Close ICS engine analyze..."));
13870     }
13871     if (first.analysisSupport && first.analyzing) {
13872       SendToProgram("exit\n", &first);
13873       first.analyzing = FALSE;
13874     }
13875     thinkOutput[0] = NULLCHAR;
13876 }
13877
13878 void
13879 EditPositionDone (Boolean fakeRights)
13880 {
13881     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13882
13883     startedFromSetupPosition = TRUE;
13884     InitChessProgram(&first, FALSE);
13885     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13886       boards[0][EP_STATUS] = EP_NONE;
13887       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13888     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13889         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13890         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13891       } else boards[0][CASTLING][2] = NoRights;
13892     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13893         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13894         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13895       } else boards[0][CASTLING][5] = NoRights;
13896     }
13897     SendToProgram("force\n", &first);
13898     if (blackPlaysFirst) {
13899         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13900         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13901         currentMove = forwardMostMove = backwardMostMove = 1;
13902         CopyBoard(boards[1], boards[0]);
13903     } else {
13904         currentMove = forwardMostMove = backwardMostMove = 0;
13905     }
13906     SendBoard(&first, forwardMostMove);
13907     if (appData.debugMode) {
13908         fprintf(debugFP, "EditPosDone\n");
13909     }
13910     DisplayTitle("");
13911     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13912     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13913     gameMode = EditGame;
13914     ModeHighlight();
13915     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13916     ClearHighlights(); /* [AS] */
13917 }
13918
13919 /* Pause for `ms' milliseconds */
13920 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13921 void
13922 TimeDelay (long ms)
13923 {
13924     TimeMark m1, m2;
13925
13926     GetTimeMark(&m1);
13927     do {
13928         GetTimeMark(&m2);
13929     } while (SubtractTimeMarks(&m2, &m1) < ms);
13930 }
13931
13932 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13933 void
13934 SendMultiLineToICS (char *buf)
13935 {
13936     char temp[MSG_SIZ+1], *p;
13937     int len;
13938
13939     len = strlen(buf);
13940     if (len > MSG_SIZ)
13941       len = MSG_SIZ;
13942
13943     strncpy(temp, buf, len);
13944     temp[len] = 0;
13945
13946     p = temp;
13947     while (*p) {
13948         if (*p == '\n' || *p == '\r')
13949           *p = ' ';
13950         ++p;
13951     }
13952
13953     strcat(temp, "\n");
13954     SendToICS(temp);
13955     SendToPlayer(temp, strlen(temp));
13956 }
13957
13958 void
13959 SetWhiteToPlayEvent ()
13960 {
13961     if (gameMode == EditPosition) {
13962         blackPlaysFirst = FALSE;
13963         DisplayBothClocks();    /* works because currentMove is 0 */
13964     } else if (gameMode == IcsExamining) {
13965         SendToICS(ics_prefix);
13966         SendToICS("tomove white\n");
13967     }
13968 }
13969
13970 void
13971 SetBlackToPlayEvent ()
13972 {
13973     if (gameMode == EditPosition) {
13974         blackPlaysFirst = TRUE;
13975         currentMove = 1;        /* kludge */
13976         DisplayBothClocks();
13977         currentMove = 0;
13978     } else if (gameMode == IcsExamining) {
13979         SendToICS(ics_prefix);
13980         SendToICS("tomove black\n");
13981     }
13982 }
13983
13984 void
13985 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13986 {
13987     char buf[MSG_SIZ];
13988     ChessSquare piece = boards[0][y][x];
13989
13990     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13991
13992     switch (selection) {
13993       case ClearBoard:
13994         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13995             SendToICS(ics_prefix);
13996             SendToICS("bsetup clear\n");
13997         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13998             SendToICS(ics_prefix);
13999             SendToICS("clearboard\n");
14000         } else {
14001             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
14002                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
14003                 for (y = 0; y < BOARD_HEIGHT; y++) {
14004                     if (gameMode == IcsExamining) {
14005                         if (boards[currentMove][y][x] != EmptySquare) {
14006                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
14007                                     AAA + x, ONE + y);
14008                             SendToICS(buf);
14009                         }
14010                     } else {
14011                         boards[0][y][x] = p;
14012                     }
14013                 }
14014             }
14015         }
14016         if (gameMode == EditPosition) {
14017             DrawPosition(FALSE, boards[0]);
14018         }
14019         break;
14020
14021       case WhitePlay:
14022         SetWhiteToPlayEvent();
14023         break;
14024
14025       case BlackPlay:
14026         SetBlackToPlayEvent();
14027         break;
14028
14029       case EmptySquare:
14030         if (gameMode == IcsExamining) {
14031             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14032             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
14033             SendToICS(buf);
14034         } else {
14035             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14036                 if(x == BOARD_LEFT-2) {
14037                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
14038                     boards[0][y][1] = 0;
14039                 } else
14040                 if(x == BOARD_RGHT+1) {
14041                     if(y >= gameInfo.holdingsSize) break;
14042                     boards[0][y][BOARD_WIDTH-2] = 0;
14043                 } else break;
14044             }
14045             boards[0][y][x] = EmptySquare;
14046             DrawPosition(FALSE, boards[0]);
14047         }
14048         break;
14049
14050       case PromotePiece:
14051         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
14052            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
14053             selection = (ChessSquare) (PROMOTED piece);
14054         } else if(piece == EmptySquare) selection = WhiteSilver;
14055         else selection = (ChessSquare)((int)piece - 1);
14056         goto defaultlabel;
14057
14058       case DemotePiece:
14059         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
14060            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
14061             selection = (ChessSquare) (DEMOTED piece);
14062         } else if(piece == EmptySquare) selection = BlackSilver;
14063         else selection = (ChessSquare)((int)piece + 1);
14064         goto defaultlabel;
14065
14066       case WhiteQueen:
14067       case BlackQueen:
14068         if(gameInfo.variant == VariantShatranj ||
14069            gameInfo.variant == VariantXiangqi  ||
14070            gameInfo.variant == VariantCourier  ||
14071            gameInfo.variant == VariantMakruk     )
14072             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
14073         goto defaultlabel;
14074
14075       case WhiteKing:
14076       case BlackKing:
14077         if(gameInfo.variant == VariantXiangqi)
14078             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
14079         if(gameInfo.variant == VariantKnightmate)
14080             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
14081       default:
14082         defaultlabel:
14083         if (gameMode == IcsExamining) {
14084             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
14085             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
14086                      PieceToChar(selection), AAA + x, ONE + y);
14087             SendToICS(buf);
14088         } else {
14089             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
14090                 int n;
14091                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
14092                     n = PieceToNumber(selection - BlackPawn);
14093                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
14094                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
14095                     boards[0][BOARD_HEIGHT-1-n][1]++;
14096                 } else
14097                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
14098                     n = PieceToNumber(selection);
14099                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
14100                     boards[0][n][BOARD_WIDTH-1] = selection;
14101                     boards[0][n][BOARD_WIDTH-2]++;
14102                 }
14103             } else
14104             boards[0][y][x] = selection;
14105             DrawPosition(TRUE, boards[0]);
14106             ClearHighlights();
14107             fromX = fromY = -1;
14108         }
14109         break;
14110     }
14111 }
14112
14113
14114 void
14115 DropMenuEvent (ChessSquare selection, int x, int y)
14116 {
14117     ChessMove moveType;
14118
14119     switch (gameMode) {
14120       case IcsPlayingWhite:
14121       case MachinePlaysBlack:
14122         if (!WhiteOnMove(currentMove)) {
14123             DisplayMoveError(_("It is Black's turn"));
14124             return;
14125         }
14126         moveType = WhiteDrop;
14127         break;
14128       case IcsPlayingBlack:
14129       case MachinePlaysWhite:
14130         if (WhiteOnMove(currentMove)) {
14131             DisplayMoveError(_("It is White's turn"));
14132             return;
14133         }
14134         moveType = BlackDrop;
14135         break;
14136       case EditGame:
14137         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
14138         break;
14139       default:
14140         return;
14141     }
14142
14143     if (moveType == BlackDrop && selection < BlackPawn) {
14144       selection = (ChessSquare) ((int) selection
14145                                  + (int) BlackPawn - (int) WhitePawn);
14146     }
14147     if (boards[currentMove][y][x] != EmptySquare) {
14148         DisplayMoveError(_("That square is occupied"));
14149         return;
14150     }
14151
14152     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
14153 }
14154
14155 void
14156 AcceptEvent ()
14157 {
14158     /* Accept a pending offer of any kind from opponent */
14159
14160     if (appData.icsActive) {
14161         SendToICS(ics_prefix);
14162         SendToICS("accept\n");
14163     } else if (cmailMsgLoaded) {
14164         if (currentMove == cmailOldMove &&
14165             commentList[cmailOldMove] != NULL &&
14166             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14167                    "Black offers a draw" : "White offers a draw")) {
14168             TruncateGame();
14169             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14170             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14171         } else {
14172             DisplayError(_("There is no pending offer on this move"), 0);
14173             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14174         }
14175     } else {
14176         /* Not used for offers from chess program */
14177     }
14178 }
14179
14180 void
14181 DeclineEvent ()
14182 {
14183     /* Decline a pending offer of any kind from opponent */
14184
14185     if (appData.icsActive) {
14186         SendToICS(ics_prefix);
14187         SendToICS("decline\n");
14188     } else if (cmailMsgLoaded) {
14189         if (currentMove == cmailOldMove &&
14190             commentList[cmailOldMove] != NULL &&
14191             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14192                    "Black offers a draw" : "White offers a draw")) {
14193 #ifdef NOTDEF
14194             AppendComment(cmailOldMove, "Draw declined", TRUE);
14195             DisplayComment(cmailOldMove - 1, "Draw declined");
14196 #endif /*NOTDEF*/
14197         } else {
14198             DisplayError(_("There is no pending offer on this move"), 0);
14199         }
14200     } else {
14201         /* Not used for offers from chess program */
14202     }
14203 }
14204
14205 void
14206 RematchEvent ()
14207 {
14208     /* Issue ICS rematch command */
14209     if (appData.icsActive) {
14210         SendToICS(ics_prefix);
14211         SendToICS("rematch\n");
14212     }
14213 }
14214
14215 void
14216 CallFlagEvent ()
14217 {
14218     /* Call your opponent's flag (claim a win on time) */
14219     if (appData.icsActive) {
14220         SendToICS(ics_prefix);
14221         SendToICS("flag\n");
14222     } else {
14223         switch (gameMode) {
14224           default:
14225             return;
14226           case MachinePlaysWhite:
14227             if (whiteFlag) {
14228                 if (blackFlag)
14229                   GameEnds(GameIsDrawn, "Both players ran out of time",
14230                            GE_PLAYER);
14231                 else
14232                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14233             } else {
14234                 DisplayError(_("Your opponent is not out of time"), 0);
14235             }
14236             break;
14237           case MachinePlaysBlack:
14238             if (blackFlag) {
14239                 if (whiteFlag)
14240                   GameEnds(GameIsDrawn, "Both players ran out of time",
14241                            GE_PLAYER);
14242                 else
14243                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14244             } else {
14245                 DisplayError(_("Your opponent is not out of time"), 0);
14246             }
14247             break;
14248         }
14249     }
14250 }
14251
14252 void
14253 ClockClick (int which)
14254 {       // [HGM] code moved to back-end from winboard.c
14255         if(which) { // black clock
14256           if (gameMode == EditPosition || gameMode == IcsExamining) {
14257             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14258             SetBlackToPlayEvent();
14259           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14260           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14261           } else if (shiftKey) {
14262             AdjustClock(which, -1);
14263           } else if (gameMode == IcsPlayingWhite ||
14264                      gameMode == MachinePlaysBlack) {
14265             CallFlagEvent();
14266           }
14267         } else { // white clock
14268           if (gameMode == EditPosition || gameMode == IcsExamining) {
14269             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14270             SetWhiteToPlayEvent();
14271           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14272           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14273           } else if (shiftKey) {
14274             AdjustClock(which, -1);
14275           } else if (gameMode == IcsPlayingBlack ||
14276                    gameMode == MachinePlaysWhite) {
14277             CallFlagEvent();
14278           }
14279         }
14280 }
14281
14282 void
14283 DrawEvent ()
14284 {
14285     /* Offer draw or accept pending draw offer from opponent */
14286
14287     if (appData.icsActive) {
14288         /* Note: tournament rules require draw offers to be
14289            made after you make your move but before you punch
14290            your clock.  Currently ICS doesn't let you do that;
14291            instead, you immediately punch your clock after making
14292            a move, but you can offer a draw at any time. */
14293
14294         SendToICS(ics_prefix);
14295         SendToICS("draw\n");
14296         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14297     } else if (cmailMsgLoaded) {
14298         if (currentMove == cmailOldMove &&
14299             commentList[cmailOldMove] != NULL &&
14300             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14301                    "Black offers a draw" : "White offers a draw")) {
14302             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14303             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14304         } else if (currentMove == cmailOldMove + 1) {
14305             char *offer = WhiteOnMove(cmailOldMove) ?
14306               "White offers a draw" : "Black offers a draw";
14307             AppendComment(currentMove, offer, TRUE);
14308             DisplayComment(currentMove - 1, offer);
14309             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14310         } else {
14311             DisplayError(_("You must make your move before offering a draw"), 0);
14312             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14313         }
14314     } else if (first.offeredDraw) {
14315         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14316     } else {
14317         if (first.sendDrawOffers) {
14318             SendToProgram("draw\n", &first);
14319             userOfferedDraw = TRUE;
14320         }
14321     }
14322 }
14323
14324 void
14325 AdjournEvent ()
14326 {
14327     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14328
14329     if (appData.icsActive) {
14330         SendToICS(ics_prefix);
14331         SendToICS("adjourn\n");
14332     } else {
14333         /* Currently GNU Chess doesn't offer or accept Adjourns */
14334     }
14335 }
14336
14337
14338 void
14339 AbortEvent ()
14340 {
14341     /* Offer Abort or accept pending Abort offer from opponent */
14342
14343     if (appData.icsActive) {
14344         SendToICS(ics_prefix);
14345         SendToICS("abort\n");
14346     } else {
14347         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14348     }
14349 }
14350
14351 void
14352 ResignEvent ()
14353 {
14354     /* Resign.  You can do this even if it's not your turn. */
14355
14356     if (appData.icsActive) {
14357         SendToICS(ics_prefix);
14358         SendToICS("resign\n");
14359     } else {
14360         switch (gameMode) {
14361           case MachinePlaysWhite:
14362             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14363             break;
14364           case MachinePlaysBlack:
14365             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14366             break;
14367           case EditGame:
14368             if (cmailMsgLoaded) {
14369                 TruncateGame();
14370                 if (WhiteOnMove(cmailOldMove)) {
14371                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14372                 } else {
14373                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14374                 }
14375                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14376             }
14377             break;
14378           default:
14379             break;
14380         }
14381     }
14382 }
14383
14384
14385 void
14386 StopObservingEvent ()
14387 {
14388     /* Stop observing current games */
14389     SendToICS(ics_prefix);
14390     SendToICS("unobserve\n");
14391 }
14392
14393 void
14394 StopExaminingEvent ()
14395 {
14396     /* Stop observing current game */
14397     SendToICS(ics_prefix);
14398     SendToICS("unexamine\n");
14399 }
14400
14401 void
14402 ForwardInner (int target)
14403 {
14404     int limit; int oldSeekGraphUp = seekGraphUp;
14405
14406     if (appData.debugMode)
14407         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14408                 target, currentMove, forwardMostMove);
14409
14410     if (gameMode == EditPosition)
14411       return;
14412
14413     seekGraphUp = FALSE;
14414     MarkTargetSquares(1);
14415
14416     if (gameMode == PlayFromGameFile && !pausing)
14417       PauseEvent();
14418
14419     if (gameMode == IcsExamining && pausing)
14420       limit = pauseExamForwardMostMove;
14421     else
14422       limit = forwardMostMove;
14423
14424     if (target > limit) target = limit;
14425
14426     if (target > 0 && moveList[target - 1][0]) {
14427         int fromX, fromY, toX, toY;
14428         toX = moveList[target - 1][2] - AAA;
14429         toY = moveList[target - 1][3] - ONE;
14430         if (moveList[target - 1][1] == '@') {
14431             if (appData.highlightLastMove) {
14432                 SetHighlights(-1, -1, toX, toY);
14433             }
14434         } else {
14435             fromX = moveList[target - 1][0] - AAA;
14436             fromY = moveList[target - 1][1] - ONE;
14437             if (target == currentMove + 1) {
14438                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14439             }
14440             if (appData.highlightLastMove) {
14441                 SetHighlights(fromX, fromY, toX, toY);
14442             }
14443         }
14444     }
14445     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14446         gameMode == Training || gameMode == PlayFromGameFile ||
14447         gameMode == AnalyzeFile) {
14448         while (currentMove < target) {
14449             SendMoveToProgram(currentMove++, &first);
14450         }
14451     } else {
14452         currentMove = target;
14453     }
14454
14455     if (gameMode == EditGame || gameMode == EndOfGame) {
14456         whiteTimeRemaining = timeRemaining[0][currentMove];
14457         blackTimeRemaining = timeRemaining[1][currentMove];
14458     }
14459     DisplayBothClocks();
14460     DisplayMove(currentMove - 1);
14461     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14462     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14463     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14464         DisplayComment(currentMove - 1, commentList[currentMove]);
14465     }
14466     ClearMap(); // [HGM] exclude: invalidate map
14467 }
14468
14469
14470 void
14471 ForwardEvent ()
14472 {
14473     if (gameMode == IcsExamining && !pausing) {
14474         SendToICS(ics_prefix);
14475         SendToICS("forward\n");
14476     } else {
14477         ForwardInner(currentMove + 1);
14478     }
14479 }
14480
14481 void
14482 ToEndEvent ()
14483 {
14484     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14485         /* to optimze, we temporarily turn off analysis mode while we feed
14486          * the remaining moves to the engine. Otherwise we get analysis output
14487          * after each move.
14488          */
14489         if (first.analysisSupport) {
14490           SendToProgram("exit\nforce\n", &first);
14491           first.analyzing = FALSE;
14492         }
14493     }
14494
14495     if (gameMode == IcsExamining && !pausing) {
14496         SendToICS(ics_prefix);
14497         SendToICS("forward 999999\n");
14498     } else {
14499         ForwardInner(forwardMostMove);
14500     }
14501
14502     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14503         /* we have fed all the moves, so reactivate analysis mode */
14504         SendToProgram("analyze\n", &first);
14505         first.analyzing = TRUE;
14506         /*first.maybeThinking = TRUE;*/
14507         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14508     }
14509 }
14510
14511 void
14512 BackwardInner (int target)
14513 {
14514     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14515
14516     if (appData.debugMode)
14517         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14518                 target, currentMove, forwardMostMove);
14519
14520     if (gameMode == EditPosition) return;
14521     seekGraphUp = FALSE;
14522     MarkTargetSquares(1);
14523     if (currentMove <= backwardMostMove) {
14524         ClearHighlights();
14525         DrawPosition(full_redraw, boards[currentMove]);
14526         return;
14527     }
14528     if (gameMode == PlayFromGameFile && !pausing)
14529       PauseEvent();
14530
14531     if (moveList[target][0]) {
14532         int fromX, fromY, toX, toY;
14533         toX = moveList[target][2] - AAA;
14534         toY = moveList[target][3] - ONE;
14535         if (moveList[target][1] == '@') {
14536             if (appData.highlightLastMove) {
14537                 SetHighlights(-1, -1, toX, toY);
14538             }
14539         } else {
14540             fromX = moveList[target][0] - AAA;
14541             fromY = moveList[target][1] - ONE;
14542             if (target == currentMove - 1) {
14543                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14544             }
14545             if (appData.highlightLastMove) {
14546                 SetHighlights(fromX, fromY, toX, toY);
14547             }
14548         }
14549     }
14550     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14551         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14552         while (currentMove > target) {
14553             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14554                 // null move cannot be undone. Reload program with move history before it.
14555                 int i;
14556                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14557                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14558                 }
14559                 SendBoard(&first, i); 
14560                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14561                 break;
14562             }
14563             SendToProgram("undo\n", &first);
14564             currentMove--;
14565         }
14566     } else {
14567         currentMove = target;
14568     }
14569
14570     if (gameMode == EditGame || gameMode == EndOfGame) {
14571         whiteTimeRemaining = timeRemaining[0][currentMove];
14572         blackTimeRemaining = timeRemaining[1][currentMove];
14573     }
14574     DisplayBothClocks();
14575     DisplayMove(currentMove - 1);
14576     DrawPosition(full_redraw, boards[currentMove]);
14577     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14578     // [HGM] PV info: routine tests if comment empty
14579     DisplayComment(currentMove - 1, commentList[currentMove]);
14580     ClearMap(); // [HGM] exclude: invalidate map
14581 }
14582
14583 void
14584 BackwardEvent ()
14585 {
14586     if (gameMode == IcsExamining && !pausing) {
14587         SendToICS(ics_prefix);
14588         SendToICS("backward\n");
14589     } else {
14590         BackwardInner(currentMove - 1);
14591     }
14592 }
14593
14594 void
14595 ToStartEvent ()
14596 {
14597     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14598         /* to optimize, we temporarily turn off analysis mode while we undo
14599          * all the moves. Otherwise we get analysis output after each undo.
14600          */
14601         if (first.analysisSupport) {
14602           SendToProgram("exit\nforce\n", &first);
14603           first.analyzing = FALSE;
14604         }
14605     }
14606
14607     if (gameMode == IcsExamining && !pausing) {
14608         SendToICS(ics_prefix);
14609         SendToICS("backward 999999\n");
14610     } else {
14611         BackwardInner(backwardMostMove);
14612     }
14613
14614     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14615         /* we have fed all the moves, so reactivate analysis mode */
14616         SendToProgram("analyze\n", &first);
14617         first.analyzing = TRUE;
14618         /*first.maybeThinking = TRUE;*/
14619         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14620     }
14621 }
14622
14623 void
14624 ToNrEvent (int to)
14625 {
14626   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14627   if (to >= forwardMostMove) to = forwardMostMove;
14628   if (to <= backwardMostMove) to = backwardMostMove;
14629   if (to < currentMove) {
14630     BackwardInner(to);
14631   } else {
14632     ForwardInner(to);
14633   }
14634 }
14635
14636 void
14637 RevertEvent (Boolean annotate)
14638 {
14639     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14640         return;
14641     }
14642     if (gameMode != IcsExamining) {
14643         DisplayError(_("You are not examining a game"), 0);
14644         return;
14645     }
14646     if (pausing) {
14647         DisplayError(_("You can't revert while pausing"), 0);
14648         return;
14649     }
14650     SendToICS(ics_prefix);
14651     SendToICS("revert\n");
14652 }
14653
14654 void
14655 RetractMoveEvent ()
14656 {
14657     switch (gameMode) {
14658       case MachinePlaysWhite:
14659       case MachinePlaysBlack:
14660         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14661             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14662             return;
14663         }
14664         if (forwardMostMove < 2) return;
14665         currentMove = forwardMostMove = forwardMostMove - 2;
14666         whiteTimeRemaining = timeRemaining[0][currentMove];
14667         blackTimeRemaining = timeRemaining[1][currentMove];
14668         DisplayBothClocks();
14669         DisplayMove(currentMove - 1);
14670         ClearHighlights();/*!! could figure this out*/
14671         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14672         SendToProgram("remove\n", &first);
14673         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14674         break;
14675
14676       case BeginningOfGame:
14677       default:
14678         break;
14679
14680       case IcsPlayingWhite:
14681       case IcsPlayingBlack:
14682         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14683             SendToICS(ics_prefix);
14684             SendToICS("takeback 2\n");
14685         } else {
14686             SendToICS(ics_prefix);
14687             SendToICS("takeback 1\n");
14688         }
14689         break;
14690     }
14691 }
14692
14693 void
14694 MoveNowEvent ()
14695 {
14696     ChessProgramState *cps;
14697
14698     switch (gameMode) {
14699       case MachinePlaysWhite:
14700         if (!WhiteOnMove(forwardMostMove)) {
14701             DisplayError(_("It is your turn"), 0);
14702             return;
14703         }
14704         cps = &first;
14705         break;
14706       case MachinePlaysBlack:
14707         if (WhiteOnMove(forwardMostMove)) {
14708             DisplayError(_("It is your turn"), 0);
14709             return;
14710         }
14711         cps = &first;
14712         break;
14713       case TwoMachinesPlay:
14714         if (WhiteOnMove(forwardMostMove) ==
14715             (first.twoMachinesColor[0] == 'w')) {
14716             cps = &first;
14717         } else {
14718             cps = &second;
14719         }
14720         break;
14721       case BeginningOfGame:
14722       default:
14723         return;
14724     }
14725     SendToProgram("?\n", cps);
14726 }
14727
14728 void
14729 TruncateGameEvent ()
14730 {
14731     EditGameEvent();
14732     if (gameMode != EditGame) return;
14733     TruncateGame();
14734 }
14735
14736 void
14737 TruncateGame ()
14738 {
14739     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14740     if (forwardMostMove > currentMove) {
14741         if (gameInfo.resultDetails != NULL) {
14742             free(gameInfo.resultDetails);
14743             gameInfo.resultDetails = NULL;
14744             gameInfo.result = GameUnfinished;
14745         }
14746         forwardMostMove = currentMove;
14747         HistorySet(parseList, backwardMostMove, forwardMostMove,
14748                    currentMove-1);
14749     }
14750 }
14751
14752 void
14753 HintEvent ()
14754 {
14755     if (appData.noChessProgram) return;
14756     switch (gameMode) {
14757       case MachinePlaysWhite:
14758         if (WhiteOnMove(forwardMostMove)) {
14759             DisplayError(_("Wait until your turn"), 0);
14760             return;
14761         }
14762         break;
14763       case BeginningOfGame:
14764       case MachinePlaysBlack:
14765         if (!WhiteOnMove(forwardMostMove)) {
14766             DisplayError(_("Wait until your turn"), 0);
14767             return;
14768         }
14769         break;
14770       default:
14771         DisplayError(_("No hint available"), 0);
14772         return;
14773     }
14774     SendToProgram("hint\n", &first);
14775     hintRequested = TRUE;
14776 }
14777
14778 void
14779 BookEvent ()
14780 {
14781     if (appData.noChessProgram) return;
14782     switch (gameMode) {
14783       case MachinePlaysWhite:
14784         if (WhiteOnMove(forwardMostMove)) {
14785             DisplayError(_("Wait until your turn"), 0);
14786             return;
14787         }
14788         break;
14789       case BeginningOfGame:
14790       case MachinePlaysBlack:
14791         if (!WhiteOnMove(forwardMostMove)) {
14792             DisplayError(_("Wait until your turn"), 0);
14793             return;
14794         }
14795         break;
14796       case EditPosition:
14797         EditPositionDone(TRUE);
14798         break;
14799       case TwoMachinesPlay:
14800         return;
14801       default:
14802         break;
14803     }
14804     SendToProgram("bk\n", &first);
14805     bookOutput[0] = NULLCHAR;
14806     bookRequested = TRUE;
14807 }
14808
14809 void
14810 AboutGameEvent ()
14811 {
14812     char *tags = PGNTags(&gameInfo);
14813     TagsPopUp(tags, CmailMsg());
14814     free(tags);
14815 }
14816
14817 /* end button procedures */
14818
14819 void
14820 PrintPosition (FILE *fp, int move)
14821 {
14822     int i, j;
14823
14824     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14825         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14826             char c = PieceToChar(boards[move][i][j]);
14827             fputc(c == 'x' ? '.' : c, fp);
14828             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14829         }
14830     }
14831     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14832       fprintf(fp, "white to play\n");
14833     else
14834       fprintf(fp, "black to play\n");
14835 }
14836
14837 void
14838 PrintOpponents (FILE *fp)
14839 {
14840     if (gameInfo.white != NULL) {
14841         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14842     } else {
14843         fprintf(fp, "\n");
14844     }
14845 }
14846
14847 /* Find last component of program's own name, using some heuristics */
14848 void
14849 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14850 {
14851     char *p, *q, c;
14852     int local = (strcmp(host, "localhost") == 0);
14853     while (!local && (p = strchr(prog, ';')) != NULL) {
14854         p++;
14855         while (*p == ' ') p++;
14856         prog = p;
14857     }
14858     if (*prog == '"' || *prog == '\'') {
14859         q = strchr(prog + 1, *prog);
14860     } else {
14861         q = strchr(prog, ' ');
14862     }
14863     if (q == NULL) q = prog + strlen(prog);
14864     p = q;
14865     while (p >= prog && *p != '/' && *p != '\\') p--;
14866     p++;
14867     if(p == prog && *p == '"') p++;
14868     c = *q; *q = 0;
14869     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14870     memcpy(buf, p, q - p);
14871     buf[q - p] = NULLCHAR;
14872     if (!local) {
14873         strcat(buf, "@");
14874         strcat(buf, host);
14875     }
14876 }
14877
14878 char *
14879 TimeControlTagValue ()
14880 {
14881     char buf[MSG_SIZ];
14882     if (!appData.clockMode) {
14883       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14884     } else if (movesPerSession > 0) {
14885       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14886     } else if (timeIncrement == 0) {
14887       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14888     } else {
14889       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14890     }
14891     return StrSave(buf);
14892 }
14893
14894 void
14895 SetGameInfo ()
14896 {
14897     /* This routine is used only for certain modes */
14898     VariantClass v = gameInfo.variant;
14899     ChessMove r = GameUnfinished;
14900     char *p = NULL;
14901
14902     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14903         r = gameInfo.result;
14904         p = gameInfo.resultDetails;
14905         gameInfo.resultDetails = NULL;
14906     }
14907     ClearGameInfo(&gameInfo);
14908     gameInfo.variant = v;
14909
14910     switch (gameMode) {
14911       case MachinePlaysWhite:
14912         gameInfo.event = StrSave( appData.pgnEventHeader );
14913         gameInfo.site = StrSave(HostName());
14914         gameInfo.date = PGNDate();
14915         gameInfo.round = StrSave("-");
14916         gameInfo.white = StrSave(first.tidy);
14917         gameInfo.black = StrSave(UserName());
14918         gameInfo.timeControl = TimeControlTagValue();
14919         break;
14920
14921       case MachinePlaysBlack:
14922         gameInfo.event = StrSave( appData.pgnEventHeader );
14923         gameInfo.site = StrSave(HostName());
14924         gameInfo.date = PGNDate();
14925         gameInfo.round = StrSave("-");
14926         gameInfo.white = StrSave(UserName());
14927         gameInfo.black = StrSave(first.tidy);
14928         gameInfo.timeControl = TimeControlTagValue();
14929         break;
14930
14931       case TwoMachinesPlay:
14932         gameInfo.event = StrSave( appData.pgnEventHeader );
14933         gameInfo.site = StrSave(HostName());
14934         gameInfo.date = PGNDate();
14935         if (roundNr > 0) {
14936             char buf[MSG_SIZ];
14937             snprintf(buf, MSG_SIZ, "%d", roundNr);
14938             gameInfo.round = StrSave(buf);
14939         } else {
14940             gameInfo.round = StrSave("-");
14941         }
14942         if (first.twoMachinesColor[0] == 'w') {
14943             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14944             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14945         } else {
14946             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14947             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14948         }
14949         gameInfo.timeControl = TimeControlTagValue();
14950         break;
14951
14952       case EditGame:
14953         gameInfo.event = StrSave("Edited game");
14954         gameInfo.site = StrSave(HostName());
14955         gameInfo.date = PGNDate();
14956         gameInfo.round = StrSave("-");
14957         gameInfo.white = StrSave("-");
14958         gameInfo.black = StrSave("-");
14959         gameInfo.result = r;
14960         gameInfo.resultDetails = p;
14961         break;
14962
14963       case EditPosition:
14964         gameInfo.event = StrSave("Edited position");
14965         gameInfo.site = StrSave(HostName());
14966         gameInfo.date = PGNDate();
14967         gameInfo.round = StrSave("-");
14968         gameInfo.white = StrSave("-");
14969         gameInfo.black = StrSave("-");
14970         break;
14971
14972       case IcsPlayingWhite:
14973       case IcsPlayingBlack:
14974       case IcsObserving:
14975       case IcsExamining:
14976         break;
14977
14978       case PlayFromGameFile:
14979         gameInfo.event = StrSave("Game from non-PGN file");
14980         gameInfo.site = StrSave(HostName());
14981         gameInfo.date = PGNDate();
14982         gameInfo.round = StrSave("-");
14983         gameInfo.white = StrSave("?");
14984         gameInfo.black = StrSave("?");
14985         break;
14986
14987       default:
14988         break;
14989     }
14990 }
14991
14992 void
14993 ReplaceComment (int index, char *text)
14994 {
14995     int len;
14996     char *p;
14997     float score;
14998
14999     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
15000        pvInfoList[index-1].depth == len &&
15001        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
15002        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
15003     while (*text == '\n') text++;
15004     len = strlen(text);
15005     while (len > 0 && text[len - 1] == '\n') len--;
15006
15007     if (commentList[index] != NULL)
15008       free(commentList[index]);
15009
15010     if (len == 0) {
15011         commentList[index] = NULL;
15012         return;
15013     }
15014   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
15015       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
15016       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
15017     commentList[index] = (char *) malloc(len + 2);
15018     strncpy(commentList[index], text, len);
15019     commentList[index][len] = '\n';
15020     commentList[index][len + 1] = NULLCHAR;
15021   } else {
15022     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
15023     char *p;
15024     commentList[index] = (char *) malloc(len + 7);
15025     safeStrCpy(commentList[index], "{\n", 3);
15026     safeStrCpy(commentList[index]+2, text, len+1);
15027     commentList[index][len+2] = NULLCHAR;
15028     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
15029     strcat(commentList[index], "\n}\n");
15030   }
15031 }
15032
15033 void
15034 CrushCRs (char *text)
15035 {
15036   char *p = text;
15037   char *q = text;
15038   char ch;
15039
15040   do {
15041     ch = *p++;
15042     if (ch == '\r') continue;
15043     *q++ = ch;
15044   } while (ch != '\0');
15045 }
15046
15047 void
15048 AppendComment (int index, char *text, Boolean addBraces)
15049 /* addBraces  tells if we should add {} */
15050 {
15051     int oldlen, len;
15052     char *old;
15053
15054 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
15055     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
15056
15057     CrushCRs(text);
15058     while (*text == '\n') text++;
15059     len = strlen(text);
15060     while (len > 0 && text[len - 1] == '\n') len--;
15061     text[len] = NULLCHAR;
15062
15063     if (len == 0) return;
15064
15065     if (commentList[index] != NULL) {
15066       Boolean addClosingBrace = addBraces;
15067         old = commentList[index];
15068         oldlen = strlen(old);
15069         while(commentList[index][oldlen-1] ==  '\n')
15070           commentList[index][--oldlen] = NULLCHAR;
15071         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
15072         safeStrCpy(commentList[index], old, oldlen + len + 6);
15073         free(old);
15074         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
15075         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
15076           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
15077           while (*text == '\n') { text++; len--; }
15078           commentList[index][--oldlen] = NULLCHAR;
15079       }
15080         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
15081         else          strcat(commentList[index], "\n");
15082         strcat(commentList[index], text);
15083         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
15084         else          strcat(commentList[index], "\n");
15085     } else {
15086         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
15087         if(addBraces)
15088           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
15089         else commentList[index][0] = NULLCHAR;
15090         strcat(commentList[index], text);
15091         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
15092         if(addBraces == TRUE) strcat(commentList[index], "}\n");
15093     }
15094 }
15095
15096 static char *
15097 FindStr (char * text, char * sub_text)
15098 {
15099     char * result = strstr( text, sub_text );
15100
15101     if( result != NULL ) {
15102         result += strlen( sub_text );
15103     }
15104
15105     return result;
15106 }
15107
15108 /* [AS] Try to extract PV info from PGN comment */
15109 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
15110 char *
15111 GetInfoFromComment (int index, char * text)
15112 {
15113     char * sep = text, *p;
15114
15115     if( text != NULL && index > 0 ) {
15116         int score = 0;
15117         int depth = 0;
15118         int time = -1, sec = 0, deci;
15119         char * s_eval = FindStr( text, "[%eval " );
15120         char * s_emt = FindStr( text, "[%emt " );
15121
15122         if( s_eval != NULL || s_emt != NULL ) {
15123             /* New style */
15124             char delim;
15125
15126             if( s_eval != NULL ) {
15127                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
15128                     return text;
15129                 }
15130
15131                 if( delim != ']' ) {
15132                     return text;
15133                 }
15134             }
15135
15136             if( s_emt != NULL ) {
15137             }
15138                 return text;
15139         }
15140         else {
15141             /* We expect something like: [+|-]nnn.nn/dd */
15142             int score_lo = 0;
15143
15144             if(*text != '{') return text; // [HGM] braces: must be normal comment
15145
15146             sep = strchr( text, '/' );
15147             if( sep == NULL || sep < (text+4) ) {
15148                 return text;
15149             }
15150
15151             p = text;
15152             if(p[1] == '(') { // comment starts with PV
15153                p = strchr(p, ')'); // locate end of PV
15154                if(p == NULL || sep < p+5) return text;
15155                // at this point we have something like "{(.*) +0.23/6 ..."
15156                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
15157                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
15158                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
15159             }
15160             time = -1; sec = -1; deci = -1;
15161             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15162                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15163                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15164                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15165                 return text;
15166             }
15167
15168             if( score_lo < 0 || score_lo >= 100 ) {
15169                 return text;
15170             }
15171
15172             if(sec >= 0) time = 600*time + 10*sec; else
15173             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15174
15175             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15176
15177             /* [HGM] PV time: now locate end of PV info */
15178             while( *++sep >= '0' && *sep <= '9'); // strip depth
15179             if(time >= 0)
15180             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15181             if(sec >= 0)
15182             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15183             if(deci >= 0)
15184             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15185             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15186         }
15187
15188         if( depth <= 0 ) {
15189             return text;
15190         }
15191
15192         if( time < 0 ) {
15193             time = -1;
15194         }
15195
15196         pvInfoList[index-1].depth = depth;
15197         pvInfoList[index-1].score = score;
15198         pvInfoList[index-1].time  = 10*time; // centi-sec
15199         if(*sep == '}') *sep = 0; else *--sep = '{';
15200         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15201     }
15202     return sep;
15203 }
15204
15205 void
15206 SendToProgram (char *message, ChessProgramState *cps)
15207 {
15208     int count, outCount, error;
15209     char buf[MSG_SIZ];
15210
15211     if (cps->pr == NoProc) return;
15212     Attention(cps);
15213
15214     if (appData.debugMode) {
15215         TimeMark now;
15216         GetTimeMark(&now);
15217         fprintf(debugFP, "%ld >%-6s: %s",
15218                 SubtractTimeMarks(&now, &programStartTime),
15219                 cps->which, message);
15220         if(serverFP)
15221             fprintf(serverFP, "%ld >%-6s: %s",
15222                 SubtractTimeMarks(&now, &programStartTime),
15223                 cps->which, message), fflush(serverFP);
15224     }
15225
15226     count = strlen(message);
15227     outCount = OutputToProcess(cps->pr, message, count, &error);
15228     if (outCount < count && !exiting
15229                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15230       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15231       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15232         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15233             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15234                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15235                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15236                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15237             } else {
15238                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15239                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15240                 gameInfo.result = res;
15241             }
15242             gameInfo.resultDetails = StrSave(buf);
15243         }
15244         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15245         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15246     }
15247 }
15248
15249 void
15250 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15251 {
15252     char *end_str;
15253     char buf[MSG_SIZ];
15254     ChessProgramState *cps = (ChessProgramState *)closure;
15255
15256     if (isr != cps->isr) return; /* Killed intentionally */
15257     if (count <= 0) {
15258         if (count == 0) {
15259             RemoveInputSource(cps->isr);
15260             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15261             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15262                     _(cps->which), cps->program);
15263         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15264                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15265                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15266                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15267                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15268                 } else {
15269                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15270                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15271                     gameInfo.result = res;
15272                 }
15273                 gameInfo.resultDetails = StrSave(buf);
15274             }
15275             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15276             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15277         } else {
15278             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15279                     _(cps->which), cps->program);
15280             RemoveInputSource(cps->isr);
15281
15282             /* [AS] Program is misbehaving badly... kill it */
15283             if( count == -2 ) {
15284                 DestroyChildProcess( cps->pr, 9 );
15285                 cps->pr = NoProc;
15286             }
15287
15288             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15289         }
15290         return;
15291     }
15292
15293     if ((end_str = strchr(message, '\r')) != NULL)
15294       *end_str = NULLCHAR;
15295     if ((end_str = strchr(message, '\n')) != NULL)
15296       *end_str = NULLCHAR;
15297
15298     if (appData.debugMode) {
15299         TimeMark now; int print = 1;
15300         char *quote = ""; char c; int i;
15301
15302         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15303                 char start = message[0];
15304                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15305                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15306                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15307                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15308                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15309                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15310                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15311                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15312                    sscanf(message, "hint: %c", &c)!=1 && 
15313                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15314                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15315                     print = (appData.engineComments >= 2);
15316                 }
15317                 message[0] = start; // restore original message
15318         }
15319         if(print) {
15320                 GetTimeMark(&now);
15321                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15322                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15323                         quote,
15324                         message);
15325                 if(serverFP)
15326                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
15327                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15328                         quote,
15329                         message), fflush(serverFP);
15330         }
15331     }
15332
15333     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15334     if (appData.icsEngineAnalyze) {
15335         if (strstr(message, "whisper") != NULL ||
15336              strstr(message, "kibitz") != NULL ||
15337             strstr(message, "tellics") != NULL) return;
15338     }
15339
15340     HandleMachineMove(message, cps);
15341 }
15342
15343
15344 void
15345 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15346 {
15347     char buf[MSG_SIZ];
15348     int seconds;
15349
15350     if( timeControl_2 > 0 ) {
15351         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15352             tc = timeControl_2;
15353         }
15354     }
15355     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15356     inc /= cps->timeOdds;
15357     st  /= cps->timeOdds;
15358
15359     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15360
15361     if (st > 0) {
15362       /* Set exact time per move, normally using st command */
15363       if (cps->stKludge) {
15364         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15365         seconds = st % 60;
15366         if (seconds == 0) {
15367           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15368         } else {
15369           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15370         }
15371       } else {
15372         snprintf(buf, MSG_SIZ, "st %d\n", st);
15373       }
15374     } else {
15375       /* Set conventional or incremental time control, using level command */
15376       if (seconds == 0) {
15377         /* Note old gnuchess bug -- minutes:seconds used to not work.
15378            Fixed in later versions, but still avoid :seconds
15379            when seconds is 0. */
15380         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15381       } else {
15382         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15383                  seconds, inc/1000.);
15384       }
15385     }
15386     SendToProgram(buf, cps);
15387
15388     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15389     /* Orthogonally, limit search to given depth */
15390     if (sd > 0) {
15391       if (cps->sdKludge) {
15392         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15393       } else {
15394         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15395       }
15396       SendToProgram(buf, cps);
15397     }
15398
15399     if(cps->nps >= 0) { /* [HGM] nps */
15400         if(cps->supportsNPS == FALSE)
15401           cps->nps = -1; // don't use if engine explicitly says not supported!
15402         else {
15403           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15404           SendToProgram(buf, cps);
15405         }
15406     }
15407 }
15408
15409 ChessProgramState *
15410 WhitePlayer ()
15411 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15412 {
15413     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15414        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15415         return &second;
15416     return &first;
15417 }
15418
15419 void
15420 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15421 {
15422     char message[MSG_SIZ];
15423     long time, otime;
15424
15425     /* Note: this routine must be called when the clocks are stopped
15426        or when they have *just* been set or switched; otherwise
15427        it will be off by the time since the current tick started.
15428     */
15429     if (machineWhite) {
15430         time = whiteTimeRemaining / 10;
15431         otime = blackTimeRemaining / 10;
15432     } else {
15433         time = blackTimeRemaining / 10;
15434         otime = whiteTimeRemaining / 10;
15435     }
15436     /* [HGM] translate opponent's time by time-odds factor */
15437     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15438
15439     if (time <= 0) time = 1;
15440     if (otime <= 0) otime = 1;
15441
15442     snprintf(message, MSG_SIZ, "time %ld\n", time);
15443     SendToProgram(message, cps);
15444
15445     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15446     SendToProgram(message, cps);
15447 }
15448
15449 int
15450 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15451 {
15452   char buf[MSG_SIZ];
15453   int len = strlen(name);
15454   int val;
15455
15456   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15457     (*p) += len + 1;
15458     sscanf(*p, "%d", &val);
15459     *loc = (val != 0);
15460     while (**p && **p != ' ')
15461       (*p)++;
15462     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15463     SendToProgram(buf, cps);
15464     return TRUE;
15465   }
15466   return FALSE;
15467 }
15468
15469 int
15470 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15471 {
15472   char buf[MSG_SIZ];
15473   int len = strlen(name);
15474   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15475     (*p) += len + 1;
15476     sscanf(*p, "%d", loc);
15477     while (**p && **p != ' ') (*p)++;
15478     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15479     SendToProgram(buf, cps);
15480     return TRUE;
15481   }
15482   return FALSE;
15483 }
15484
15485 int
15486 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15487 {
15488   char buf[MSG_SIZ];
15489   int len = strlen(name);
15490   if (strncmp((*p), name, len) == 0
15491       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15492     (*p) += len + 2;
15493     sscanf(*p, "%[^\"]", loc);
15494     while (**p && **p != '\"') (*p)++;
15495     if (**p == '\"') (*p)++;
15496     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15497     SendToProgram(buf, cps);
15498     return TRUE;
15499   }
15500   return FALSE;
15501 }
15502
15503 int
15504 ParseOption (Option *opt, ChessProgramState *cps)
15505 // [HGM] options: process the string that defines an engine option, and determine
15506 // name, type, default value, and allowed value range
15507 {
15508         char *p, *q, buf[MSG_SIZ];
15509         int n, min = (-1)<<31, max = 1<<31, def;
15510
15511         if(p = strstr(opt->name, " -spin ")) {
15512             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15513             if(max < min) max = min; // enforce consistency
15514             if(def < min) def = min;
15515             if(def > max) def = max;
15516             opt->value = def;
15517             opt->min = min;
15518             opt->max = max;
15519             opt->type = Spin;
15520         } else if((p = strstr(opt->name, " -slider "))) {
15521             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15522             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15523             if(max < min) max = min; // enforce consistency
15524             if(def < min) def = min;
15525             if(def > max) def = max;
15526             opt->value = def;
15527             opt->min = min;
15528             opt->max = max;
15529             opt->type = Spin; // Slider;
15530         } else if((p = strstr(opt->name, " -string "))) {
15531             opt->textValue = p+9;
15532             opt->type = TextBox;
15533         } else if((p = strstr(opt->name, " -file "))) {
15534             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15535             opt->textValue = p+7;
15536             opt->type = FileName; // FileName;
15537         } else if((p = strstr(opt->name, " -path "))) {
15538             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15539             opt->textValue = p+7;
15540             opt->type = PathName; // PathName;
15541         } else if(p = strstr(opt->name, " -check ")) {
15542             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15543             opt->value = (def != 0);
15544             opt->type = CheckBox;
15545         } else if(p = strstr(opt->name, " -combo ")) {
15546             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
15547             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15548             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15549             opt->value = n = 0;
15550             while(q = StrStr(q, " /// ")) {
15551                 n++; *q = 0;    // count choices, and null-terminate each of them
15552                 q += 5;
15553                 if(*q == '*') { // remember default, which is marked with * prefix
15554                     q++;
15555                     opt->value = n;
15556                 }
15557                 cps->comboList[cps->comboCnt++] = q;
15558             }
15559             cps->comboList[cps->comboCnt++] = NULL;
15560             opt->max = n + 1;
15561             opt->type = ComboBox;
15562         } else if(p = strstr(opt->name, " -button")) {
15563             opt->type = Button;
15564         } else if(p = strstr(opt->name, " -save")) {
15565             opt->type = SaveButton;
15566         } else return FALSE;
15567         *p = 0; // terminate option name
15568         // now look if the command-line options define a setting for this engine option.
15569         if(cps->optionSettings && cps->optionSettings[0])
15570             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15571         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15572           snprintf(buf, MSG_SIZ, "option %s", p);
15573                 if(p = strstr(buf, ",")) *p = 0;
15574                 if(q = strchr(buf, '=')) switch(opt->type) {
15575                     case ComboBox:
15576                         for(n=0; n<opt->max; n++)
15577                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15578                         break;
15579                     case TextBox:
15580                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15581                         break;
15582                     case Spin:
15583                     case CheckBox:
15584                         opt->value = atoi(q+1);
15585                     default:
15586                         break;
15587                 }
15588                 strcat(buf, "\n");
15589                 SendToProgram(buf, cps);
15590         }
15591         return TRUE;
15592 }
15593
15594 void
15595 FeatureDone (ChessProgramState *cps, int val)
15596 {
15597   DelayedEventCallback cb = GetDelayedEvent();
15598   if ((cb == InitBackEnd3 && cps == &first) ||
15599       (cb == SettingsMenuIfReady && cps == &second) ||
15600       (cb == LoadEngine) ||
15601       (cb == TwoMachinesEventIfReady)) {
15602     CancelDelayedEvent();
15603     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15604   }
15605   cps->initDone = val;
15606 }
15607
15608 /* Parse feature command from engine */
15609 void
15610 ParseFeatures (char *args, ChessProgramState *cps)
15611 {
15612   char *p = args;
15613   char *q;
15614   int val;
15615   char buf[MSG_SIZ];
15616
15617   for (;;) {
15618     while (*p == ' ') p++;
15619     if (*p == NULLCHAR) return;
15620
15621     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15622     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15623     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15624     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15625     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15626     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15627     if (BoolFeature(&p, "reuse", &val, cps)) {
15628       /* Engine can disable reuse, but can't enable it if user said no */
15629       if (!val) cps->reuse = FALSE;
15630       continue;
15631     }
15632     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15633     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15634       if (gameMode == TwoMachinesPlay) {
15635         DisplayTwoMachinesTitle();
15636       } else {
15637         DisplayTitle("");
15638       }
15639       continue;
15640     }
15641     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15642     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15643     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15644     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15645     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15646     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15647     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
15648     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15649     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15650     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15651     if (IntFeature(&p, "done", &val, cps)) {
15652       FeatureDone(cps, val);
15653       continue;
15654     }
15655     /* Added by Tord: */
15656     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15657     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15658     /* End of additions by Tord */
15659
15660     /* [HGM] added features: */
15661     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15662     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15663     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15664     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15665     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15666     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15667     if (StringFeature(&p, "option", buf, cps)) {
15668         FREE(cps->option[cps->nrOptions].name);
15669         cps->option[cps->nrOptions].name = malloc(MSG_SIZ);
15670         safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ);
15671         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15672           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15673             SendToProgram(buf, cps);
15674             continue;
15675         }
15676         if(cps->nrOptions >= MAX_OPTIONS) {
15677             cps->nrOptions--;
15678             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15679             DisplayError(buf, 0);
15680         }
15681         continue;
15682     }
15683     /* End of additions by HGM */
15684
15685     /* unknown feature: complain and skip */
15686     q = p;
15687     while (*q && *q != '=') q++;
15688     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15689     SendToProgram(buf, cps);
15690     p = q;
15691     if (*p == '=') {
15692       p++;
15693       if (*p == '\"') {
15694         p++;
15695         while (*p && *p != '\"') p++;
15696         if (*p == '\"') p++;
15697       } else {
15698         while (*p && *p != ' ') p++;
15699       }
15700     }
15701   }
15702
15703 }
15704
15705 void
15706 PeriodicUpdatesEvent (int newState)
15707 {
15708     if (newState == appData.periodicUpdates)
15709       return;
15710
15711     appData.periodicUpdates=newState;
15712
15713     /* Display type changes, so update it now */
15714 //    DisplayAnalysis();
15715
15716     /* Get the ball rolling again... */
15717     if (newState) {
15718         AnalysisPeriodicEvent(1);
15719         StartAnalysisClock();
15720     }
15721 }
15722
15723 void
15724 PonderNextMoveEvent (int newState)
15725 {
15726     if (newState == appData.ponderNextMove) return;
15727     if (gameMode == EditPosition) EditPositionDone(TRUE);
15728     if (newState) {
15729         SendToProgram("hard\n", &first);
15730         if (gameMode == TwoMachinesPlay) {
15731             SendToProgram("hard\n", &second);
15732         }
15733     } else {
15734         SendToProgram("easy\n", &first);
15735         thinkOutput[0] = NULLCHAR;
15736         if (gameMode == TwoMachinesPlay) {
15737             SendToProgram("easy\n", &second);
15738         }
15739     }
15740     appData.ponderNextMove = newState;
15741 }
15742
15743 void
15744 NewSettingEvent (int option, int *feature, char *command, int value)
15745 {
15746     char buf[MSG_SIZ];
15747
15748     if (gameMode == EditPosition) EditPositionDone(TRUE);
15749     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15750     if(feature == NULL || *feature) SendToProgram(buf, &first);
15751     if (gameMode == TwoMachinesPlay) {
15752         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15753     }
15754 }
15755
15756 void
15757 ShowThinkingEvent ()
15758 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15759 {
15760     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15761     int newState = appData.showThinking
15762         // [HGM] thinking: other features now need thinking output as well
15763         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15764
15765     if (oldState == newState) return;
15766     oldState = newState;
15767     if (gameMode == EditPosition) EditPositionDone(TRUE);
15768     if (oldState) {
15769         SendToProgram("post\n", &first);
15770         if (gameMode == TwoMachinesPlay) {
15771             SendToProgram("post\n", &second);
15772         }
15773     } else {
15774         SendToProgram("nopost\n", &first);
15775         thinkOutput[0] = NULLCHAR;
15776         if (gameMode == TwoMachinesPlay) {
15777             SendToProgram("nopost\n", &second);
15778         }
15779     }
15780 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15781 }
15782
15783 void
15784 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15785 {
15786   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15787   if (pr == NoProc) return;
15788   AskQuestion(title, question, replyPrefix, pr);
15789 }
15790
15791 void
15792 TypeInEvent (char firstChar)
15793 {
15794     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15795         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15796         gameMode == AnalyzeMode || gameMode == EditGame || 
15797         gameMode == EditPosition || gameMode == IcsExamining ||
15798         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15799         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15800                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15801                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15802         gameMode == Training) PopUpMoveDialog(firstChar);
15803 }
15804
15805 void
15806 TypeInDoneEvent (char *move)
15807 {
15808         Board board;
15809         int n, fromX, fromY, toX, toY;
15810         char promoChar;
15811         ChessMove moveType;
15812
15813         // [HGM] FENedit
15814         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15815                 EditPositionPasteFEN(move);
15816                 return;
15817         }
15818         // [HGM] movenum: allow move number to be typed in any mode
15819         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15820           ToNrEvent(2*n-1);
15821           return;
15822         }
15823         // undocumented kludge: allow command-line option to be typed in!
15824         // (potentially fatal, and does not implement the effect of the option.)
15825         // should only be used for options that are values on which future decisions will be made,
15826         // and definitely not on options that would be used during initialization.
15827         if(strstr(move, "!!! -") == move) {
15828             ParseArgsFromString(move+4);
15829             return;
15830         }
15831
15832       if (gameMode != EditGame && currentMove != forwardMostMove && 
15833         gameMode != Training) {
15834         DisplayMoveError(_("Displayed move is not current"));
15835       } else {
15836         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15837           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15838         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15839         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15840           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15841           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15842         } else {
15843           DisplayMoveError(_("Could not parse move"));
15844         }
15845       }
15846 }
15847
15848 void
15849 DisplayMove (int moveNumber)
15850 {
15851     char message[MSG_SIZ];
15852     char res[MSG_SIZ];
15853     char cpThinkOutput[MSG_SIZ];
15854
15855     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15856
15857     if (moveNumber == forwardMostMove - 1 ||
15858         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15859
15860         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15861
15862         if (strchr(cpThinkOutput, '\n')) {
15863             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15864         }
15865     } else {
15866         *cpThinkOutput = NULLCHAR;
15867     }
15868
15869     /* [AS] Hide thinking from human user */
15870     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15871         *cpThinkOutput = NULLCHAR;
15872         if( thinkOutput[0] != NULLCHAR ) {
15873             int i;
15874
15875             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15876                 cpThinkOutput[i] = '.';
15877             }
15878             cpThinkOutput[i] = NULLCHAR;
15879             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15880         }
15881     }
15882
15883     if (moveNumber == forwardMostMove - 1 &&
15884         gameInfo.resultDetails != NULL) {
15885         if (gameInfo.resultDetails[0] == NULLCHAR) {
15886           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15887         } else {
15888           snprintf(res, MSG_SIZ, " {%s} %s",
15889                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15890         }
15891     } else {
15892         res[0] = NULLCHAR;
15893     }
15894
15895     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15896         DisplayMessage(res, cpThinkOutput);
15897     } else {
15898       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15899                 WhiteOnMove(moveNumber) ? " " : ".. ",
15900                 parseList[moveNumber], res);
15901         DisplayMessage(message, cpThinkOutput);
15902     }
15903 }
15904
15905 void
15906 DisplayComment (int moveNumber, char *text)
15907 {
15908     char title[MSG_SIZ];
15909
15910     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15911       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15912     } else {
15913       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15914               WhiteOnMove(moveNumber) ? " " : ".. ",
15915               parseList[moveNumber]);
15916     }
15917     if (text != NULL && (appData.autoDisplayComment || commentUp))
15918         CommentPopUp(title, text);
15919 }
15920
15921 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15922  * might be busy thinking or pondering.  It can be omitted if your
15923  * gnuchess is configured to stop thinking immediately on any user
15924  * input.  However, that gnuchess feature depends on the FIONREAD
15925  * ioctl, which does not work properly on some flavors of Unix.
15926  */
15927 void
15928 Attention (ChessProgramState *cps)
15929 {
15930 #if ATTENTION
15931     if (!cps->useSigint) return;
15932     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15933     switch (gameMode) {
15934       case MachinePlaysWhite:
15935       case MachinePlaysBlack:
15936       case TwoMachinesPlay:
15937       case IcsPlayingWhite:
15938       case IcsPlayingBlack:
15939       case AnalyzeMode:
15940       case AnalyzeFile:
15941         /* Skip if we know it isn't thinking */
15942         if (!cps->maybeThinking) return;
15943         if (appData.debugMode)
15944           fprintf(debugFP, "Interrupting %s\n", cps->which);
15945         InterruptChildProcess(cps->pr);
15946         cps->maybeThinking = FALSE;
15947         break;
15948       default:
15949         break;
15950     }
15951 #endif /*ATTENTION*/
15952 }
15953
15954 int
15955 CheckFlags ()
15956 {
15957     if (whiteTimeRemaining <= 0) {
15958         if (!whiteFlag) {
15959             whiteFlag = TRUE;
15960             if (appData.icsActive) {
15961                 if (appData.autoCallFlag &&
15962                     gameMode == IcsPlayingBlack && !blackFlag) {
15963                   SendToICS(ics_prefix);
15964                   SendToICS("flag\n");
15965                 }
15966             } else {
15967                 if (blackFlag) {
15968                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15969                 } else {
15970                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15971                     if (appData.autoCallFlag) {
15972                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15973                         return TRUE;
15974                     }
15975                 }
15976             }
15977         }
15978     }
15979     if (blackTimeRemaining <= 0) {
15980         if (!blackFlag) {
15981             blackFlag = TRUE;
15982             if (appData.icsActive) {
15983                 if (appData.autoCallFlag &&
15984                     gameMode == IcsPlayingWhite && !whiteFlag) {
15985                   SendToICS(ics_prefix);
15986                   SendToICS("flag\n");
15987                 }
15988             } else {
15989                 if (whiteFlag) {
15990                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15991                 } else {
15992                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15993                     if (appData.autoCallFlag) {
15994                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15995                         return TRUE;
15996                     }
15997                 }
15998             }
15999         }
16000     }
16001     return FALSE;
16002 }
16003
16004 void
16005 CheckTimeControl ()
16006 {
16007     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
16008         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
16009
16010     /*
16011      * add time to clocks when time control is achieved ([HGM] now also used for increment)
16012      */
16013     if ( !WhiteOnMove(forwardMostMove) ) {
16014         /* White made time control */
16015         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
16016         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
16017         /* [HGM] time odds: correct new time quota for time odds! */
16018                                             / WhitePlayer()->timeOdds;
16019         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
16020     } else {
16021         lastBlack -= blackTimeRemaining;
16022         /* Black made time control */
16023         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
16024                                             / WhitePlayer()->other->timeOdds;
16025         lastWhite = whiteTimeRemaining;
16026     }
16027 }
16028
16029 void
16030 DisplayBothClocks ()
16031 {
16032     int wom = gameMode == EditPosition ?
16033       !blackPlaysFirst : WhiteOnMove(currentMove);
16034     DisplayWhiteClock(whiteTimeRemaining, wom);
16035     DisplayBlackClock(blackTimeRemaining, !wom);
16036 }
16037
16038
16039 /* Timekeeping seems to be a portability nightmare.  I think everyone
16040    has ftime(), but I'm really not sure, so I'm including some ifdefs
16041    to use other calls if you don't.  Clocks will be less accurate if
16042    you have neither ftime nor gettimeofday.
16043 */
16044
16045 /* VS 2008 requires the #include outside of the function */
16046 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
16047 #include <sys/timeb.h>
16048 #endif
16049
16050 /* Get the current time as a TimeMark */
16051 void
16052 GetTimeMark (TimeMark *tm)
16053 {
16054 #if HAVE_GETTIMEOFDAY
16055
16056     struct timeval timeVal;
16057     struct timezone timeZone;
16058
16059     gettimeofday(&timeVal, &timeZone);
16060     tm->sec = (long) timeVal.tv_sec;
16061     tm->ms = (int) (timeVal.tv_usec / 1000L);
16062
16063 #else /*!HAVE_GETTIMEOFDAY*/
16064 #if HAVE_FTIME
16065
16066 // include <sys/timeb.h> / moved to just above start of function
16067     struct timeb timeB;
16068
16069     ftime(&timeB);
16070     tm->sec = (long) timeB.time;
16071     tm->ms = (int) timeB.millitm;
16072
16073 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
16074     tm->sec = (long) time(NULL);
16075     tm->ms = 0;
16076 #endif
16077 #endif
16078 }
16079
16080 /* Return the difference in milliseconds between two
16081    time marks.  We assume the difference will fit in a long!
16082 */
16083 long
16084 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
16085 {
16086     return 1000L*(tm2->sec - tm1->sec) +
16087            (long) (tm2->ms - tm1->ms);
16088 }
16089
16090
16091 /*
16092  * Code to manage the game clocks.
16093  *
16094  * In tournament play, black starts the clock and then white makes a move.
16095  * We give the human user a slight advantage if he is playing white---the
16096  * clocks don't run until he makes his first move, so it takes zero time.
16097  * Also, we don't account for network lag, so we could get out of sync
16098  * with GNU Chess's clock -- but then, referees are always right.
16099  */
16100
16101 static TimeMark tickStartTM;
16102 static long intendedTickLength;
16103
16104 long
16105 NextTickLength (long timeRemaining)
16106 {
16107     long nominalTickLength, nextTickLength;
16108
16109     if (timeRemaining > 0L && timeRemaining <= 10000L)
16110       nominalTickLength = 100L;
16111     else
16112       nominalTickLength = 1000L;
16113     nextTickLength = timeRemaining % nominalTickLength;
16114     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
16115
16116     return nextTickLength;
16117 }
16118
16119 /* Adjust clock one minute up or down */
16120 void
16121 AdjustClock (Boolean which, int dir)
16122 {
16123     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
16124     if(which) blackTimeRemaining += 60000*dir;
16125     else      whiteTimeRemaining += 60000*dir;
16126     DisplayBothClocks();
16127     adjustedClock = TRUE;
16128 }
16129
16130 /* Stop clocks and reset to a fresh time control */
16131 void
16132 ResetClocks ()
16133 {
16134     (void) StopClockTimer();
16135     if (appData.icsActive) {
16136         whiteTimeRemaining = blackTimeRemaining = 0;
16137     } else if (searchTime) {
16138         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16139         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16140     } else { /* [HGM] correct new time quote for time odds */
16141         whiteTC = blackTC = fullTimeControlString;
16142         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16143         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16144     }
16145     if (whiteFlag || blackFlag) {
16146         DisplayTitle("");
16147         whiteFlag = blackFlag = FALSE;
16148     }
16149     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16150     DisplayBothClocks();
16151     adjustedClock = FALSE;
16152 }
16153
16154 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16155
16156 /* Decrement running clock by amount of time that has passed */
16157 void
16158 DecrementClocks ()
16159 {
16160     long timeRemaining;
16161     long lastTickLength, fudge;
16162     TimeMark now;
16163
16164     if (!appData.clockMode) return;
16165     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16166
16167     GetTimeMark(&now);
16168
16169     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16170
16171     /* Fudge if we woke up a little too soon */
16172     fudge = intendedTickLength - lastTickLength;
16173     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16174
16175     if (WhiteOnMove(forwardMostMove)) {
16176         if(whiteNPS >= 0) lastTickLength = 0;
16177         timeRemaining = whiteTimeRemaining -= lastTickLength;
16178         if(timeRemaining < 0 && !appData.icsActive) {
16179             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16180             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16181                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16182                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16183             }
16184         }
16185         DisplayWhiteClock(whiteTimeRemaining - fudge,
16186                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16187     } else {
16188         if(blackNPS >= 0) lastTickLength = 0;
16189         timeRemaining = blackTimeRemaining -= lastTickLength;
16190         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16191             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16192             if(suddenDeath) {
16193                 blackStartMove = forwardMostMove;
16194                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16195             }
16196         }
16197         DisplayBlackClock(blackTimeRemaining - fudge,
16198                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16199     }
16200     if (CheckFlags()) return;
16201
16202     tickStartTM = now;
16203     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16204     StartClockTimer(intendedTickLength);
16205
16206     /* if the time remaining has fallen below the alarm threshold, sound the
16207      * alarm. if the alarm has sounded and (due to a takeback or time control
16208      * with increment) the time remaining has increased to a level above the
16209      * threshold, reset the alarm so it can sound again.
16210      */
16211
16212     if (appData.icsActive && appData.icsAlarm) {
16213
16214         /* make sure we are dealing with the user's clock */
16215         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16216                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16217            )) return;
16218
16219         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16220             alarmSounded = FALSE;
16221         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16222             PlayAlarmSound();
16223             alarmSounded = TRUE;
16224         }
16225     }
16226 }
16227
16228
16229 /* A player has just moved, so stop the previously running
16230    clock and (if in clock mode) start the other one.
16231    We redisplay both clocks in case we're in ICS mode, because
16232    ICS gives us an update to both clocks after every move.
16233    Note that this routine is called *after* forwardMostMove
16234    is updated, so the last fractional tick must be subtracted
16235    from the color that is *not* on move now.
16236 */
16237 void
16238 SwitchClocks (int newMoveNr)
16239 {
16240     long lastTickLength;
16241     TimeMark now;
16242     int flagged = FALSE;
16243
16244     GetTimeMark(&now);
16245
16246     if (StopClockTimer() && appData.clockMode) {
16247         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16248         if (!WhiteOnMove(forwardMostMove)) {
16249             if(blackNPS >= 0) lastTickLength = 0;
16250             blackTimeRemaining -= lastTickLength;
16251            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16252 //         if(pvInfoList[forwardMostMove].time == -1)
16253                  pvInfoList[forwardMostMove].time =               // use GUI time
16254                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16255         } else {
16256            if(whiteNPS >= 0) lastTickLength = 0;
16257            whiteTimeRemaining -= lastTickLength;
16258            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16259 //         if(pvInfoList[forwardMostMove].time == -1)
16260                  pvInfoList[forwardMostMove].time =
16261                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16262         }
16263         flagged = CheckFlags();
16264     }
16265     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16266     CheckTimeControl();
16267
16268     if (flagged || !appData.clockMode) return;
16269
16270     switch (gameMode) {
16271       case MachinePlaysBlack:
16272       case MachinePlaysWhite:
16273       case BeginningOfGame:
16274         if (pausing) return;
16275         break;
16276
16277       case EditGame:
16278       case PlayFromGameFile:
16279       case IcsExamining:
16280         return;
16281
16282       default:
16283         break;
16284     }
16285
16286     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16287         if(WhiteOnMove(forwardMostMove))
16288              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16289         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16290     }
16291
16292     tickStartTM = now;
16293     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16294       whiteTimeRemaining : blackTimeRemaining);
16295     StartClockTimer(intendedTickLength);
16296 }
16297
16298
16299 /* Stop both clocks */
16300 void
16301 StopClocks ()
16302 {
16303     long lastTickLength;
16304     TimeMark now;
16305
16306     if (!StopClockTimer()) return;
16307     if (!appData.clockMode) return;
16308
16309     GetTimeMark(&now);
16310
16311     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16312     if (WhiteOnMove(forwardMostMove)) {
16313         if(whiteNPS >= 0) lastTickLength = 0;
16314         whiteTimeRemaining -= lastTickLength;
16315         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16316     } else {
16317         if(blackNPS >= 0) lastTickLength = 0;
16318         blackTimeRemaining -= lastTickLength;
16319         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16320     }
16321     CheckFlags();
16322 }
16323
16324 /* Start clock of player on move.  Time may have been reset, so
16325    if clock is already running, stop and restart it. */
16326 void
16327 StartClocks ()
16328 {
16329     (void) StopClockTimer(); /* in case it was running already */
16330     DisplayBothClocks();
16331     if (CheckFlags()) return;
16332
16333     if (!appData.clockMode) return;
16334     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16335
16336     GetTimeMark(&tickStartTM);
16337     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16338       whiteTimeRemaining : blackTimeRemaining);
16339
16340    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16341     whiteNPS = blackNPS = -1;
16342     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16343        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16344         whiteNPS = first.nps;
16345     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16346        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16347         blackNPS = first.nps;
16348     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16349         whiteNPS = second.nps;
16350     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16351         blackNPS = second.nps;
16352     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16353
16354     StartClockTimer(intendedTickLength);
16355 }
16356
16357 char *
16358 TimeString (long ms)
16359 {
16360     long second, minute, hour, day;
16361     char *sign = "";
16362     static char buf[32];
16363
16364     if (ms > 0 && ms <= 9900) {
16365       /* convert milliseconds to tenths, rounding up */
16366       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16367
16368       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16369       return buf;
16370     }
16371
16372     /* convert milliseconds to seconds, rounding up */
16373     /* use floating point to avoid strangeness of integer division
16374        with negative dividends on many machines */
16375     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16376
16377     if (second < 0) {
16378         sign = "-";
16379         second = -second;
16380     }
16381
16382     day = second / (60 * 60 * 24);
16383     second = second % (60 * 60 * 24);
16384     hour = second / (60 * 60);
16385     second = second % (60 * 60);
16386     minute = second / 60;
16387     second = second % 60;
16388
16389     if (day > 0)
16390       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16391               sign, day, hour, minute, second);
16392     else if (hour > 0)
16393       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16394     else
16395       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16396
16397     return buf;
16398 }
16399
16400
16401 /*
16402  * This is necessary because some C libraries aren't ANSI C compliant yet.
16403  */
16404 char *
16405 StrStr (char *string, char *match)
16406 {
16407     int i, length;
16408
16409     length = strlen(match);
16410
16411     for (i = strlen(string) - length; i >= 0; i--, string++)
16412       if (!strncmp(match, string, length))
16413         return string;
16414
16415     return NULL;
16416 }
16417
16418 char *
16419 StrCaseStr (char *string, char *match)
16420 {
16421     int i, j, length;
16422
16423     length = strlen(match);
16424
16425     for (i = strlen(string) - length; i >= 0; i--, string++) {
16426         for (j = 0; j < length; j++) {
16427             if (ToLower(match[j]) != ToLower(string[j]))
16428               break;
16429         }
16430         if (j == length) return string;
16431     }
16432
16433     return NULL;
16434 }
16435
16436 #ifndef _amigados
16437 int
16438 StrCaseCmp (char *s1, char *s2)
16439 {
16440     char c1, c2;
16441
16442     for (;;) {
16443         c1 = ToLower(*s1++);
16444         c2 = ToLower(*s2++);
16445         if (c1 > c2) return 1;
16446         if (c1 < c2) return -1;
16447         if (c1 == NULLCHAR) return 0;
16448     }
16449 }
16450
16451
16452 int
16453 ToLower (int c)
16454 {
16455     return isupper(c) ? tolower(c) : c;
16456 }
16457
16458
16459 int
16460 ToUpper (int c)
16461 {
16462     return islower(c) ? toupper(c) : c;
16463 }
16464 #endif /* !_amigados    */
16465
16466 char *
16467 StrSave (char *s)
16468 {
16469   char *ret;
16470
16471   if ((ret = (char *) malloc(strlen(s) + 1)))
16472     {
16473       safeStrCpy(ret, s, strlen(s)+1);
16474     }
16475   return ret;
16476 }
16477
16478 char *
16479 StrSavePtr (char *s, char **savePtr)
16480 {
16481     if (*savePtr) {
16482         free(*savePtr);
16483     }
16484     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16485       safeStrCpy(*savePtr, s, strlen(s)+1);
16486     }
16487     return(*savePtr);
16488 }
16489
16490 char *
16491 PGNDate ()
16492 {
16493     time_t clock;
16494     struct tm *tm;
16495     char buf[MSG_SIZ];
16496
16497     clock = time((time_t *)NULL);
16498     tm = localtime(&clock);
16499     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16500             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16501     return StrSave(buf);
16502 }
16503
16504
16505 char *
16506 PositionToFEN (int move, char *overrideCastling)
16507 {
16508     int i, j, fromX, fromY, toX, toY;
16509     int whiteToPlay;
16510     char buf[MSG_SIZ];
16511     char *p, *q;
16512     int emptycount;
16513     ChessSquare piece;
16514
16515     whiteToPlay = (gameMode == EditPosition) ?
16516       !blackPlaysFirst : (move % 2 == 0);
16517     p = buf;
16518
16519     /* Piece placement data */
16520     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16521         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16522         emptycount = 0;
16523         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16524             if (boards[move][i][j] == EmptySquare) {
16525                 emptycount++;
16526             } else { ChessSquare piece = boards[move][i][j];
16527                 if (emptycount > 0) {
16528                     if(emptycount<10) /* [HGM] can be >= 10 */
16529                         *p++ = '0' + emptycount;
16530                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16531                     emptycount = 0;
16532                 }
16533                 if(PieceToChar(piece) == '+') {
16534                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16535                     *p++ = '+';
16536                     piece = (ChessSquare)(DEMOTED piece);
16537                 }
16538                 *p++ = PieceToChar(piece);
16539                 if(p[-1] == '~') {
16540                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16541                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16542                     *p++ = '~';
16543                 }
16544             }
16545         }
16546         if (emptycount > 0) {
16547             if(emptycount<10) /* [HGM] can be >= 10 */
16548                 *p++ = '0' + emptycount;
16549             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16550             emptycount = 0;
16551         }
16552         *p++ = '/';
16553     }
16554     *(p - 1) = ' ';
16555
16556     /* [HGM] print Crazyhouse or Shogi holdings */
16557     if( gameInfo.holdingsWidth ) {
16558         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16559         q = p;
16560         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16561             piece = boards[move][i][BOARD_WIDTH-1];
16562             if( piece != EmptySquare )
16563               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16564                   *p++ = PieceToChar(piece);
16565         }
16566         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16567             piece = boards[move][BOARD_HEIGHT-i-1][0];
16568             if( piece != EmptySquare )
16569               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16570                   *p++ = PieceToChar(piece);
16571         }
16572
16573         if( q == p ) *p++ = '-';
16574         *p++ = ']';
16575         *p++ = ' ';
16576     }
16577
16578     /* Active color */
16579     *p++ = whiteToPlay ? 'w' : 'b';
16580     *p++ = ' ';
16581
16582   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16583     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16584   } else {
16585   if(nrCastlingRights) {
16586      q = p;
16587      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16588        /* [HGM] write directly from rights */
16589            if(boards[move][CASTLING][2] != NoRights &&
16590               boards[move][CASTLING][0] != NoRights   )
16591                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16592            if(boards[move][CASTLING][2] != NoRights &&
16593               boards[move][CASTLING][1] != NoRights   )
16594                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16595            if(boards[move][CASTLING][5] != NoRights &&
16596               boards[move][CASTLING][3] != NoRights   )
16597                 *p++ = boards[move][CASTLING][3] + AAA;
16598            if(boards[move][CASTLING][5] != NoRights &&
16599               boards[move][CASTLING][4] != NoRights   )
16600                 *p++ = boards[move][CASTLING][4] + AAA;
16601      } else {
16602
16603         /* [HGM] write true castling rights */
16604         if( nrCastlingRights == 6 ) {
16605             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16606                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16607             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16608                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16609             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16610                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16611             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16612                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16613         }
16614      }
16615      if (q == p) *p++ = '-'; /* No castling rights */
16616      *p++ = ' ';
16617   }
16618
16619   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16620      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16621     /* En passant target square */
16622     if (move > backwardMostMove) {
16623         fromX = moveList[move - 1][0] - AAA;
16624         fromY = moveList[move - 1][1] - ONE;
16625         toX = moveList[move - 1][2] - AAA;
16626         toY = moveList[move - 1][3] - ONE;
16627         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16628             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16629             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16630             fromX == toX) {
16631             /* 2-square pawn move just happened */
16632             *p++ = toX + AAA;
16633             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16634         } else {
16635             *p++ = '-';
16636         }
16637     } else if(move == backwardMostMove) {
16638         // [HGM] perhaps we should always do it like this, and forget the above?
16639         if((signed char)boards[move][EP_STATUS] >= 0) {
16640             *p++ = boards[move][EP_STATUS] + AAA;
16641             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16642         } else {
16643             *p++ = '-';
16644         }
16645     } else {
16646         *p++ = '-';
16647     }
16648     *p++ = ' ';
16649   }
16650   }
16651
16652     /* [HGM] find reversible plies */
16653     {   int i = 0, j=move;
16654
16655         if (appData.debugMode) { int k;
16656             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16657             for(k=backwardMostMove; k<=forwardMostMove; k++)
16658                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16659
16660         }
16661
16662         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16663         if( j == backwardMostMove ) i += initialRulePlies;
16664         sprintf(p, "%d ", i);
16665         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16666     }
16667     /* Fullmove number */
16668     sprintf(p, "%d", (move / 2) + 1);
16669
16670     return StrSave(buf);
16671 }
16672
16673 Boolean
16674 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16675 {
16676     int i, j;
16677     char *p, c;
16678     int emptycount;
16679     ChessSquare piece;
16680
16681     p = fen;
16682
16683     /* [HGM] by default clear Crazyhouse holdings, if present */
16684     if(gameInfo.holdingsWidth) {
16685        for(i=0; i<BOARD_HEIGHT; i++) {
16686            board[i][0]             = EmptySquare; /* black holdings */
16687            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16688            board[i][1]             = (ChessSquare) 0; /* black counts */
16689            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16690        }
16691     }
16692
16693     /* Piece placement data */
16694     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16695         j = 0;
16696         for (;;) {
16697             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16698                 if (*p == '/') p++;
16699                 emptycount = gameInfo.boardWidth - j;
16700                 while (emptycount--)
16701                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16702                 break;
16703 #if(BOARD_FILES >= 10)
16704             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16705                 p++; emptycount=10;
16706                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16707                 while (emptycount--)
16708                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16709 #endif
16710             } else if (isdigit(*p)) {
16711                 emptycount = *p++ - '0';
16712                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16713                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16714                 while (emptycount--)
16715                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16716             } else if (*p == '+' || isalpha(*p)) {
16717                 if (j >= gameInfo.boardWidth) return FALSE;
16718                 if(*p=='+') {
16719                     piece = CharToPiece(*++p);
16720                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16721                     piece = (ChessSquare) (PROMOTED piece ); p++;
16722                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16723                 } else piece = CharToPiece(*p++);
16724
16725                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16726                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16727                     piece = (ChessSquare) (PROMOTED piece);
16728                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16729                     p++;
16730                 }
16731                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16732             } else {
16733                 return FALSE;
16734             }
16735         }
16736     }
16737     while (*p == '/' || *p == ' ') p++;
16738
16739     /* [HGM] look for Crazyhouse holdings here */
16740     while(*p==' ') p++;
16741     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16742         if(*p == '[') p++;
16743         if(*p == '-' ) p++; /* empty holdings */ else {
16744             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16745             /* if we would allow FEN reading to set board size, we would   */
16746             /* have to add holdings and shift the board read so far here   */
16747             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16748                 p++;
16749                 if((int) piece >= (int) BlackPawn ) {
16750                     i = (int)piece - (int)BlackPawn;
16751                     i = PieceToNumber((ChessSquare)i);
16752                     if( i >= gameInfo.holdingsSize ) return FALSE;
16753                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16754                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16755                 } else {
16756                     i = (int)piece - (int)WhitePawn;
16757                     i = PieceToNumber((ChessSquare)i);
16758                     if( i >= gameInfo.holdingsSize ) return FALSE;
16759                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16760                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16761                 }
16762             }
16763         }
16764         if(*p == ']') p++;
16765     }
16766
16767     while(*p == ' ') p++;
16768
16769     /* Active color */
16770     c = *p++;
16771     if(appData.colorNickNames) {
16772       if( c == appData.colorNickNames[0] ) c = 'w'; else
16773       if( c == appData.colorNickNames[1] ) c = 'b';
16774     }
16775     switch (c) {
16776       case 'w':
16777         *blackPlaysFirst = FALSE;
16778         break;
16779       case 'b':
16780         *blackPlaysFirst = TRUE;
16781         break;
16782       default:
16783         return FALSE;
16784     }
16785
16786     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16787     /* return the extra info in global variiables             */
16788
16789     /* set defaults in case FEN is incomplete */
16790     board[EP_STATUS] = EP_UNKNOWN;
16791     for(i=0; i<nrCastlingRights; i++ ) {
16792         board[CASTLING][i] =
16793             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16794     }   /* assume possible unless obviously impossible */
16795     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16796     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16797     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16798                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16799     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16800     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16801     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16802                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16803     FENrulePlies = 0;
16804
16805     while(*p==' ') p++;
16806     if(nrCastlingRights) {
16807       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16808           /* castling indicator present, so default becomes no castlings */
16809           for(i=0; i<nrCastlingRights; i++ ) {
16810                  board[CASTLING][i] = NoRights;
16811           }
16812       }
16813       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16814              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16815              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16816              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16817         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16818
16819         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16820             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16821             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16822         }
16823         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16824             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16825         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16826                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16827         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16828                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16829         switch(c) {
16830           case'K':
16831               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16832               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16833               board[CASTLING][2] = whiteKingFile;
16834               break;
16835           case'Q':
16836               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16837               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16838               board[CASTLING][2] = whiteKingFile;
16839               break;
16840           case'k':
16841               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16842               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16843               board[CASTLING][5] = blackKingFile;
16844               break;
16845           case'q':
16846               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16847               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16848               board[CASTLING][5] = blackKingFile;
16849           case '-':
16850               break;
16851           default: /* FRC castlings */
16852               if(c >= 'a') { /* black rights */
16853                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16854                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16855                   if(i == BOARD_RGHT) break;
16856                   board[CASTLING][5] = i;
16857                   c -= AAA;
16858                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16859                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16860                   if(c > i)
16861                       board[CASTLING][3] = c;
16862                   else
16863                       board[CASTLING][4] = c;
16864               } else { /* white rights */
16865                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16866                     if(board[0][i] == WhiteKing) break;
16867                   if(i == BOARD_RGHT) break;
16868                   board[CASTLING][2] = i;
16869                   c -= AAA - 'a' + 'A';
16870                   if(board[0][c] >= WhiteKing) break;
16871                   if(c > i)
16872                       board[CASTLING][0] = c;
16873                   else
16874                       board[CASTLING][1] = c;
16875               }
16876         }
16877       }
16878       for(i=0; i<nrCastlingRights; i++)
16879         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16880     if (appData.debugMode) {
16881         fprintf(debugFP, "FEN castling rights:");
16882         for(i=0; i<nrCastlingRights; i++)
16883         fprintf(debugFP, " %d", board[CASTLING][i]);
16884         fprintf(debugFP, "\n");
16885     }
16886
16887       while(*p==' ') p++;
16888     }
16889
16890     /* read e.p. field in games that know e.p. capture */
16891     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16892        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16893       if(*p=='-') {
16894         p++; board[EP_STATUS] = EP_NONE;
16895       } else {
16896          char c = *p++ - AAA;
16897
16898          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16899          if(*p >= '0' && *p <='9') p++;
16900          board[EP_STATUS] = c;
16901       }
16902     }
16903
16904
16905     if(sscanf(p, "%d", &i) == 1) {
16906         FENrulePlies = i; /* 50-move ply counter */
16907         /* (The move number is still ignored)    */
16908     }
16909
16910     return TRUE;
16911 }
16912
16913 void
16914 EditPositionPasteFEN (char *fen)
16915 {
16916   if (fen != NULL) {
16917     Board initial_position;
16918
16919     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16920       DisplayError(_("Bad FEN position in clipboard"), 0);
16921       return ;
16922     } else {
16923       int savedBlackPlaysFirst = blackPlaysFirst;
16924       EditPositionEvent();
16925       blackPlaysFirst = savedBlackPlaysFirst;
16926       CopyBoard(boards[0], initial_position);
16927       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16928       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16929       DisplayBothClocks();
16930       DrawPosition(FALSE, boards[currentMove]);
16931     }
16932   }
16933 }
16934
16935 static char cseq[12] = "\\   ";
16936
16937 Boolean
16938 set_cont_sequence (char *new_seq)
16939 {
16940     int len;
16941     Boolean ret;
16942
16943     // handle bad attempts to set the sequence
16944         if (!new_seq)
16945                 return 0; // acceptable error - no debug
16946
16947     len = strlen(new_seq);
16948     ret = (len > 0) && (len < sizeof(cseq));
16949     if (ret)
16950       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16951     else if (appData.debugMode)
16952       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16953     return ret;
16954 }
16955
16956 /*
16957     reformat a source message so words don't cross the width boundary.  internal
16958     newlines are not removed.  returns the wrapped size (no null character unless
16959     included in source message).  If dest is NULL, only calculate the size required
16960     for the dest buffer.  lp argument indicats line position upon entry, and it's
16961     passed back upon exit.
16962 */
16963 int
16964 wrap (char *dest, char *src, int count, int width, int *lp)
16965 {
16966     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16967
16968     cseq_len = strlen(cseq);
16969     old_line = line = *lp;
16970     ansi = len = clen = 0;
16971
16972     for (i=0; i < count; i++)
16973     {
16974         if (src[i] == '\033')
16975             ansi = 1;
16976
16977         // if we hit the width, back up
16978         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16979         {
16980             // store i & len in case the word is too long
16981             old_i = i, old_len = len;
16982
16983             // find the end of the last word
16984             while (i && src[i] != ' ' && src[i] != '\n')
16985             {
16986                 i--;
16987                 len--;
16988             }
16989
16990             // word too long?  restore i & len before splitting it
16991             if ((old_i-i+clen) >= width)
16992             {
16993                 i = old_i;
16994                 len = old_len;
16995             }
16996
16997             // extra space?
16998             if (i && src[i-1] == ' ')
16999                 len--;
17000
17001             if (src[i] != ' ' && src[i] != '\n')
17002             {
17003                 i--;
17004                 if (len)
17005                     len--;
17006             }
17007
17008             // now append the newline and continuation sequence
17009             if (dest)
17010                 dest[len] = '\n';
17011             len++;
17012             if (dest)
17013                 strncpy(dest+len, cseq, cseq_len);
17014             len += cseq_len;
17015             line = cseq_len;
17016             clen = cseq_len;
17017             continue;
17018         }
17019
17020         if (dest)
17021             dest[len] = src[i];
17022         len++;
17023         if (!ansi)
17024             line++;
17025         if (src[i] == '\n')
17026             line = 0;
17027         if (src[i] == 'm')
17028             ansi = 0;
17029     }
17030     if (dest && appData.debugMode)
17031     {
17032         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
17033             count, width, line, len, *lp);
17034         show_bytes(debugFP, src, count);
17035         fprintf(debugFP, "\ndest: ");
17036         show_bytes(debugFP, dest, len);
17037         fprintf(debugFP, "\n");
17038     }
17039     *lp = dest ? line : old_line;
17040
17041     return len;
17042 }
17043
17044 // [HGM] vari: routines for shelving variations
17045 Boolean modeRestore = FALSE;
17046
17047 void
17048 PushInner (int firstMove, int lastMove)
17049 {
17050         int i, j, nrMoves = lastMove - firstMove;
17051
17052         // push current tail of game on stack
17053         savedResult[storedGames] = gameInfo.result;
17054         savedDetails[storedGames] = gameInfo.resultDetails;
17055         gameInfo.resultDetails = NULL;
17056         savedFirst[storedGames] = firstMove;
17057         savedLast [storedGames] = lastMove;
17058         savedFramePtr[storedGames] = framePtr;
17059         framePtr -= nrMoves; // reserve space for the boards
17060         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
17061             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
17062             for(j=0; j<MOVE_LEN; j++)
17063                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
17064             for(j=0; j<2*MOVE_LEN; j++)
17065                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
17066             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
17067             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
17068             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
17069             pvInfoList[firstMove+i-1].depth = 0;
17070             commentList[framePtr+i] = commentList[firstMove+i];
17071             commentList[firstMove+i] = NULL;
17072         }
17073
17074         storedGames++;
17075         forwardMostMove = firstMove; // truncate game so we can start variation
17076 }
17077
17078 void
17079 PushTail (int firstMove, int lastMove)
17080 {
17081         if(appData.icsActive) { // only in local mode
17082                 forwardMostMove = currentMove; // mimic old ICS behavior
17083                 return;
17084         }
17085         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
17086
17087         PushInner(firstMove, lastMove);
17088         if(storedGames == 1) GreyRevert(FALSE);
17089         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
17090 }
17091
17092 void
17093 PopInner (Boolean annotate)
17094 {
17095         int i, j, nrMoves;
17096         char buf[8000], moveBuf[20];
17097
17098         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
17099         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
17100         nrMoves = savedLast[storedGames] - currentMove;
17101         if(annotate) {
17102                 int cnt = 10;
17103                 if(!WhiteOnMove(currentMove))
17104                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
17105                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
17106                 for(i=currentMove; i<forwardMostMove; i++) {
17107                         if(WhiteOnMove(i))
17108                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
17109                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
17110                         strcat(buf, moveBuf);
17111                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
17112                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
17113                 }
17114                 strcat(buf, ")");
17115         }
17116         for(i=1; i<=nrMoves; i++) { // copy last variation back
17117             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
17118             for(j=0; j<MOVE_LEN; j++)
17119                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
17120             for(j=0; j<2*MOVE_LEN; j++)
17121                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
17122             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
17123             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
17124             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
17125             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
17126             commentList[currentMove+i] = commentList[framePtr+i];
17127             commentList[framePtr+i] = NULL;
17128         }
17129         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17130         framePtr = savedFramePtr[storedGames];
17131         gameInfo.result = savedResult[storedGames];
17132         if(gameInfo.resultDetails != NULL) {
17133             free(gameInfo.resultDetails);
17134       }
17135         gameInfo.resultDetails = savedDetails[storedGames];
17136         forwardMostMove = currentMove + nrMoves;
17137 }
17138
17139 Boolean
17140 PopTail (Boolean annotate)
17141 {
17142         if(appData.icsActive) return FALSE; // only in local mode
17143         if(!storedGames) return FALSE; // sanity
17144         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17145
17146         PopInner(annotate);
17147         if(currentMove < forwardMostMove) ForwardEvent(); else
17148         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17149
17150         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17151         return TRUE;
17152 }
17153
17154 void
17155 CleanupTail ()
17156 {       // remove all shelved variations
17157         int i;
17158         for(i=0; i<storedGames; i++) {
17159             if(savedDetails[i])
17160                 free(savedDetails[i]);
17161             savedDetails[i] = NULL;
17162         }
17163         for(i=framePtr; i<MAX_MOVES; i++) {
17164                 if(commentList[i]) free(commentList[i]);
17165                 commentList[i] = NULL;
17166         }
17167         framePtr = MAX_MOVES-1;
17168         storedGames = 0;
17169 }
17170
17171 void
17172 LoadVariation (int index, char *text)
17173 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17174         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17175         int level = 0, move;
17176
17177         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17178         // first find outermost bracketing variation
17179         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17180             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17181                 if(*p == '{') wait = '}'; else
17182                 if(*p == '[') wait = ']'; else
17183                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17184                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17185             }
17186             if(*p == wait) wait = NULLCHAR; // closing ]} found
17187             p++;
17188         }
17189         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17190         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17191         end[1] = NULLCHAR; // clip off comment beyond variation
17192         ToNrEvent(currentMove-1);
17193         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17194         // kludge: use ParsePV() to append variation to game
17195         move = currentMove;
17196         ParsePV(start, TRUE, TRUE);
17197         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17198         ClearPremoveHighlights();
17199         CommentPopDown();
17200         ToNrEvent(currentMove+1);
17201 }
17202