Allow engine to force popup of its settings dialog
[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, 2013, 2014, 2015 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 #   ifdef ARC_64BIT
63 #       define EGBB_NAME "egbbdll64.dll"
64 #   else
65 #       define EGBB_NAME "egbbdll.dll"
66 #   endif
67
68 #else
69
70 #   include <sys/file.h>
71 #   define SLASH '/'
72
73 #   include <dlfcn.h>
74 #   ifdef ARC_64BIT
75 #       define EGBB_NAME "egbbso64.so"
76 #   else
77 #       define EGBB_NAME "egbbso.so"
78 #   endif
79     // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
80 #   define CDECL
81 #   define HMODULE void *
82 #   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
83 #   define GetProcAddress dlsym
84
85 #endif
86
87 #include "config.h"
88
89 #include <assert.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 #include <errno.h>
93 #include <sys/types.h>
94 #include <sys/stat.h>
95 #include <math.h>
96 #include <ctype.h>
97
98 #if STDC_HEADERS
99 # include <stdlib.h>
100 # include <string.h>
101 # include <stdarg.h>
102 #else /* not STDC_HEADERS */
103 # if HAVE_STRING_H
104 #  include <string.h>
105 # else /* not HAVE_STRING_H */
106 #  include <strings.h>
107 # endif /* not HAVE_STRING_H */
108 #endif /* not STDC_HEADERS */
109
110 #if HAVE_SYS_FCNTL_H
111 # include <sys/fcntl.h>
112 #else /* not HAVE_SYS_FCNTL_H */
113 # if HAVE_FCNTL_H
114 #  include <fcntl.h>
115 # endif /* HAVE_FCNTL_H */
116 #endif /* not HAVE_SYS_FCNTL_H */
117
118 #if TIME_WITH_SYS_TIME
119 # include <sys/time.h>
120 # include <time.h>
121 #else
122 # if HAVE_SYS_TIME_H
123 #  include <sys/time.h>
124 # else
125 #  include <time.h>
126 # endif
127 #endif
128
129 #if defined(_amigados) && !defined(__GNUC__)
130 struct timezone {
131     int tz_minuteswest;
132     int tz_dsttime;
133 };
134 extern int gettimeofday(struct timeval *, struct timezone *);
135 #endif
136
137 #if HAVE_UNISTD_H
138 # include <unistd.h>
139 #endif
140
141 #include "common.h"
142 #include "frontend.h"
143 #include "backend.h"
144 #include "parser.h"
145 #include "moves.h"
146 #if ZIPPY
147 # include "zippy.h"
148 #endif
149 #include "backendz.h"
150 #include "evalgraph.h"
151 #include "engineoutput.h"
152 #include "gettext.h"
153
154 #ifdef ENABLE_NLS
155 # define _(s) gettext (s)
156 # define N_(s) gettext_noop (s)
157 # define T_(s) gettext(s)
158 #else
159 # ifdef WIN32
160 #   define _(s) T_(s)
161 #   define N_(s) s
162 # else
163 #   define _(s) (s)
164 #   define N_(s) s
165 #   define T_(s) s
166 # endif
167 #endif
168
169
170 int establish P((void));
171 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
172                          char *buf, int count, int error));
173 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
174                       char *buf, int count, int error));
175 void SendToICS P((char *s));
176 void SendToICSDelayed P((char *s, long msdelay));
177 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
178 void HandleMachineMove P((char *message, ChessProgramState *cps));
179 int AutoPlayOneMove P((void));
180 int LoadGameOneMove P((ChessMove readAhead));
181 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
182 int LoadPositionFromFile P((char *filename, int n, char *title));
183 int SavePositionToFile P((char *filename));
184 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
185 void ShowMove P((int fromX, int fromY, int toX, int toY));
186 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
187                    /*char*/int promoChar));
188 void BackwardInner P((int target));
189 void ForwardInner P((int target));
190 int Adjudicate P((ChessProgramState *cps));
191 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
192 void EditPositionDone P((Boolean fakeRights));
193 void PrintOpponents P((FILE *fp));
194 void PrintPosition P((FILE *fp, int move));
195 void StartChessProgram P((ChessProgramState *cps));
196 void SendToProgram P((char *message, ChessProgramState *cps));
197 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
198 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
199                            char *buf, int count, int error));
200 void SendTimeControl P((ChessProgramState *cps,
201                         int mps, long tc, int inc, int sd, int st));
202 char *TimeControlTagValue P((void));
203 void Attention P((ChessProgramState *cps));
204 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
205 int ResurrectChessProgram P((void));
206 void DisplayComment P((int moveNumber, char *text));
207 void DisplayMove P((int moveNumber));
208
209 void ParseGameHistory P((char *game));
210 void ParseBoard12 P((char *string));
211 void KeepAlive P((void));
212 void StartClocks P((void));
213 void SwitchClocks P((int nr));
214 void StopClocks P((void));
215 void ResetClocks P((void));
216 char *PGNDate P((void));
217 void SetGameInfo P((void));
218 int RegisterMove P((void));
219 void MakeRegisteredMove P((void));
220 void TruncateGame P((void));
221 int looking_at P((char *, int *, char *));
222 void CopyPlayerNameIntoFileName P((char **, char *));
223 char *SavePart P((char *));
224 int SaveGameOldStyle P((FILE *));
225 int SaveGamePGN P((FILE *));
226 int CheckFlags P((void));
227 long NextTickLength P((long));
228 void CheckTimeControl P((void));
229 void show_bytes P((FILE *, char *, int));
230 int string_to_rating P((char *str));
231 void ParseFeatures P((char* args, ChessProgramState *cps));
232 void InitBackEnd3 P((void));
233 void FeatureDone P((ChessProgramState* cps, int val));
234 void InitChessProgram P((ChessProgramState *cps, int setup));
235 void OutputKibitz(int window, char *text);
236 int PerpetualChase(int first, int last);
237 int EngineOutputIsUp();
238 void InitDrawingSizes(int x, int y);
239 void NextMatchGame P((void));
240 int NextTourneyGame P((int nr, int *swap));
241 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
242 FILE *WriteTourneyFile P((char *results, FILE *f));
243 void DisplayTwoMachinesTitle P(());
244 static void ExcludeClick P((int index));
245 void ToggleSecond P((void));
246 void PauseEngine P((ChessProgramState *cps));
247 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
248
249 #ifdef WIN32
250        extern void ConsoleCreate();
251 #endif
252
253 ChessProgramState *WhitePlayer();
254 int VerifyDisplayMode P(());
255
256 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
257 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
258 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
259 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
260 void ics_update_width P((int new_width));
261 extern char installDir[MSG_SIZ];
262 VariantClass startVariant; /* [HGM] nicks: initial variant */
263 Boolean abortMatch;
264
265 extern int tinyLayout, smallLayout;
266 ChessProgramStats programStats;
267 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
268 int endPV = -1;
269 static int exiting = 0; /* [HGM] moved to top */
270 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
271 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
272 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
273 int partnerHighlight[2];
274 Boolean partnerBoardValid = 0;
275 char partnerStatus[MSG_SIZ];
276 Boolean partnerUp;
277 Boolean originalFlip;
278 Boolean twoBoards = 0;
279 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
280 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
281 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
282 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
283 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
284 int opponentKibitzes;
285 int lastSavedGame; /* [HGM] save: ID of game */
286 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
287 extern int chatCount;
288 int chattingPartner;
289 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
290 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
291 char lastMsg[MSG_SIZ];
292 char lastTalker[MSG_SIZ];
293 ChessSquare pieceSweep = EmptySquare;
294 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
295 int promoDefaultAltered;
296 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
297 static int initPing = -1;
298 int border;       /* [HGM] width of board rim, needed to size seek graph  */
299 char bestMove[MSG_SIZ];
300 int solvingTime, totalTime;
301
302 /* States for ics_getting_history */
303 #define H_FALSE 0
304 #define H_REQUESTED 1
305 #define H_GOT_REQ_HEADER 2
306 #define H_GOT_UNREQ_HEADER 3
307 #define H_GETTING_MOVES 4
308 #define H_GOT_UNWANTED_HEADER 5
309
310 /* whosays values for GameEnds */
311 #define GE_ICS 0
312 #define GE_ENGINE 1
313 #define GE_PLAYER 2
314 #define GE_FILE 3
315 #define GE_XBOARD 4
316 #define GE_ENGINE1 5
317 #define GE_ENGINE2 6
318
319 /* Maximum number of games in a cmail message */
320 #define CMAIL_MAX_GAMES 20
321
322 /* Different types of move when calling RegisterMove */
323 #define CMAIL_MOVE   0
324 #define CMAIL_RESIGN 1
325 #define CMAIL_DRAW   2
326 #define CMAIL_ACCEPT 3
327
328 /* Different types of result to remember for each game */
329 #define CMAIL_NOT_RESULT 0
330 #define CMAIL_OLD_RESULT 1
331 #define CMAIL_NEW_RESULT 2
332
333 /* Telnet protocol constants */
334 #define TN_WILL 0373
335 #define TN_WONT 0374
336 #define TN_DO   0375
337 #define TN_DONT 0376
338 #define TN_IAC  0377
339 #define TN_ECHO 0001
340 #define TN_SGA  0003
341 #define TN_PORT 23
342
343 char*
344 safeStrCpy (char *dst, const char *src, size_t count)
345 { // [HGM] made safe
346   int i;
347   assert( dst != NULL );
348   assert( src != NULL );
349   assert( count > 0 );
350
351   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
352   if(  i == count && dst[count-1] != NULLCHAR)
353     {
354       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
355       if(appData.debugMode)
356         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
357     }
358
359   return dst;
360 }
361
362 /* Some compiler can't cast u64 to double
363  * This function do the job for us:
364
365  * We use the highest bit for cast, this only
366  * works if the highest bit is not
367  * in use (This should not happen)
368  *
369  * We used this for all compiler
370  */
371 double
372 u64ToDouble (u64 value)
373 {
374   double r;
375   u64 tmp = value & u64Const(0x7fffffffffffffff);
376   r = (double)(s64)tmp;
377   if (value & u64Const(0x8000000000000000))
378        r +=  9.2233720368547758080e18; /* 2^63 */
379  return r;
380 }
381
382 /* Fake up flags for now, as we aren't keeping track of castling
383    availability yet. [HGM] Change of logic: the flag now only
384    indicates the type of castlings allowed by the rule of the game.
385    The actual rights themselves are maintained in the array
386    castlingRights, as part of the game history, and are not probed
387    by this function.
388  */
389 int
390 PosFlags (index)
391 {
392   int flags = F_ALL_CASTLE_OK;
393   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
394   switch (gameInfo.variant) {
395   case VariantSuicide:
396     flags &= ~F_ALL_CASTLE_OK;
397   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
398     flags |= F_IGNORE_CHECK;
399   case VariantLosers:
400     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
401     break;
402   case VariantAtomic:
403     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
404     break;
405   case VariantKriegspiel:
406     flags |= F_KRIEGSPIEL_CAPTURE;
407     break;
408   case VariantCapaRandom:
409   case VariantFischeRandom:
410     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
411   case VariantNoCastle:
412   case VariantShatranj:
413   case VariantCourier:
414   case VariantMakruk:
415   case VariantASEAN:
416   case VariantGrand:
417     flags &= ~F_ALL_CASTLE_OK;
418     break;
419   case VariantChu:
420   case VariantChuChess:
421   case VariantLion:
422     flags |= F_NULL_MOVE;
423     break;
424   default:
425     break;
426   }
427   if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
428   return flags;
429 }
430
431 FILE *gameFileFP, *debugFP, *serverFP;
432 char *currentDebugFile; // [HGM] debug split: to remember name
433
434 /*
435     [AS] Note: sometimes, the sscanf() function is used to parse the input
436     into a fixed-size buffer. Because of this, we must be prepared to
437     receive strings as long as the size of the input buffer, which is currently
438     set to 4K for Windows and 8K for the rest.
439     So, we must either allocate sufficiently large buffers here, or
440     reduce the size of the input buffer in the input reading part.
441 */
442
443 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
444 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
445 char thinkOutput1[MSG_SIZ*10];
446 char promoRestrict[MSG_SIZ];
447
448 ChessProgramState first, second, pairing;
449
450 /* premove variables */
451 int premoveToX = 0;
452 int premoveToY = 0;
453 int premoveFromX = 0;
454 int premoveFromY = 0;
455 int premovePromoChar = 0;
456 int gotPremove = 0;
457 Boolean alarmSounded;
458 /* end premove variables */
459
460 char *ics_prefix = "$";
461 enum ICS_TYPE ics_type = ICS_GENERIC;
462
463 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
464 int pauseExamForwardMostMove = 0;
465 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
466 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
467 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
468 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
469 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
470 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
471 int whiteFlag = FALSE, blackFlag = FALSE;
472 int userOfferedDraw = FALSE;
473 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
474 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
475 int cmailMoveType[CMAIL_MAX_GAMES];
476 long ics_clock_paused = 0;
477 ProcRef icsPR = NoProc, cmailPR = NoProc;
478 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
479 GameMode gameMode = BeginningOfGame;
480 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
481 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
482 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
483 int hiddenThinkOutputState = 0; /* [AS] */
484 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
485 int adjudicateLossPlies = 6;
486 char white_holding[64], black_holding[64];
487 TimeMark lastNodeCountTime;
488 long lastNodeCount=0;
489 int shiftKey, controlKey; // [HGM] set by mouse handler
490
491 int have_sent_ICS_logon = 0;
492 int movesPerSession;
493 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
494 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
495 Boolean adjustedClock;
496 long timeControl_2; /* [AS] Allow separate time controls */
497 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
498 long timeRemaining[2][MAX_MOVES];
499 int matchGame = 0, nextGame = 0, roundNr = 0;
500 Boolean waitingForGame = FALSE, startingEngine = FALSE;
501 TimeMark programStartTime, pauseStart;
502 char ics_handle[MSG_SIZ];
503 int have_set_title = 0;
504
505 /* animateTraining preserves the state of appData.animate
506  * when Training mode is activated. This allows the
507  * response to be animated when appData.animate == TRUE and
508  * appData.animateDragging == TRUE.
509  */
510 Boolean animateTraining;
511
512 GameInfo gameInfo;
513
514 AppData appData;
515
516 Board boards[MAX_MOVES];
517 /* [HGM] Following 7 needed for accurate legality tests: */
518 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
519 signed char  initialRights[BOARD_FILES];
520 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
521 int   initialRulePlies, FENrulePlies;
522 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
523 int loadFlag = 0;
524 Boolean shuffleOpenings;
525 int mute; // mute all sounds
526
527 // [HGM] vari: next 12 to save and restore variations
528 #define MAX_VARIATIONS 10
529 int framePtr = MAX_MOVES-1; // points to free stack entry
530 int storedGames = 0;
531 int savedFirst[MAX_VARIATIONS];
532 int savedLast[MAX_VARIATIONS];
533 int savedFramePtr[MAX_VARIATIONS];
534 char *savedDetails[MAX_VARIATIONS];
535 ChessMove savedResult[MAX_VARIATIONS];
536
537 void PushTail P((int firstMove, int lastMove));
538 Boolean PopTail P((Boolean annotate));
539 void PushInner P((int firstMove, int lastMove));
540 void PopInner P((Boolean annotate));
541 void CleanupTail P((void));
542
543 ChessSquare  FIDEArray[2][BOARD_FILES] = {
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
547         BlackKing, BlackBishop, BlackKnight, BlackRook }
548 };
549
550 ChessSquare twoKingsArray[2][BOARD_FILES] = {
551     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
552         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
554         BlackKing, BlackKing, BlackKnight, BlackRook }
555 };
556
557 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
558     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
559         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
560     { BlackRook, BlackMan, BlackBishop, BlackQueen,
561         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
562 };
563
564 ChessSquare SpartanArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
566         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
567     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
568         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
569 };
570
571 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
572     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
574     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
575         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
576 };
577
578 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
579     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
580         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
582         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 };
584
585 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
586     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
587         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackMan, BlackFerz,
589         BlackKing, BlackMan, BlackKnight, BlackRook }
590 };
591
592 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
593     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
594         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackMan, BlackFerz,
596         BlackKing, BlackMan, BlackKnight, BlackRook }
597 };
598
599 ChessSquare  lionArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
601         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackLion, BlackBishop, BlackQueen,
603         BlackKing, BlackBishop, BlackKnight, BlackRook }
604 };
605
606
607 #if (BOARD_FILES>=10)
608 ChessSquare ShogiArray[2][BOARD_FILES] = {
609     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
610         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
611     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
612         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
613 };
614
615 ChessSquare XiangqiArray[2][BOARD_FILES] = {
616     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
617         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
618     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
619         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
620 };
621
622 ChessSquare CapablancaArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
624         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
626         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
627 };
628
629 ChessSquare GreatArray[2][BOARD_FILES] = {
630     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
631         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
632     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
633         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
634 };
635
636 ChessSquare JanusArray[2][BOARD_FILES] = {
637     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
638         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
639     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
640         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
641 };
642
643 ChessSquare GrandArray[2][BOARD_FILES] = {
644     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
645         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
646     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
647         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
648 };
649
650 ChessSquare ChuChessArray[2][BOARD_FILES] = {
651     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
652         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
653     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
654         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
655 };
656
657 #ifdef GOTHIC
658 ChessSquare GothicArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
660         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
662         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
663 };
664 #else // !GOTHIC
665 #define GothicArray CapablancaArray
666 #endif // !GOTHIC
667
668 #ifdef FALCON
669 ChessSquare FalconArray[2][BOARD_FILES] = {
670     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
671         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
672     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
673         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
674 };
675 #else // !FALCON
676 #define FalconArray CapablancaArray
677 #endif // !FALCON
678
679 #else // !(BOARD_FILES>=10)
680 #define XiangqiPosition FIDEArray
681 #define CapablancaArray FIDEArray
682 #define GothicArray FIDEArray
683 #define GreatArray FIDEArray
684 #endif // !(BOARD_FILES>=10)
685
686 #if (BOARD_FILES>=12)
687 ChessSquare CourierArray[2][BOARD_FILES] = {
688     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
689         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
690     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
691         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
692 };
693 ChessSquare ChuArray[6][BOARD_FILES] = {
694     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
695       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
696     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
697       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
698     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
699       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
700     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
701       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
702     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
703       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
704     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
705       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
706 };
707 #else // !(BOARD_FILES>=12)
708 #define CourierArray CapablancaArray
709 #define ChuArray CapablancaArray
710 #endif // !(BOARD_FILES>=12)
711
712
713 Board initialPosition;
714
715
716 /* Convert str to a rating. Checks for special cases of "----",
717
718    "++++", etc. Also strips ()'s */
719 int
720 string_to_rating (char *str)
721 {
722   while(*str && !isdigit(*str)) ++str;
723   if (!*str)
724     return 0;   /* One of the special "no rating" cases */
725   else
726     return atoi(str);
727 }
728
729 void
730 ClearProgramStats ()
731 {
732     /* Init programStats */
733     programStats.movelist[0] = 0;
734     programStats.depth = 0;
735     programStats.nr_moves = 0;
736     programStats.moves_left = 0;
737     programStats.nodes = 0;
738     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
739     programStats.score = 0;
740     programStats.got_only_move = 0;
741     programStats.got_fail = 0;
742     programStats.line_is_book = 0;
743 }
744
745 void
746 CommonEngineInit ()
747 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
748     if (appData.firstPlaysBlack) {
749         first.twoMachinesColor = "black\n";
750         second.twoMachinesColor = "white\n";
751     } else {
752         first.twoMachinesColor = "white\n";
753         second.twoMachinesColor = "black\n";
754     }
755
756     first.other = &second;
757     second.other = &first;
758
759     { float norm = 1;
760         if(appData.timeOddsMode) {
761             norm = appData.timeOdds[0];
762             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
763         }
764         first.timeOdds  = appData.timeOdds[0]/norm;
765         second.timeOdds = appData.timeOdds[1]/norm;
766     }
767
768     if(programVersion) free(programVersion);
769     if (appData.noChessProgram) {
770         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
771         sprintf(programVersion, "%s", PACKAGE_STRING);
772     } else {
773       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
774       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
775       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
776     }
777 }
778
779 void
780 UnloadEngine (ChessProgramState *cps)
781 {
782         /* Kill off first chess program */
783         if (cps->isr != NULL)
784           RemoveInputSource(cps->isr);
785         cps->isr = NULL;
786
787         if (cps->pr != NoProc) {
788             ExitAnalyzeMode();
789             DoSleep( appData.delayBeforeQuit );
790             SendToProgram("quit\n", cps);
791             DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
792         }
793         cps->pr = NoProc;
794         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
795 }
796
797 void
798 ClearOptions (ChessProgramState *cps)
799 {
800     int i;
801     cps->nrOptions = cps->comboCnt = 0;
802     for(i=0; i<MAX_OPTIONS; i++) {
803         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
804         cps->option[i].textValue = 0;
805     }
806 }
807
808 char *engineNames[] = {
809   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
810      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
811 N_("first"),
812   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
813      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
814 N_("second")
815 };
816
817 void
818 InitEngine (ChessProgramState *cps, int n)
819 {   // [HGM] all engine initialiation put in a function that does one engine
820
821     ClearOptions(cps);
822
823     cps->which = engineNames[n];
824     cps->maybeThinking = FALSE;
825     cps->pr = NoProc;
826     cps->isr = NULL;
827     cps->sendTime = 2;
828     cps->sendDrawOffers = 1;
829
830     cps->program = appData.chessProgram[n];
831     cps->host = appData.host[n];
832     cps->dir = appData.directory[n];
833     cps->initString = appData.engInitString[n];
834     cps->computerString = appData.computerString[n];
835     cps->useSigint  = TRUE;
836     cps->useSigterm = TRUE;
837     cps->reuse = appData.reuse[n];
838     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
839     cps->useSetboard = FALSE;
840     cps->useSAN = FALSE;
841     cps->usePing = FALSE;
842     cps->lastPing = 0;
843     cps->lastPong = 0;
844     cps->usePlayother = FALSE;
845     cps->useColors = TRUE;
846     cps->useUsermove = FALSE;
847     cps->sendICS = FALSE;
848     cps->sendName = appData.icsActive;
849     cps->sdKludge = FALSE;
850     cps->stKludge = FALSE;
851     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
852     TidyProgramName(cps->program, cps->host, cps->tidy);
853     cps->matchWins = 0;
854     ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
855     cps->analysisSupport = 2; /* detect */
856     cps->analyzing = FALSE;
857     cps->initDone = FALSE;
858     cps->reload = FALSE;
859     cps->pseudo = appData.pseudo[n];
860
861     /* New features added by Tord: */
862     cps->useFEN960 = FALSE;
863     cps->useOOCastle = TRUE;
864     /* End of new features added by Tord. */
865     cps->fenOverride  = appData.fenOverride[n];
866
867     /* [HGM] time odds: set factor for each machine */
868     cps->timeOdds  = appData.timeOdds[n];
869
870     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
871     cps->accumulateTC = appData.accumulateTC[n];
872     cps->maxNrOfSessions = 1;
873
874     /* [HGM] debug */
875     cps->debug = FALSE;
876
877     cps->drawDepth = appData.drawDepth[n];
878     cps->supportsNPS = UNKNOWN;
879     cps->memSize = FALSE;
880     cps->maxCores = FALSE;
881     ASSIGN(cps->egtFormats, "");
882
883     /* [HGM] options */
884     cps->optionSettings  = appData.engOptions[n];
885
886     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
887     cps->isUCI = appData.isUCI[n]; /* [AS] */
888     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
889     cps->highlight = 0;
890
891     if (appData.protocolVersion[n] > PROTOVER
892         || appData.protocolVersion[n] < 1)
893       {
894         char buf[MSG_SIZ];
895         int len;
896
897         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
898                        appData.protocolVersion[n]);
899         if( (len >= MSG_SIZ) && appData.debugMode )
900           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
901
902         DisplayFatalError(buf, 0, 2);
903       }
904     else
905       {
906         cps->protocolVersion = appData.protocolVersion[n];
907       }
908
909     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
910     ParseFeatures(appData.featureDefaults, cps);
911 }
912
913 ChessProgramState *savCps;
914
915 GameMode oldMode;
916
917 void
918 LoadEngine ()
919 {
920     int i;
921     if(WaitForEngine(savCps, LoadEngine)) return;
922     CommonEngineInit(); // recalculate time odds
923     if(gameInfo.variant != StringToVariant(appData.variant)) {
924         // we changed variant when loading the engine; this forces us to reset
925         Reset(TRUE, savCps != &first);
926         oldMode = BeginningOfGame; // to prevent restoring old mode
927     }
928     InitChessProgram(savCps, FALSE);
929     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
930     DisplayMessage("", "");
931     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
932     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
933     ThawUI();
934     SetGNUMode();
935     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
936 }
937
938 void
939 ReplaceEngine (ChessProgramState *cps, int n)
940 {
941     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
942     keepInfo = 1;
943     if(oldMode != BeginningOfGame) EditGameEvent();
944     keepInfo = 0;
945     UnloadEngine(cps);
946     appData.noChessProgram = FALSE;
947     appData.clockMode = TRUE;
948     InitEngine(cps, n);
949     UpdateLogos(TRUE);
950     if(n) return; // only startup first engine immediately; second can wait
951     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
952     LoadEngine();
953 }
954
955 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
956 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
957
958 static char resetOptions[] =
959         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
960         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
961         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
962         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
963
964 void
965 FloatToFront(char **list, char *engineLine)
966 {
967     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
968     int i=0;
969     if(appData.recentEngines <= 0) return;
970     TidyProgramName(engineLine, "localhost", tidy+1);
971     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
972     strncpy(buf+1, *list, MSG_SIZ-50);
973     if(p = strstr(buf, tidy)) { // tidy name appears in list
974         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
975         while(*p++ = *++q); // squeeze out
976     }
977     strcat(tidy, buf+1); // put list behind tidy name
978     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
979     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
980     ASSIGN(*list, tidy+1);
981 }
982
983 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
984
985 void
986 Load (ChessProgramState *cps, int i)
987 {
988     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
989     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
990         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
991         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
992         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
993         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
994         appData.firstProtocolVersion = PROTOVER;
995         ParseArgsFromString(buf);
996         SwapEngines(i);
997         ReplaceEngine(cps, i);
998         FloatToFront(&appData.recentEngineList, engineLine);
999         return;
1000     }
1001     p = engineName;
1002     while(q = strchr(p, SLASH)) p = q+1;
1003     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
1004     if(engineDir[0] != NULLCHAR) {
1005         ASSIGN(appData.directory[i], engineDir); p = engineName;
1006     } else if(p != engineName) { // derive directory from engine path, when not given
1007         p[-1] = 0;
1008         ASSIGN(appData.directory[i], engineName);
1009         p[-1] = SLASH;
1010         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
1011     } else { ASSIGN(appData.directory[i], "."); }
1012     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
1013     if(params[0]) {
1014         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
1015         snprintf(command, MSG_SIZ, "%s %s", p, params);
1016         p = command;
1017     }
1018     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
1019     ASSIGN(appData.chessProgram[i], p);
1020     appData.isUCI[i] = isUCI;
1021     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
1022     appData.hasOwnBookUCI[i] = hasBook;
1023     if(!nickName[0]) useNick = FALSE;
1024     if(useNick) ASSIGN(appData.pgnName[i], nickName);
1025     if(addToList) {
1026         int len;
1027         char quote;
1028         q = firstChessProgramNames;
1029         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1030         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1031         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1032                         quote, p, quote, appData.directory[i],
1033                         useNick ? " -fn \"" : "",
1034                         useNick ? nickName : "",
1035                         useNick ? "\"" : "",
1036                         v1 ? " -firstProtocolVersion 1" : "",
1037                         hasBook ? "" : " -fNoOwnBookUCI",
1038                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1039                         storeVariant ? " -variant " : "",
1040                         storeVariant ? VariantName(gameInfo.variant) : "");
1041         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1042         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1043         if(insert != q) insert[-1] = NULLCHAR;
1044         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1045         if(q)   free(q);
1046         FloatToFront(&appData.recentEngineList, buf);
1047     }
1048     ReplaceEngine(cps, i);
1049 }
1050
1051 void
1052 InitTimeControls ()
1053 {
1054     int matched, min, sec;
1055     /*
1056      * Parse timeControl resource
1057      */
1058     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1059                           appData.movesPerSession)) {
1060         char buf[MSG_SIZ];
1061         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1062         DisplayFatalError(buf, 0, 2);
1063     }
1064
1065     /*
1066      * Parse searchTime resource
1067      */
1068     if (*appData.searchTime != NULLCHAR) {
1069         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1070         if (matched == 1) {
1071             searchTime = min * 60;
1072         } else if (matched == 2) {
1073             searchTime = min * 60 + sec;
1074         } else {
1075             char buf[MSG_SIZ];
1076             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1077             DisplayFatalError(buf, 0, 2);
1078         }
1079     }
1080 }
1081
1082 void
1083 InitBackEnd1 ()
1084 {
1085
1086     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1087     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1088
1089     GetTimeMark(&programStartTime);
1090     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1091     appData.seedBase = random() + (random()<<15);
1092     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1093
1094     ClearProgramStats();
1095     programStats.ok_to_send = 1;
1096     programStats.seen_stat = 0;
1097
1098     /*
1099      * Initialize game list
1100      */
1101     ListNew(&gameList);
1102
1103
1104     /*
1105      * Internet chess server status
1106      */
1107     if (appData.icsActive) {
1108         appData.matchMode = FALSE;
1109         appData.matchGames = 0;
1110 #if ZIPPY
1111         appData.noChessProgram = !appData.zippyPlay;
1112 #else
1113         appData.zippyPlay = FALSE;
1114         appData.zippyTalk = FALSE;
1115         appData.noChessProgram = TRUE;
1116 #endif
1117         if (*appData.icsHelper != NULLCHAR) {
1118             appData.useTelnet = TRUE;
1119             appData.telnetProgram = appData.icsHelper;
1120         }
1121     } else {
1122         appData.zippyTalk = appData.zippyPlay = FALSE;
1123     }
1124
1125     /* [AS] Initialize pv info list [HGM] and game state */
1126     {
1127         int i, j;
1128
1129         for( i=0; i<=framePtr; i++ ) {
1130             pvInfoList[i].depth = -1;
1131             boards[i][EP_STATUS] = EP_NONE;
1132             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1133         }
1134     }
1135
1136     InitTimeControls();
1137
1138     /* [AS] Adjudication threshold */
1139     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1140
1141     InitEngine(&first, 0);
1142     InitEngine(&second, 1);
1143     CommonEngineInit();
1144
1145     pairing.which = "pairing"; // pairing engine
1146     pairing.pr = NoProc;
1147     pairing.isr = NULL;
1148     pairing.program = appData.pairingEngine;
1149     pairing.host = "localhost";
1150     pairing.dir = ".";
1151
1152     if (appData.icsActive) {
1153         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1154     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1155         appData.clockMode = FALSE;
1156         first.sendTime = second.sendTime = 0;
1157     }
1158
1159 #if ZIPPY
1160     /* Override some settings from environment variables, for backward
1161        compatibility.  Unfortunately it's not feasible to have the env
1162        vars just set defaults, at least in xboard.  Ugh.
1163     */
1164     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1165       ZippyInit();
1166     }
1167 #endif
1168
1169     if (!appData.icsActive) {
1170       char buf[MSG_SIZ];
1171       int len;
1172
1173       /* Check for variants that are supported only in ICS mode,
1174          or not at all.  Some that are accepted here nevertheless
1175          have bugs; see comments below.
1176       */
1177       VariantClass variant = StringToVariant(appData.variant);
1178       switch (variant) {
1179       case VariantBughouse:     /* need four players and two boards */
1180       case VariantKriegspiel:   /* need to hide pieces and move details */
1181         /* case VariantFischeRandom: (Fabien: moved below) */
1182         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1183         if( (len >= MSG_SIZ) && appData.debugMode )
1184           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1185
1186         DisplayFatalError(buf, 0, 2);
1187         return;
1188
1189       case VariantUnknown:
1190       case VariantLoadable:
1191       case Variant29:
1192       case Variant30:
1193       case Variant31:
1194       case Variant32:
1195       case Variant33:
1196       case Variant34:
1197       case Variant35:
1198       case Variant36:
1199       default:
1200         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1201         if( (len >= MSG_SIZ) && appData.debugMode )
1202           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1203
1204         DisplayFatalError(buf, 0, 2);
1205         return;
1206
1207       case VariantNormal:     /* definitely works! */
1208         if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
1209           safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
1210           return;
1211         }
1212       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1213       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1214       case VariantGothic:     /* [HGM] should work */
1215       case VariantCapablanca: /* [HGM] should work */
1216       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1217       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1218       case VariantChu:        /* [HGM] experimental */
1219       case VariantKnightmate: /* [HGM] should work */
1220       case VariantCylinder:   /* [HGM] untested */
1221       case VariantFalcon:     /* [HGM] untested */
1222       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1223                                  offboard interposition not understood */
1224       case VariantWildCastle: /* pieces not automatically shuffled */
1225       case VariantNoCastle:   /* pieces not automatically shuffled */
1226       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1227       case VariantLosers:     /* should work except for win condition,
1228                                  and doesn't know captures are mandatory */
1229       case VariantSuicide:    /* should work except for win condition,
1230                                  and doesn't know captures are mandatory */
1231       case VariantGiveaway:   /* should work except for win condition,
1232                                  and doesn't know captures are mandatory */
1233       case VariantTwoKings:   /* should work */
1234       case VariantAtomic:     /* should work except for win condition */
1235       case Variant3Check:     /* should work except for win condition */
1236       case VariantShatranj:   /* should work except for all win conditions */
1237       case VariantMakruk:     /* should work except for draw countdown */
1238       case VariantASEAN :     /* should work except for draw countdown */
1239       case VariantBerolina:   /* might work if TestLegality is off */
1240       case VariantCapaRandom: /* should work */
1241       case VariantJanus:      /* should work */
1242       case VariantSuper:      /* experimental */
1243       case VariantGreat:      /* experimental, requires legality testing to be off */
1244       case VariantSChess:     /* S-Chess, should work */
1245       case VariantGrand:      /* should work */
1246       case VariantSpartan:    /* should work */
1247       case VariantLion:       /* should work */
1248       case VariantChuChess:   /* should work */
1249         break;
1250       }
1251     }
1252
1253 }
1254
1255 int
1256 NextIntegerFromString (char ** str, long * value)
1257 {
1258     int result = -1;
1259     char * s = *str;
1260
1261     while( *s == ' ' || *s == '\t' ) {
1262         s++;
1263     }
1264
1265     *value = 0;
1266
1267     if( *s >= '0' && *s <= '9' ) {
1268         while( *s >= '0' && *s <= '9' ) {
1269             *value = *value * 10 + (*s - '0');
1270             s++;
1271         }
1272
1273         result = 0;
1274     }
1275
1276     *str = s;
1277
1278     return result;
1279 }
1280
1281 int
1282 NextTimeControlFromString (char ** str, long * value)
1283 {
1284     long temp;
1285     int result = NextIntegerFromString( str, &temp );
1286
1287     if( result == 0 ) {
1288         *value = temp * 60; /* Minutes */
1289         if( **str == ':' ) {
1290             (*str)++;
1291             result = NextIntegerFromString( str, &temp );
1292             *value += temp; /* Seconds */
1293         }
1294     }
1295
1296     return result;
1297 }
1298
1299 int
1300 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1301 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1302     int result = -1, type = 0; long temp, temp2;
1303
1304     if(**str != ':') return -1; // old params remain in force!
1305     (*str)++;
1306     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1307     if( NextIntegerFromString( str, &temp ) ) return -1;
1308     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1309
1310     if(**str != '/') {
1311         /* time only: incremental or sudden-death time control */
1312         if(**str == '+') { /* increment follows; read it */
1313             (*str)++;
1314             if(**str == '!') type = *(*str)++; // Bronstein TC
1315             if(result = NextIntegerFromString( str, &temp2)) return -1;
1316             *inc = temp2 * 1000;
1317             if(**str == '.') { // read fraction of increment
1318                 char *start = ++(*str);
1319                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1320                 temp2 *= 1000;
1321                 while(start++ < *str) temp2 /= 10;
1322                 *inc += temp2;
1323             }
1324         } else *inc = 0;
1325         *moves = 0; *tc = temp * 1000; *incType = type;
1326         return 0;
1327     }
1328
1329     (*str)++; /* classical time control */
1330     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1331
1332     if(result == 0) {
1333         *moves = temp;
1334         *tc    = temp2 * 1000;
1335         *inc   = 0;
1336         *incType = type;
1337     }
1338     return result;
1339 }
1340
1341 int
1342 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1343 {   /* [HGM] get time to add from the multi-session time-control string */
1344     int incType, moves=1; /* kludge to force reading of first session */
1345     long time, increment;
1346     char *s = tcString;
1347
1348     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1349     do {
1350         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1351         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1352         if(movenr == -1) return time;    /* last move before new session     */
1353         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1354         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1355         if(!moves) return increment;     /* current session is incremental   */
1356         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1357     } while(movenr >= -1);               /* try again for next session       */
1358
1359     return 0; // no new time quota on this move
1360 }
1361
1362 int
1363 ParseTimeControl (char *tc, float ti, int mps)
1364 {
1365   long tc1;
1366   long tc2;
1367   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1368   int min, sec=0;
1369
1370   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1371   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1372       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1373   if(ti > 0) {
1374
1375     if(mps)
1376       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1377     else
1378       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1379   } else {
1380     if(mps)
1381       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1382     else
1383       snprintf(buf, MSG_SIZ, ":%s", mytc);
1384   }
1385   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1386
1387   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1388     return FALSE;
1389   }
1390
1391   if( *tc == '/' ) {
1392     /* Parse second time control */
1393     tc++;
1394
1395     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1396       return FALSE;
1397     }
1398
1399     if( tc2 == 0 ) {
1400       return FALSE;
1401     }
1402
1403     timeControl_2 = tc2 * 1000;
1404   }
1405   else {
1406     timeControl_2 = 0;
1407   }
1408
1409   if( tc1 == 0 ) {
1410     return FALSE;
1411   }
1412
1413   timeControl = tc1 * 1000;
1414
1415   if (ti >= 0) {
1416     timeIncrement = ti * 1000;  /* convert to ms */
1417     movesPerSession = 0;
1418   } else {
1419     timeIncrement = 0;
1420     movesPerSession = mps;
1421   }
1422   return TRUE;
1423 }
1424
1425 void
1426 InitBackEnd2 ()
1427 {
1428     if (appData.debugMode) {
1429 #    ifdef __GIT_VERSION
1430       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1431 #    else
1432       fprintf(debugFP, "Version: %s\n", programVersion);
1433 #    endif
1434     }
1435     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1436
1437     set_cont_sequence(appData.wrapContSeq);
1438     if (appData.matchGames > 0) {
1439         appData.matchMode = TRUE;
1440     } else if (appData.matchMode) {
1441         appData.matchGames = 1;
1442     }
1443     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1444         appData.matchGames = appData.sameColorGames;
1445     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1446         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1447         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1448     }
1449     Reset(TRUE, FALSE);
1450     if (appData.noChessProgram || first.protocolVersion == 1) {
1451       InitBackEnd3();
1452     } else {
1453       /* kludge: allow timeout for initial "feature" commands */
1454       FreezeUI();
1455       DisplayMessage("", _("Starting chess program"));
1456       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1457     }
1458 }
1459
1460 int
1461 CalculateIndex (int index, int gameNr)
1462 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1463     int res;
1464     if(index > 0) return index; // fixed nmber
1465     if(index == 0) return 1;
1466     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1467     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1468     return res;
1469 }
1470
1471 int
1472 LoadGameOrPosition (int gameNr)
1473 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1474     if (*appData.loadGameFile != NULLCHAR) {
1475         if (!LoadGameFromFile(appData.loadGameFile,
1476                 CalculateIndex(appData.loadGameIndex, gameNr),
1477                               appData.loadGameFile, FALSE)) {
1478             DisplayFatalError(_("Bad game file"), 0, 1);
1479             return 0;
1480         }
1481     } else if (*appData.loadPositionFile != NULLCHAR) {
1482         if (!LoadPositionFromFile(appData.loadPositionFile,
1483                 CalculateIndex(appData.loadPositionIndex, gameNr),
1484                                   appData.loadPositionFile)) {
1485             DisplayFatalError(_("Bad position file"), 0, 1);
1486             return 0;
1487         }
1488     }
1489     return 1;
1490 }
1491
1492 void
1493 ReserveGame (int gameNr, char resChar)
1494 {
1495     FILE *tf = fopen(appData.tourneyFile, "r+");
1496     char *p, *q, c, buf[MSG_SIZ];
1497     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1498     safeStrCpy(buf, lastMsg, MSG_SIZ);
1499     DisplayMessage(_("Pick new game"), "");
1500     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1501     ParseArgsFromFile(tf);
1502     p = q = appData.results;
1503     if(appData.debugMode) {
1504       char *r = appData.participants;
1505       fprintf(debugFP, "results = '%s'\n", p);
1506       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1507       fprintf(debugFP, "\n");
1508     }
1509     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1510     nextGame = q - p;
1511     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1512     safeStrCpy(q, p, strlen(p) + 2);
1513     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1514     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1515     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1516         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1517         q[nextGame] = '*';
1518     }
1519     fseek(tf, -(strlen(p)+4), SEEK_END);
1520     c = fgetc(tf);
1521     if(c != '"') // depending on DOS or Unix line endings we can be one off
1522          fseek(tf, -(strlen(p)+2), SEEK_END);
1523     else fseek(tf, -(strlen(p)+3), SEEK_END);
1524     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1525     DisplayMessage(buf, "");
1526     free(p); appData.results = q;
1527     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1528        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1529       int round = appData.defaultMatchGames * appData.tourneyType;
1530       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1531          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1532         UnloadEngine(&first);  // next game belongs to other pairing;
1533         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1534     }
1535     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1536 }
1537
1538 void
1539 MatchEvent (int mode)
1540 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1541         int dummy;
1542         if(matchMode) { // already in match mode: switch it off
1543             abortMatch = TRUE;
1544             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1545             return;
1546         }
1547 //      if(gameMode != BeginningOfGame) {
1548 //          DisplayError(_("You can only start a match from the initial position."), 0);
1549 //          return;
1550 //      }
1551         abortMatch = FALSE;
1552         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1553         /* Set up machine vs. machine match */
1554         nextGame = 0;
1555         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1556         if(appData.tourneyFile[0]) {
1557             ReserveGame(-1, 0);
1558             if(nextGame > appData.matchGames) {
1559                 char buf[MSG_SIZ];
1560                 if(strchr(appData.results, '*') == NULL) {
1561                     FILE *f;
1562                     appData.tourneyCycles++;
1563                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1564                         fclose(f);
1565                         NextTourneyGame(-1, &dummy);
1566                         ReserveGame(-1, 0);
1567                         if(nextGame <= appData.matchGames) {
1568                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1569                             matchMode = mode;
1570                             ScheduleDelayedEvent(NextMatchGame, 10000);
1571                             return;
1572                         }
1573                     }
1574                 }
1575                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1576                 DisplayError(buf, 0);
1577                 appData.tourneyFile[0] = 0;
1578                 return;
1579             }
1580         } else
1581         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1582             DisplayFatalError(_("Can't have a match with no chess programs"),
1583                               0, 2);
1584             return;
1585         }
1586         matchMode = mode;
1587         matchGame = roundNr = 1;
1588         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1589         NextMatchGame();
1590 }
1591
1592 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1593
1594 void
1595 InitBackEnd3 P((void))
1596 {
1597     GameMode initialMode;
1598     char buf[MSG_SIZ];
1599     int err, len;
1600
1601     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1602        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1603         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1604        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1605        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1606         char c, *q = first.variants, *p = strchr(q, ',');
1607         if(p) *p = NULLCHAR;
1608         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1609             int w, h, s;
1610             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1611                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1612             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1613             Reset(TRUE, FALSE);         // and re-initialize
1614         }
1615         if(p) *p = ',';
1616     }
1617
1618     InitChessProgram(&first, startedFromSetupPosition);
1619
1620     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1621         free(programVersion);
1622         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1623         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1624         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1625     }
1626
1627     if (appData.icsActive) {
1628 #ifdef WIN32
1629         /* [DM] Make a console window if needed [HGM] merged ifs */
1630         ConsoleCreate();
1631 #endif
1632         err = establish();
1633         if (err != 0)
1634           {
1635             if (*appData.icsCommPort != NULLCHAR)
1636               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1637                              appData.icsCommPort);
1638             else
1639               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1640                         appData.icsHost, appData.icsPort);
1641
1642             if( (len >= MSG_SIZ) && appData.debugMode )
1643               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1644
1645             DisplayFatalError(buf, err, 1);
1646             return;
1647         }
1648         SetICSMode();
1649         telnetISR =
1650           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1651         fromUserISR =
1652           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1653         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1654             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1655     } else if (appData.noChessProgram) {
1656         SetNCPMode();
1657     } else {
1658         SetGNUMode();
1659     }
1660
1661     if (*appData.cmailGameName != NULLCHAR) {
1662         SetCmailMode();
1663         OpenLoopback(&cmailPR);
1664         cmailISR =
1665           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1666     }
1667
1668     ThawUI();
1669     DisplayMessage("", "");
1670     if (StrCaseCmp(appData.initialMode, "") == 0) {
1671       initialMode = BeginningOfGame;
1672       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1673         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1674         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1675         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1676         ModeHighlight();
1677       }
1678     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1679       initialMode = TwoMachinesPlay;
1680     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1681       initialMode = AnalyzeFile;
1682     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1683       initialMode = AnalyzeMode;
1684     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1685       initialMode = MachinePlaysWhite;
1686     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1687       initialMode = MachinePlaysBlack;
1688     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1689       initialMode = EditGame;
1690     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1691       initialMode = EditPosition;
1692     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1693       initialMode = Training;
1694     } else {
1695       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1696       if( (len >= MSG_SIZ) && appData.debugMode )
1697         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1698
1699       DisplayFatalError(buf, 0, 2);
1700       return;
1701     }
1702
1703     if (appData.matchMode) {
1704         if(appData.tourneyFile[0]) { // start tourney from command line
1705             FILE *f;
1706             if(f = fopen(appData.tourneyFile, "r")) {
1707                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1708                 fclose(f);
1709                 appData.clockMode = TRUE;
1710                 SetGNUMode();
1711             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1712         }
1713         MatchEvent(TRUE);
1714     } else if (*appData.cmailGameName != NULLCHAR) {
1715         /* Set up cmail mode */
1716         ReloadCmailMsgEvent(TRUE);
1717     } else {
1718         /* Set up other modes */
1719         if (initialMode == AnalyzeFile) {
1720           if (*appData.loadGameFile == NULLCHAR) {
1721             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1722             return;
1723           }
1724         }
1725         if (*appData.loadGameFile != NULLCHAR) {
1726             (void) LoadGameFromFile(appData.loadGameFile,
1727                                     appData.loadGameIndex,
1728                                     appData.loadGameFile, TRUE);
1729         } else if (*appData.loadPositionFile != NULLCHAR) {
1730             (void) LoadPositionFromFile(appData.loadPositionFile,
1731                                         appData.loadPositionIndex,
1732                                         appData.loadPositionFile);
1733             /* [HGM] try to make self-starting even after FEN load */
1734             /* to allow automatic setup of fairy variants with wtm */
1735             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1736                 gameMode = BeginningOfGame;
1737                 setboardSpoiledMachineBlack = 1;
1738             }
1739             /* [HGM] loadPos: make that every new game uses the setup */
1740             /* from file as long as we do not switch variant          */
1741             if(!blackPlaysFirst) {
1742                 startedFromPositionFile = TRUE;
1743                 CopyBoard(filePosition, boards[0]);
1744                 CopyBoard(initialPosition, boards[0]);
1745             }
1746         }
1747         if (initialMode == AnalyzeMode) {
1748           if (appData.noChessProgram) {
1749             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1750             return;
1751           }
1752           if (appData.icsActive) {
1753             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1754             return;
1755           }
1756           AnalyzeModeEvent();
1757         } else if (initialMode == AnalyzeFile) {
1758           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1759           ShowThinkingEvent();
1760           AnalyzeFileEvent();
1761           AnalysisPeriodicEvent(1);
1762         } else if (initialMode == MachinePlaysWhite) {
1763           if (appData.noChessProgram) {
1764             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1765                               0, 2);
1766             return;
1767           }
1768           if (appData.icsActive) {
1769             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1770                               0, 2);
1771             return;
1772           }
1773           MachineWhiteEvent();
1774         } else if (initialMode == MachinePlaysBlack) {
1775           if (appData.noChessProgram) {
1776             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1777                               0, 2);
1778             return;
1779           }
1780           if (appData.icsActive) {
1781             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1782                               0, 2);
1783             return;
1784           }
1785           MachineBlackEvent();
1786         } else if (initialMode == TwoMachinesPlay) {
1787           if (appData.noChessProgram) {
1788             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1789                               0, 2);
1790             return;
1791           }
1792           if (appData.icsActive) {
1793             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1794                               0, 2);
1795             return;
1796           }
1797           TwoMachinesEvent();
1798         } else if (initialMode == EditGame) {
1799           EditGameEvent();
1800         } else if (initialMode == EditPosition) {
1801           EditPositionEvent();
1802         } else if (initialMode == Training) {
1803           if (*appData.loadGameFile == NULLCHAR) {
1804             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1805             return;
1806           }
1807           TrainingEvent();
1808         }
1809     }
1810 }
1811
1812 void
1813 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1814 {
1815     DisplayBook(current+1);
1816
1817     MoveHistorySet( movelist, first, last, current, pvInfoList );
1818
1819     EvalGraphSet( first, last, current, pvInfoList );
1820
1821     MakeEngineOutputTitle();
1822 }
1823
1824 /*
1825  * Establish will establish a contact to a remote host.port.
1826  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1827  *  used to talk to the host.
1828  * Returns 0 if okay, error code if not.
1829  */
1830 int
1831 establish ()
1832 {
1833     char buf[MSG_SIZ];
1834
1835     if (*appData.icsCommPort != NULLCHAR) {
1836         /* Talk to the host through a serial comm port */
1837         return OpenCommPort(appData.icsCommPort, &icsPR);
1838
1839     } else if (*appData.gateway != NULLCHAR) {
1840         if (*appData.remoteShell == NULLCHAR) {
1841             /* Use the rcmd protocol to run telnet program on a gateway host */
1842             snprintf(buf, sizeof(buf), "%s %s %s",
1843                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1844             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1845
1846         } else {
1847             /* Use the rsh program to run telnet program on a gateway host */
1848             if (*appData.remoteUser == NULLCHAR) {
1849                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1850                         appData.gateway, appData.telnetProgram,
1851                         appData.icsHost, appData.icsPort);
1852             } else {
1853                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1854                         appData.remoteShell, appData.gateway,
1855                         appData.remoteUser, appData.telnetProgram,
1856                         appData.icsHost, appData.icsPort);
1857             }
1858             return StartChildProcess(buf, "", &icsPR);
1859
1860         }
1861     } else if (appData.useTelnet) {
1862         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1863
1864     } else {
1865         /* TCP socket interface differs somewhat between
1866            Unix and NT; handle details in the front end.
1867            */
1868         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1869     }
1870 }
1871
1872 void
1873 EscapeExpand (char *p, char *q)
1874 {       // [HGM] initstring: routine to shape up string arguments
1875         while(*p++ = *q++) if(p[-1] == '\\')
1876             switch(*q++) {
1877                 case 'n': p[-1] = '\n'; break;
1878                 case 'r': p[-1] = '\r'; break;
1879                 case 't': p[-1] = '\t'; break;
1880                 case '\\': p[-1] = '\\'; break;
1881                 case 0: *p = 0; return;
1882                 default: p[-1] = q[-1]; break;
1883             }
1884 }
1885
1886 void
1887 show_bytes (FILE *fp, char *buf, int count)
1888 {
1889     while (count--) {
1890         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1891             fprintf(fp, "\\%03o", *buf & 0xff);
1892         } else {
1893             putc(*buf, fp);
1894         }
1895         buf++;
1896     }
1897     fflush(fp);
1898 }
1899
1900 /* Returns an errno value */
1901 int
1902 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1903 {
1904     char buf[8192], *p, *q, *buflim;
1905     int left, newcount, outcount;
1906
1907     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1908         *appData.gateway != NULLCHAR) {
1909         if (appData.debugMode) {
1910             fprintf(debugFP, ">ICS: ");
1911             show_bytes(debugFP, message, count);
1912             fprintf(debugFP, "\n");
1913         }
1914         return OutputToProcess(pr, message, count, outError);
1915     }
1916
1917     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1918     p = message;
1919     q = buf;
1920     left = count;
1921     newcount = 0;
1922     while (left) {
1923         if (q >= buflim) {
1924             if (appData.debugMode) {
1925                 fprintf(debugFP, ">ICS: ");
1926                 show_bytes(debugFP, buf, newcount);
1927                 fprintf(debugFP, "\n");
1928             }
1929             outcount = OutputToProcess(pr, buf, newcount, outError);
1930             if (outcount < newcount) return -1; /* to be sure */
1931             q = buf;
1932             newcount = 0;
1933         }
1934         if (*p == '\n') {
1935             *q++ = '\r';
1936             newcount++;
1937         } else if (((unsigned char) *p) == TN_IAC) {
1938             *q++ = (char) TN_IAC;
1939             newcount ++;
1940         }
1941         *q++ = *p++;
1942         newcount++;
1943         left--;
1944     }
1945     if (appData.debugMode) {
1946         fprintf(debugFP, ">ICS: ");
1947         show_bytes(debugFP, buf, newcount);
1948         fprintf(debugFP, "\n");
1949     }
1950     outcount = OutputToProcess(pr, buf, newcount, outError);
1951     if (outcount < newcount) return -1; /* to be sure */
1952     return count;
1953 }
1954
1955 void
1956 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1957 {
1958     int outError, outCount;
1959     static int gotEof = 0;
1960     static FILE *ini;
1961
1962     /* Pass data read from player on to ICS */
1963     if (count > 0) {
1964         gotEof = 0;
1965         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1966         if (outCount < count) {
1967             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1968         }
1969         if(have_sent_ICS_logon == 2) {
1970           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1971             fprintf(ini, "%s", message);
1972             have_sent_ICS_logon = 3;
1973           } else
1974             have_sent_ICS_logon = 1;
1975         } else if(have_sent_ICS_logon == 3) {
1976             fprintf(ini, "%s", message);
1977             fclose(ini);
1978           have_sent_ICS_logon = 1;
1979         }
1980     } else if (count < 0) {
1981         RemoveInputSource(isr);
1982         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1983     } else if (gotEof++ > 0) {
1984         RemoveInputSource(isr);
1985         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1986     }
1987 }
1988
1989 void
1990 KeepAlive ()
1991 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1992     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1993     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1994     SendToICS("date\n");
1995     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1996 }
1997
1998 /* added routine for printf style output to ics */
1999 void
2000 ics_printf (char *format, ...)
2001 {
2002     char buffer[MSG_SIZ];
2003     va_list args;
2004
2005     va_start(args, format);
2006     vsnprintf(buffer, sizeof(buffer), format, args);
2007     buffer[sizeof(buffer)-1] = '\0';
2008     SendToICS(buffer);
2009     va_end(args);
2010 }
2011
2012 void
2013 SendToICS (char *s)
2014 {
2015     int count, outCount, outError;
2016
2017     if (icsPR == NoProc) return;
2018
2019     count = strlen(s);
2020     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
2021     if (outCount < count) {
2022         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2023     }
2024 }
2025
2026 /* This is used for sending logon scripts to the ICS. Sending
2027    without a delay causes problems when using timestamp on ICC
2028    (at least on my machine). */
2029 void
2030 SendToICSDelayed (char *s, long msdelay)
2031 {
2032     int count, outCount, outError;
2033
2034     if (icsPR == NoProc) return;
2035
2036     count = strlen(s);
2037     if (appData.debugMode) {
2038         fprintf(debugFP, ">ICS: ");
2039         show_bytes(debugFP, s, count);
2040         fprintf(debugFP, "\n");
2041     }
2042     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2043                                       msdelay);
2044     if (outCount < count) {
2045         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2046     }
2047 }
2048
2049
2050 /* Remove all highlighting escape sequences in s
2051    Also deletes any suffix starting with '('
2052    */
2053 char *
2054 StripHighlightAndTitle (char *s)
2055 {
2056     static char retbuf[MSG_SIZ];
2057     char *p = retbuf;
2058
2059     while (*s != NULLCHAR) {
2060         while (*s == '\033') {
2061             while (*s != NULLCHAR && !isalpha(*s)) s++;
2062             if (*s != NULLCHAR) s++;
2063         }
2064         while (*s != NULLCHAR && *s != '\033') {
2065             if (*s == '(' || *s == '[') {
2066                 *p = NULLCHAR;
2067                 return retbuf;
2068             }
2069             *p++ = *s++;
2070         }
2071     }
2072     *p = NULLCHAR;
2073     return retbuf;
2074 }
2075
2076 /* Remove all highlighting escape sequences in s */
2077 char *
2078 StripHighlight (char *s)
2079 {
2080     static char retbuf[MSG_SIZ];
2081     char *p = retbuf;
2082
2083     while (*s != NULLCHAR) {
2084         while (*s == '\033') {
2085             while (*s != NULLCHAR && !isalpha(*s)) s++;
2086             if (*s != NULLCHAR) s++;
2087         }
2088         while (*s != NULLCHAR && *s != '\033') {
2089             *p++ = *s++;
2090         }
2091     }
2092     *p = NULLCHAR;
2093     return retbuf;
2094 }
2095
2096 char engineVariant[MSG_SIZ];
2097 char *variantNames[] = VARIANT_NAMES;
2098 char *
2099 VariantName (VariantClass v)
2100 {
2101     if(v == VariantUnknown || *engineVariant) return engineVariant;
2102     return variantNames[v];
2103 }
2104
2105
2106 /* Identify a variant from the strings the chess servers use or the
2107    PGN Variant tag names we use. */
2108 VariantClass
2109 StringToVariant (char *e)
2110 {
2111     char *p;
2112     int wnum = -1;
2113     VariantClass v = VariantNormal;
2114     int i, found = FALSE;
2115     char buf[MSG_SIZ], c;
2116     int len;
2117
2118     if (!e) return v;
2119
2120     /* [HGM] skip over optional board-size prefixes */
2121     if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
2122         sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
2123         while( *e++ != '_');
2124     }
2125
2126     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2127         v = VariantNormal;
2128         found = TRUE;
2129     } else
2130     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2131       if (p = StrCaseStr(e, variantNames[i])) {
2132         if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
2133         v = (VariantClass) i;
2134         found = TRUE;
2135         break;
2136       }
2137     }
2138
2139     if (!found) {
2140       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2141           || StrCaseStr(e, "wild/fr")
2142           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2143         v = VariantFischeRandom;
2144       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2145                  (i = 1, p = StrCaseStr(e, "w"))) {
2146         p += i;
2147         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2148         if (isdigit(*p)) {
2149           wnum = atoi(p);
2150         } else {
2151           wnum = -1;
2152         }
2153         switch (wnum) {
2154         case 0: /* FICS only, actually */
2155         case 1:
2156           /* Castling legal even if K starts on d-file */
2157           v = VariantWildCastle;
2158           break;
2159         case 2:
2160         case 3:
2161         case 4:
2162           /* Castling illegal even if K & R happen to start in
2163              normal positions. */
2164           v = VariantNoCastle;
2165           break;
2166         case 5:
2167         case 7:
2168         case 8:
2169         case 10:
2170         case 11:
2171         case 12:
2172         case 13:
2173         case 14:
2174         case 15:
2175         case 18:
2176         case 19:
2177           /* Castling legal iff K & R start in normal positions */
2178           v = VariantNormal;
2179           break;
2180         case 6:
2181         case 20:
2182         case 21:
2183           /* Special wilds for position setup; unclear what to do here */
2184           v = VariantLoadable;
2185           break;
2186         case 9:
2187           /* Bizarre ICC game */
2188           v = VariantTwoKings;
2189           break;
2190         case 16:
2191           v = VariantKriegspiel;
2192           break;
2193         case 17:
2194           v = VariantLosers;
2195           break;
2196         case 22:
2197           v = VariantFischeRandom;
2198           break;
2199         case 23:
2200           v = VariantCrazyhouse;
2201           break;
2202         case 24:
2203           v = VariantBughouse;
2204           break;
2205         case 25:
2206           v = Variant3Check;
2207           break;
2208         case 26:
2209           /* Not quite the same as FICS suicide! */
2210           v = VariantGiveaway;
2211           break;
2212         case 27:
2213           v = VariantAtomic;
2214           break;
2215         case 28:
2216           v = VariantShatranj;
2217           break;
2218
2219         /* Temporary names for future ICC types.  The name *will* change in
2220            the next xboard/WinBoard release after ICC defines it. */
2221         case 29:
2222           v = Variant29;
2223           break;
2224         case 30:
2225           v = Variant30;
2226           break;
2227         case 31:
2228           v = Variant31;
2229           break;
2230         case 32:
2231           v = Variant32;
2232           break;
2233         case 33:
2234           v = Variant33;
2235           break;
2236         case 34:
2237           v = Variant34;
2238           break;
2239         case 35:
2240           v = Variant35;
2241           break;
2242         case 36:
2243           v = Variant36;
2244           break;
2245         case 37:
2246           v = VariantShogi;
2247           break;
2248         case 38:
2249           v = VariantXiangqi;
2250           break;
2251         case 39:
2252           v = VariantCourier;
2253           break;
2254         case 40:
2255           v = VariantGothic;
2256           break;
2257         case 41:
2258           v = VariantCapablanca;
2259           break;
2260         case 42:
2261           v = VariantKnightmate;
2262           break;
2263         case 43:
2264           v = VariantFairy;
2265           break;
2266         case 44:
2267           v = VariantCylinder;
2268           break;
2269         case 45:
2270           v = VariantFalcon;
2271           break;
2272         case 46:
2273           v = VariantCapaRandom;
2274           break;
2275         case 47:
2276           v = VariantBerolina;
2277           break;
2278         case 48:
2279           v = VariantJanus;
2280           break;
2281         case 49:
2282           v = VariantSuper;
2283           break;
2284         case 50:
2285           v = VariantGreat;
2286           break;
2287         case -1:
2288           /* Found "wild" or "w" in the string but no number;
2289              must assume it's normal chess. */
2290           v = VariantNormal;
2291           break;
2292         default:
2293           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2294           if( (len >= MSG_SIZ) && appData.debugMode )
2295             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2296
2297           DisplayError(buf, 0);
2298           v = VariantUnknown;
2299           break;
2300         }
2301       }
2302     }
2303     if (appData.debugMode) {
2304       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2305               e, wnum, VariantName(v));
2306     }
2307     return v;
2308 }
2309
2310 static int leftover_start = 0, leftover_len = 0;
2311 char star_match[STAR_MATCH_N][MSG_SIZ];
2312
2313 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2314    advance *index beyond it, and set leftover_start to the new value of
2315    *index; else return FALSE.  If pattern contains the character '*', it
2316    matches any sequence of characters not containing '\r', '\n', or the
2317    character following the '*' (if any), and the matched sequence(s) are
2318    copied into star_match.
2319    */
2320 int
2321 looking_at ( char *buf, int *index, char *pattern)
2322 {
2323     char *bufp = &buf[*index], *patternp = pattern;
2324     int star_count = 0;
2325     char *matchp = star_match[0];
2326
2327     for (;;) {
2328         if (*patternp == NULLCHAR) {
2329             *index = leftover_start = bufp - buf;
2330             *matchp = NULLCHAR;
2331             return TRUE;
2332         }
2333         if (*bufp == NULLCHAR) return FALSE;
2334         if (*patternp == '*') {
2335             if (*bufp == *(patternp + 1)) {
2336                 *matchp = NULLCHAR;
2337                 matchp = star_match[++star_count];
2338                 patternp += 2;
2339                 bufp++;
2340                 continue;
2341             } else if (*bufp == '\n' || *bufp == '\r') {
2342                 patternp++;
2343                 if (*patternp == NULLCHAR)
2344                   continue;
2345                 else
2346                   return FALSE;
2347             } else {
2348                 *matchp++ = *bufp++;
2349                 continue;
2350             }
2351         }
2352         if (*patternp != *bufp) return FALSE;
2353         patternp++;
2354         bufp++;
2355     }
2356 }
2357
2358 void
2359 SendToPlayer (char *data, int length)
2360 {
2361     int error, outCount;
2362     outCount = OutputToProcess(NoProc, data, length, &error);
2363     if (outCount < length) {
2364         DisplayFatalError(_("Error writing to display"), error, 1);
2365     }
2366 }
2367
2368 void
2369 PackHolding (char packed[], char *holding)
2370 {
2371     char *p = holding;
2372     char *q = packed;
2373     int runlength = 0;
2374     int curr = 9999;
2375     do {
2376         if (*p == curr) {
2377             runlength++;
2378         } else {
2379             switch (runlength) {
2380               case 0:
2381                 break;
2382               case 1:
2383                 *q++ = curr;
2384                 break;
2385               case 2:
2386                 *q++ = curr;
2387                 *q++ = curr;
2388                 break;
2389               default:
2390                 sprintf(q, "%d", runlength);
2391                 while (*q) q++;
2392                 *q++ = curr;
2393                 break;
2394             }
2395             runlength = 1;
2396             curr = *p;
2397         }
2398     } while (*p++);
2399     *q = NULLCHAR;
2400 }
2401
2402 /* Telnet protocol requests from the front end */
2403 void
2404 TelnetRequest (unsigned char ddww, unsigned char option)
2405 {
2406     unsigned char msg[3];
2407     int outCount, outError;
2408
2409     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2410
2411     if (appData.debugMode) {
2412         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2413         switch (ddww) {
2414           case TN_DO:
2415             ddwwStr = "DO";
2416             break;
2417           case TN_DONT:
2418             ddwwStr = "DONT";
2419             break;
2420           case TN_WILL:
2421             ddwwStr = "WILL";
2422             break;
2423           case TN_WONT:
2424             ddwwStr = "WONT";
2425             break;
2426           default:
2427             ddwwStr = buf1;
2428             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2429             break;
2430         }
2431         switch (option) {
2432           case TN_ECHO:
2433             optionStr = "ECHO";
2434             break;
2435           default:
2436             optionStr = buf2;
2437             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2438             break;
2439         }
2440         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2441     }
2442     msg[0] = TN_IAC;
2443     msg[1] = ddww;
2444     msg[2] = option;
2445     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2446     if (outCount < 3) {
2447         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2448     }
2449 }
2450
2451 void
2452 DoEcho ()
2453 {
2454     if (!appData.icsActive) return;
2455     TelnetRequest(TN_DO, TN_ECHO);
2456 }
2457
2458 void
2459 DontEcho ()
2460 {
2461     if (!appData.icsActive) return;
2462     TelnetRequest(TN_DONT, TN_ECHO);
2463 }
2464
2465 void
2466 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2467 {
2468     /* put the holdings sent to us by the server on the board holdings area */
2469     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2470     char p;
2471     ChessSquare piece;
2472
2473     if(gameInfo.holdingsWidth < 2)  return;
2474     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2475         return; // prevent overwriting by pre-board holdings
2476
2477     if( (int)lowestPiece >= BlackPawn ) {
2478         holdingsColumn = 0;
2479         countsColumn = 1;
2480         holdingsStartRow = BOARD_HEIGHT-1;
2481         direction = -1;
2482     } else {
2483         holdingsColumn = BOARD_WIDTH-1;
2484         countsColumn = BOARD_WIDTH-2;
2485         holdingsStartRow = 0;
2486         direction = 1;
2487     }
2488
2489     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2490         board[i][holdingsColumn] = EmptySquare;
2491         board[i][countsColumn]   = (ChessSquare) 0;
2492     }
2493     while( (p=*holdings++) != NULLCHAR ) {
2494         piece = CharToPiece( ToUpper(p) );
2495         if(piece == EmptySquare) continue;
2496         /*j = (int) piece - (int) WhitePawn;*/
2497         j = PieceToNumber(piece);
2498         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2499         if(j < 0) continue;               /* should not happen */
2500         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2501         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2502         board[holdingsStartRow+j*direction][countsColumn]++;
2503     }
2504 }
2505
2506
2507 void
2508 VariantSwitch (Board board, VariantClass newVariant)
2509 {
2510    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2511    static Board oldBoard;
2512
2513    startedFromPositionFile = FALSE;
2514    if(gameInfo.variant == newVariant) return;
2515
2516    /* [HGM] This routine is called each time an assignment is made to
2517     * gameInfo.variant during a game, to make sure the board sizes
2518     * are set to match the new variant. If that means adding or deleting
2519     * holdings, we shift the playing board accordingly
2520     * This kludge is needed because in ICS observe mode, we get boards
2521     * of an ongoing game without knowing the variant, and learn about the
2522     * latter only later. This can be because of the move list we requested,
2523     * in which case the game history is refilled from the beginning anyway,
2524     * but also when receiving holdings of a crazyhouse game. In the latter
2525     * case we want to add those holdings to the already received position.
2526     */
2527
2528
2529    if (appData.debugMode) {
2530      fprintf(debugFP, "Switch board from %s to %s\n",
2531              VariantName(gameInfo.variant), VariantName(newVariant));
2532      setbuf(debugFP, NULL);
2533    }
2534    shuffleOpenings = 0;       /* [HGM] shuffle */
2535    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2536    switch(newVariant)
2537      {
2538      case VariantShogi:
2539        newWidth = 9;  newHeight = 9;
2540        gameInfo.holdingsSize = 7;
2541      case VariantBughouse:
2542      case VariantCrazyhouse:
2543        newHoldingsWidth = 2; break;
2544      case VariantGreat:
2545        newWidth = 10;
2546      case VariantSuper:
2547        newHoldingsWidth = 2;
2548        gameInfo.holdingsSize = 8;
2549        break;
2550      case VariantGothic:
2551      case VariantCapablanca:
2552      case VariantCapaRandom:
2553        newWidth = 10;
2554      default:
2555        newHoldingsWidth = gameInfo.holdingsSize = 0;
2556      };
2557
2558    if(newWidth  != gameInfo.boardWidth  ||
2559       newHeight != gameInfo.boardHeight ||
2560       newHoldingsWidth != gameInfo.holdingsWidth ) {
2561
2562      /* shift position to new playing area, if needed */
2563      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2564        for(i=0; i<BOARD_HEIGHT; i++)
2565          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2566            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2567              board[i][j];
2568        for(i=0; i<newHeight; i++) {
2569          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2570          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2571        }
2572      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2573        for(i=0; i<BOARD_HEIGHT; i++)
2574          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2575            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2576              board[i][j];
2577      }
2578      board[HOLDINGS_SET] = 0;
2579      gameInfo.boardWidth  = newWidth;
2580      gameInfo.boardHeight = newHeight;
2581      gameInfo.holdingsWidth = newHoldingsWidth;
2582      gameInfo.variant = newVariant;
2583      InitDrawingSizes(-2, 0);
2584    } else gameInfo.variant = newVariant;
2585    CopyBoard(oldBoard, board);   // remember correctly formatted board
2586      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2587    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2588 }
2589
2590 static int loggedOn = FALSE;
2591
2592 /*-- Game start info cache: --*/
2593 int gs_gamenum;
2594 char gs_kind[MSG_SIZ];
2595 static char player1Name[128] = "";
2596 static char player2Name[128] = "";
2597 static char cont_seq[] = "\n\\   ";
2598 static int player1Rating = -1;
2599 static int player2Rating = -1;
2600 /*----------------------------*/
2601
2602 ColorClass curColor = ColorNormal;
2603 int suppressKibitz = 0;
2604
2605 // [HGM] seekgraph
2606 Boolean soughtPending = FALSE;
2607 Boolean seekGraphUp;
2608 #define MAX_SEEK_ADS 200
2609 #define SQUARE 0x80
2610 char *seekAdList[MAX_SEEK_ADS];
2611 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2612 float tcList[MAX_SEEK_ADS];
2613 char colorList[MAX_SEEK_ADS];
2614 int nrOfSeekAds = 0;
2615 int minRating = 1010, maxRating = 2800;
2616 int hMargin = 10, vMargin = 20, h, w;
2617 extern int squareSize, lineGap;
2618
2619 void
2620 PlotSeekAd (int i)
2621 {
2622         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2623         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2624         if(r < minRating+100 && r >=0 ) r = minRating+100;
2625         if(r > maxRating) r = maxRating;
2626         if(tc < 1.f) tc = 1.f;
2627         if(tc > 95.f) tc = 95.f;
2628         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2629         y = ((double)r - minRating)/(maxRating - minRating)
2630             * (h-vMargin-squareSize/8-1) + vMargin;
2631         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2632         if(strstr(seekAdList[i], " u ")) color = 1;
2633         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2634            !strstr(seekAdList[i], "bullet") &&
2635            !strstr(seekAdList[i], "blitz") &&
2636            !strstr(seekAdList[i], "standard") ) color = 2;
2637         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2638         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2639 }
2640
2641 void
2642 PlotSingleSeekAd (int i)
2643 {
2644         PlotSeekAd(i);
2645 }
2646
2647 void
2648 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2649 {
2650         char buf[MSG_SIZ], *ext = "";
2651         VariantClass v = StringToVariant(type);
2652         if(strstr(type, "wild")) {
2653             ext = type + 4; // append wild number
2654             if(v == VariantFischeRandom) type = "chess960"; else
2655             if(v == VariantLoadable) type = "setup"; else
2656             type = VariantName(v);
2657         }
2658         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2659         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2660             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2661             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2662             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2663             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2664             seekNrList[nrOfSeekAds] = nr;
2665             zList[nrOfSeekAds] = 0;
2666             seekAdList[nrOfSeekAds++] = StrSave(buf);
2667             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2668         }
2669 }
2670
2671 void
2672 EraseSeekDot (int i)
2673 {
2674     int x = xList[i], y = yList[i], d=squareSize/4, k;
2675     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2676     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2677     // now replot every dot that overlapped
2678     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2679         int xx = xList[k], yy = yList[k];
2680         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2681             DrawSeekDot(xx, yy, colorList[k]);
2682     }
2683 }
2684
2685 void
2686 RemoveSeekAd (int nr)
2687 {
2688         int i;
2689         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2690             EraseSeekDot(i);
2691             if(seekAdList[i]) free(seekAdList[i]);
2692             seekAdList[i] = seekAdList[--nrOfSeekAds];
2693             seekNrList[i] = seekNrList[nrOfSeekAds];
2694             ratingList[i] = ratingList[nrOfSeekAds];
2695             colorList[i]  = colorList[nrOfSeekAds];
2696             tcList[i] = tcList[nrOfSeekAds];
2697             xList[i]  = xList[nrOfSeekAds];
2698             yList[i]  = yList[nrOfSeekAds];
2699             zList[i]  = zList[nrOfSeekAds];
2700             seekAdList[nrOfSeekAds] = NULL;
2701             break;
2702         }
2703 }
2704
2705 Boolean
2706 MatchSoughtLine (char *line)
2707 {
2708     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2709     int nr, base, inc, u=0; char dummy;
2710
2711     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2712        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2713        (u=1) &&
2714        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2715         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2716         // match: compact and save the line
2717         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2718         return TRUE;
2719     }
2720     return FALSE;
2721 }
2722
2723 int
2724 DrawSeekGraph ()
2725 {
2726     int i;
2727     if(!seekGraphUp) return FALSE;
2728     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
2729     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
2730
2731     DrawSeekBackground(0, 0, w, h);
2732     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2733     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2734     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2735         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2736         yy = h-1-yy;
2737         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2738         if(i%500 == 0) {
2739             char buf[MSG_SIZ];
2740             snprintf(buf, MSG_SIZ, "%d", i);
2741             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2742         }
2743     }
2744     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2745     for(i=1; i<100; i+=(i<10?1:5)) {
2746         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2747         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2748         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2749             char buf[MSG_SIZ];
2750             snprintf(buf, MSG_SIZ, "%d", i);
2751             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2752         }
2753     }
2754     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2755     return TRUE;
2756 }
2757
2758 int
2759 SeekGraphClick (ClickType click, int x, int y, int moving)
2760 {
2761     static int lastDown = 0, displayed = 0, lastSecond;
2762     if(y < 0) return FALSE;
2763     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2764         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2765         if(!seekGraphUp) return FALSE;
2766         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2767         DrawPosition(TRUE, NULL);
2768         return TRUE;
2769     }
2770     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2771         if(click == Release || moving) return FALSE;
2772         nrOfSeekAds = 0;
2773         soughtPending = TRUE;
2774         SendToICS(ics_prefix);
2775         SendToICS("sought\n"); // should this be "sought all"?
2776     } else { // issue challenge based on clicked ad
2777         int dist = 10000; int i, closest = 0, second = 0;
2778         for(i=0; i<nrOfSeekAds; i++) {
2779             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2780             if(d < dist) { dist = d; closest = i; }
2781             second += (d - zList[i] < 120); // count in-range ads
2782             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2783         }
2784         if(dist < 120) {
2785             char buf[MSG_SIZ];
2786             second = (second > 1);
2787             if(displayed != closest || second != lastSecond) {
2788                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2789                 lastSecond = second; displayed = closest;
2790             }
2791             if(click == Press) {
2792                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2793                 lastDown = closest;
2794                 return TRUE;
2795             } // on press 'hit', only show info
2796             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2797             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2798             SendToICS(ics_prefix);
2799             SendToICS(buf);
2800             return TRUE; // let incoming board of started game pop down the graph
2801         } else if(click == Release) { // release 'miss' is ignored
2802             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2803             if(moving == 2) { // right up-click
2804                 nrOfSeekAds = 0; // refresh graph
2805                 soughtPending = TRUE;
2806                 SendToICS(ics_prefix);
2807                 SendToICS("sought\n"); // should this be "sought all"?
2808             }
2809             return TRUE;
2810         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2811         // press miss or release hit 'pop down' seek graph
2812         seekGraphUp = FALSE;
2813         DrawPosition(TRUE, NULL);
2814     }
2815     return TRUE;
2816 }
2817
2818 void
2819 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2820 {
2821 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2822 #define STARTED_NONE 0
2823 #define STARTED_MOVES 1
2824 #define STARTED_BOARD 2
2825 #define STARTED_OBSERVE 3
2826 #define STARTED_HOLDINGS 4
2827 #define STARTED_CHATTER 5
2828 #define STARTED_COMMENT 6
2829 #define STARTED_MOVES_NOHIDE 7
2830
2831     static int started = STARTED_NONE;
2832     static char parse[20000];
2833     static int parse_pos = 0;
2834     static char buf[BUF_SIZE + 1];
2835     static int firstTime = TRUE, intfSet = FALSE;
2836     static ColorClass prevColor = ColorNormal;
2837     static int savingComment = FALSE;
2838     static int cmatch = 0; // continuation sequence match
2839     char *bp;
2840     char str[MSG_SIZ];
2841     int i, oldi;
2842     int buf_len;
2843     int next_out;
2844     int tkind;
2845     int backup;    /* [DM] For zippy color lines */
2846     char *p;
2847     char talker[MSG_SIZ]; // [HGM] chat
2848     int channel, collective=0;
2849
2850     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2851
2852     if (appData.debugMode) {
2853       if (!error) {
2854         fprintf(debugFP, "<ICS: ");
2855         show_bytes(debugFP, data, count);
2856         fprintf(debugFP, "\n");
2857       }
2858     }
2859
2860     if (appData.debugMode) { int f = forwardMostMove;
2861         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2862                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2863                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2864     }
2865     if (count > 0) {
2866         /* If last read ended with a partial line that we couldn't parse,
2867            prepend it to the new read and try again. */
2868         if (leftover_len > 0) {
2869             for (i=0; i<leftover_len; i++)
2870               buf[i] = buf[leftover_start + i];
2871         }
2872
2873     /* copy new characters into the buffer */
2874     bp = buf + leftover_len;
2875     buf_len=leftover_len;
2876     for (i=0; i<count; i++)
2877     {
2878         // ignore these
2879         if (data[i] == '\r')
2880             continue;
2881
2882         // join lines split by ICS?
2883         if (!appData.noJoin)
2884         {
2885             /*
2886                 Joining just consists of finding matches against the
2887                 continuation sequence, and discarding that sequence
2888                 if found instead of copying it.  So, until a match
2889                 fails, there's nothing to do since it might be the
2890                 complete sequence, and thus, something we don't want
2891                 copied.
2892             */
2893             if (data[i] == cont_seq[cmatch])
2894             {
2895                 cmatch++;
2896                 if (cmatch == strlen(cont_seq))
2897                 {
2898                     cmatch = 0; // complete match.  just reset the counter
2899
2900                     /*
2901                         it's possible for the ICS to not include the space
2902                         at the end of the last word, making our [correct]
2903                         join operation fuse two separate words.  the server
2904                         does this when the space occurs at the width setting.
2905                     */
2906                     if (!buf_len || buf[buf_len-1] != ' ')
2907                     {
2908                         *bp++ = ' ';
2909                         buf_len++;
2910                     }
2911                 }
2912                 continue;
2913             }
2914             else if (cmatch)
2915             {
2916                 /*
2917                     match failed, so we have to copy what matched before
2918                     falling through and copying this character.  In reality,
2919                     this will only ever be just the newline character, but
2920                     it doesn't hurt to be precise.
2921                 */
2922                 strncpy(bp, cont_seq, cmatch);
2923                 bp += cmatch;
2924                 buf_len += cmatch;
2925                 cmatch = 0;
2926             }
2927         }
2928
2929         // copy this char
2930         *bp++ = data[i];
2931         buf_len++;
2932     }
2933
2934         buf[buf_len] = NULLCHAR;
2935 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2936         next_out = 0;
2937         leftover_start = 0;
2938
2939         i = 0;
2940         while (i < buf_len) {
2941             /* Deal with part of the TELNET option negotiation
2942                protocol.  We refuse to do anything beyond the
2943                defaults, except that we allow the WILL ECHO option,
2944                which ICS uses to turn off password echoing when we are
2945                directly connected to it.  We reject this option
2946                if localLineEditing mode is on (always on in xboard)
2947                and we are talking to port 23, which might be a real
2948                telnet server that will try to keep WILL ECHO on permanently.
2949              */
2950             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2951                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2952                 unsigned char option;
2953                 oldi = i;
2954                 switch ((unsigned char) buf[++i]) {
2955                   case TN_WILL:
2956                     if (appData.debugMode)
2957                       fprintf(debugFP, "\n<WILL ");
2958                     switch (option = (unsigned char) buf[++i]) {
2959                       case TN_ECHO:
2960                         if (appData.debugMode)
2961                           fprintf(debugFP, "ECHO ");
2962                         /* Reply only if this is a change, according
2963                            to the protocol rules. */
2964                         if (remoteEchoOption) break;
2965                         if (appData.localLineEditing &&
2966                             atoi(appData.icsPort) == TN_PORT) {
2967                             TelnetRequest(TN_DONT, TN_ECHO);
2968                         } else {
2969                             EchoOff();
2970                             TelnetRequest(TN_DO, TN_ECHO);
2971                             remoteEchoOption = TRUE;
2972                         }
2973                         break;
2974                       default:
2975                         if (appData.debugMode)
2976                           fprintf(debugFP, "%d ", option);
2977                         /* Whatever this is, we don't want it. */
2978                         TelnetRequest(TN_DONT, option);
2979                         break;
2980                     }
2981                     break;
2982                   case TN_WONT:
2983                     if (appData.debugMode)
2984                       fprintf(debugFP, "\n<WONT ");
2985                     switch (option = (unsigned char) buf[++i]) {
2986                       case TN_ECHO:
2987                         if (appData.debugMode)
2988                           fprintf(debugFP, "ECHO ");
2989                         /* Reply only if this is a change, according
2990                            to the protocol rules. */
2991                         if (!remoteEchoOption) break;
2992                         EchoOn();
2993                         TelnetRequest(TN_DONT, TN_ECHO);
2994                         remoteEchoOption = FALSE;
2995                         break;
2996                       default:
2997                         if (appData.debugMode)
2998                           fprintf(debugFP, "%d ", (unsigned char) option);
2999                         /* Whatever this is, it must already be turned
3000                            off, because we never agree to turn on
3001                            anything non-default, so according to the
3002                            protocol rules, we don't reply. */
3003                         break;
3004                     }
3005                     break;
3006                   case TN_DO:
3007                     if (appData.debugMode)
3008                       fprintf(debugFP, "\n<DO ");
3009                     switch (option = (unsigned char) buf[++i]) {
3010                       default:
3011                         /* Whatever this is, we refuse to do it. */
3012                         if (appData.debugMode)
3013                           fprintf(debugFP, "%d ", option);
3014                         TelnetRequest(TN_WONT, option);
3015                         break;
3016                     }
3017                     break;
3018                   case TN_DONT:
3019                     if (appData.debugMode)
3020                       fprintf(debugFP, "\n<DONT ");
3021                     switch (option = (unsigned char) buf[++i]) {
3022                       default:
3023                         if (appData.debugMode)
3024                           fprintf(debugFP, "%d ", option);
3025                         /* Whatever this is, we are already not doing
3026                            it, because we never agree to do anything
3027                            non-default, so according to the protocol
3028                            rules, we don't reply. */
3029                         break;
3030                     }
3031                     break;
3032                   case TN_IAC:
3033                     if (appData.debugMode)
3034                       fprintf(debugFP, "\n<IAC ");
3035                     /* Doubled IAC; pass it through */
3036                     i--;
3037                     break;
3038                   default:
3039                     if (appData.debugMode)
3040                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3041                     /* Drop all other telnet commands on the floor */
3042                     break;
3043                 }
3044                 if (oldi > next_out)
3045                   SendToPlayer(&buf[next_out], oldi - next_out);
3046                 if (++i > next_out)
3047                   next_out = i;
3048                 continue;
3049             }
3050
3051             /* OK, this at least will *usually* work */
3052             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3053                 loggedOn = TRUE;
3054             }
3055
3056             if (loggedOn && !intfSet) {
3057                 if (ics_type == ICS_ICC) {
3058                   snprintf(str, MSG_SIZ,
3059                           "/set-quietly interface %s\n/set-quietly style 12\n",
3060                           programVersion);
3061                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3062                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3063                 } else if (ics_type == ICS_CHESSNET) {
3064                   snprintf(str, MSG_SIZ, "/style 12\n");
3065                 } else {
3066                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3067                   strcat(str, programVersion);
3068                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3069                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3070                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3071 #ifdef WIN32
3072                   strcat(str, "$iset nohighlight 1\n");
3073 #endif
3074                   strcat(str, "$iset lock 1\n$style 12\n");
3075                 }
3076                 SendToICS(str);
3077                 NotifyFrontendLogin();
3078                 intfSet = TRUE;
3079             }
3080
3081             if (started == STARTED_COMMENT) {
3082                 /* Accumulate characters in comment */
3083                 parse[parse_pos++] = buf[i];
3084                 if (buf[i] == '\n') {
3085                     parse[parse_pos] = NULLCHAR;
3086                     if(chattingPartner>=0) {
3087                         char mess[MSG_SIZ];
3088                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3089                         OutputChatMessage(chattingPartner, mess);
3090                         if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
3091                             int p;
3092                             talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
3093                             for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3094                                 snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
3095                                 OutputChatMessage(p, mess);
3096                                 break;
3097                             }
3098                         }
3099                         chattingPartner = -1;
3100                         if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
3101                         collective = 0;
3102                     } else
3103                     if(!suppressKibitz) // [HGM] kibitz
3104                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3105                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3106                         int nrDigit = 0, nrAlph = 0, j;
3107                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3108                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3109                         parse[parse_pos] = NULLCHAR;
3110                         // try to be smart: if it does not look like search info, it should go to
3111                         // ICS interaction window after all, not to engine-output window.
3112                         for(j=0; j<parse_pos; j++) { // count letters and digits
3113                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3114                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3115                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3116                         }
3117                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3118                             int depth=0; float score;
3119                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3120                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3121                                 pvInfoList[forwardMostMove-1].depth = depth;
3122                                 pvInfoList[forwardMostMove-1].score = 100*score;
3123                             }
3124                             OutputKibitz(suppressKibitz, parse);
3125                         } else {
3126                             char tmp[MSG_SIZ];
3127                             if(gameMode == IcsObserving) // restore original ICS messages
3128                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3129                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3130                             else
3131                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3132                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3133                             SendToPlayer(tmp, strlen(tmp));
3134                         }
3135                         next_out = i+1; // [HGM] suppress printing in ICS window
3136                     }
3137                     started = STARTED_NONE;
3138                 } else {
3139                     /* Don't match patterns against characters in comment */
3140                     i++;
3141                     continue;
3142                 }
3143             }
3144             if (started == STARTED_CHATTER) {
3145                 if (buf[i] != '\n') {
3146                     /* Don't match patterns against characters in chatter */
3147                     i++;
3148                     continue;
3149                 }
3150                 started = STARTED_NONE;
3151                 if(suppressKibitz) next_out = i+1;
3152             }
3153
3154             /* Kludge to deal with rcmd protocol */
3155             if (firstTime && looking_at(buf, &i, "\001*")) {
3156                 DisplayFatalError(&buf[1], 0, 1);
3157                 continue;
3158             } else {
3159                 firstTime = FALSE;
3160             }
3161
3162             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3163                 ics_type = ICS_ICC;
3164                 ics_prefix = "/";
3165                 if (appData.debugMode)
3166                   fprintf(debugFP, "ics_type %d\n", ics_type);
3167                 continue;
3168             }
3169             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3170                 ics_type = ICS_FICS;
3171                 ics_prefix = "$";
3172                 if (appData.debugMode)
3173                   fprintf(debugFP, "ics_type %d\n", ics_type);
3174                 continue;
3175             }
3176             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3177                 ics_type = ICS_CHESSNET;
3178                 ics_prefix = "/";
3179                 if (appData.debugMode)
3180                   fprintf(debugFP, "ics_type %d\n", ics_type);
3181                 continue;
3182             }
3183
3184             if (!loggedOn &&
3185                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3186                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3187                  looking_at(buf, &i, "will be \"*\""))) {
3188               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3189               continue;
3190             }
3191
3192             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3193               char buf[MSG_SIZ];
3194               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3195               DisplayIcsInteractionTitle(buf);
3196               have_set_title = TRUE;
3197             }
3198
3199             /* skip finger notes */
3200             if (started == STARTED_NONE &&
3201                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3202                  (buf[i] == '1' && buf[i+1] == '0')) &&
3203                 buf[i+2] == ':' && buf[i+3] == ' ') {
3204               started = STARTED_CHATTER;
3205               i += 3;
3206               continue;
3207             }
3208
3209             oldi = i;
3210             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3211             if(appData.seekGraph) {
3212                 if(soughtPending && MatchSoughtLine(buf+i)) {
3213                     i = strstr(buf+i, "rated") - buf;
3214                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3215                     next_out = leftover_start = i;
3216                     started = STARTED_CHATTER;
3217                     suppressKibitz = TRUE;
3218                     continue;
3219                 }
3220                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3221                         && looking_at(buf, &i, "* ads displayed")) {
3222                     soughtPending = FALSE;
3223                     seekGraphUp = TRUE;
3224                     DrawSeekGraph();
3225                     continue;
3226                 }
3227                 if(appData.autoRefresh) {
3228                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3229                         int s = (ics_type == ICS_ICC); // ICC format differs
3230                         if(seekGraphUp)
3231                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3232                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3233                         looking_at(buf, &i, "*% "); // eat prompt
3234                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3235                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3236                         next_out = i; // suppress
3237                         continue;
3238                     }
3239                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3240                         char *p = star_match[0];
3241                         while(*p) {
3242                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3243                             while(*p && *p++ != ' '); // next
3244                         }
3245                         looking_at(buf, &i, "*% "); // eat prompt
3246                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = i;
3248                         continue;
3249                     }
3250                 }
3251             }
3252
3253             /* skip formula vars */
3254             if (started == STARTED_NONE &&
3255                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3256               started = STARTED_CHATTER;
3257               i += 3;
3258               continue;
3259             }
3260
3261             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3262             if (appData.autoKibitz && started == STARTED_NONE &&
3263                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3264                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3265                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3266                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3267                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3268                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3269                         suppressKibitz = TRUE;
3270                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3271                         next_out = i;
3272                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3273                                 && (gameMode == IcsPlayingWhite)) ||
3274                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3275                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3276                             started = STARTED_CHATTER; // own kibitz we simply discard
3277                         else {
3278                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3279                             parse_pos = 0; parse[0] = NULLCHAR;
3280                             savingComment = TRUE;
3281                             suppressKibitz = gameMode != IcsObserving ? 2 :
3282                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3283                         }
3284                         continue;
3285                 } else
3286                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3287                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3288                          && atoi(star_match[0])) {
3289                     // suppress the acknowledgements of our own autoKibitz
3290                     char *p;
3291                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3292                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3293                     SendToPlayer(star_match[0], strlen(star_match[0]));
3294                     if(looking_at(buf, &i, "*% ")) // eat prompt
3295                         suppressKibitz = FALSE;
3296                     next_out = i;
3297                     continue;
3298                 }
3299             } // [HGM] kibitz: end of patch
3300
3301             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3302
3303             // [HGM] chat: intercept tells by users for which we have an open chat window
3304             channel = -1;
3305             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3306                                            looking_at(buf, &i, "* whispers:") ||
3307                                            looking_at(buf, &i, "* kibitzes:") ||
3308                                            looking_at(buf, &i, "* shouts:") ||
3309                                            looking_at(buf, &i, "* c-shouts:") ||
3310                                            looking_at(buf, &i, "--> * ") ||
3311                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3312                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3313                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3314                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3315                 int p;
3316                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3317                 chattingPartner = -1; collective = 0;
3318
3319                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3320                 for(p=0; p<MAX_CHAT; p++) {
3321                     collective = 1;
3322                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3323                     talker[0] = '['; strcat(talker, "] ");
3324                     Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
3325                     chattingPartner = p; break;
3326                     }
3327                 } else
3328                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3329                 for(p=0; p<MAX_CHAT; p++) {
3330                     collective = 1;
3331                     if(!strcmp("kibitzes", chatPartner[p])) {
3332                         talker[0] = '['; strcat(talker, "] ");
3333                         chattingPartner = p; break;
3334                     }
3335                 } else
3336                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3337                 for(p=0; p<MAX_CHAT; p++) {
3338                     collective = 1;
3339                     if(!strcmp("whispers", chatPartner[p])) {
3340                         talker[0] = '['; strcat(talker, "] ");
3341                         chattingPartner = p; break;
3342                     }
3343                 } else
3344                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3345                   if(buf[i-8] == '-' && buf[i-3] == 't')
3346                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3347                     collective = 1;
3348                     if(!strcmp("c-shouts", chatPartner[p])) {
3349                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3350                         chattingPartner = p; break;
3351                     }
3352                   }
3353                   if(chattingPartner < 0)
3354                   for(p=0; p<MAX_CHAT; p++) {
3355                     collective = 1;
3356                     if(!strcmp("shouts", chatPartner[p])) {
3357                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3358                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3359                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3360                         chattingPartner = p; break;
3361                     }
3362                   }
3363                 }
3364                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3365                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3366                     talker[0] = 0;
3367                     Colorize(ColorTell, FALSE);
3368                     if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
3369                     collective |= 2;
3370                     chattingPartner = p; break;
3371                 }
3372                 if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
3373                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3374                     started = STARTED_COMMENT;
3375                     parse_pos = 0; parse[0] = NULLCHAR;
3376                     savingComment = 3 + chattingPartner; // counts as TRUE
3377                     if(collective == 3) i = oldi; else {
3378                         suppressKibitz = TRUE;
3379                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3380                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3381                         continue;
3382                     }
3383                 }
3384             } // [HGM] chat: end of patch
3385
3386           backup = i;
3387             if (appData.zippyTalk || appData.zippyPlay) {
3388                 /* [DM] Backup address for color zippy lines */
3389 #if ZIPPY
3390                if (loggedOn == TRUE)
3391                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3392                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3393 #endif
3394             } // [DM] 'else { ' deleted
3395                 if (
3396                     /* Regular tells and says */
3397                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3398                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3399                     looking_at(buf, &i, "* says: ") ||
3400                     /* Don't color "message" or "messages" output */
3401                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3402                     looking_at(buf, &i, "*. * at *:*: ") ||
3403                     looking_at(buf, &i, "--* (*:*): ") ||
3404                     /* Message notifications (same color as tells) */
3405                     looking_at(buf, &i, "* has left a message ") ||
3406                     looking_at(buf, &i, "* just sent you a message:\n") ||
3407                     /* Whispers and kibitzes */
3408                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3409                     looking_at(buf, &i, "* kibitzes: ") ||
3410                     /* Channel tells */
3411                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3412
3413                   if (tkind == 1 && strchr(star_match[0], ':')) {
3414                       /* Avoid "tells you:" spoofs in channels */
3415                      tkind = 3;
3416                   }
3417                   if (star_match[0][0] == NULLCHAR ||
3418                       strchr(star_match[0], ' ') ||
3419                       (tkind == 3 && strchr(star_match[1], ' '))) {
3420                     /* Reject bogus matches */
3421                     i = oldi;
3422                   } else {
3423                     if (appData.colorize) {
3424                       if (oldi > next_out) {
3425                         SendToPlayer(&buf[next_out], oldi - next_out);
3426                         next_out = oldi;
3427                       }
3428                       switch (tkind) {
3429                       case 1:
3430                         Colorize(ColorTell, FALSE);
3431                         curColor = ColorTell;
3432                         break;
3433                       case 2:
3434                         Colorize(ColorKibitz, FALSE);
3435                         curColor = ColorKibitz;
3436                         break;
3437                       case 3:
3438                         p = strrchr(star_match[1], '(');
3439                         if (p == NULL) {
3440                           p = star_match[1];
3441                         } else {
3442                           p++;
3443                         }
3444                         if (atoi(p) == 1) {
3445                           Colorize(ColorChannel1, FALSE);
3446                           curColor = ColorChannel1;
3447                         } else {
3448                           Colorize(ColorChannel, FALSE);
3449                           curColor = ColorChannel;
3450                         }
3451                         break;
3452                       case 5:
3453                         curColor = ColorNormal;
3454                         break;
3455                       }
3456                     }
3457                     if (started == STARTED_NONE && appData.autoComment &&
3458                         (gameMode == IcsObserving ||
3459                          gameMode == IcsPlayingWhite ||
3460                          gameMode == IcsPlayingBlack)) {
3461                       parse_pos = i - oldi;
3462                       memcpy(parse, &buf[oldi], parse_pos);
3463                       parse[parse_pos] = NULLCHAR;
3464                       started = STARTED_COMMENT;
3465                       savingComment = TRUE;
3466                     } else if(collective != 3) {
3467                       started = STARTED_CHATTER;
3468                       savingComment = FALSE;
3469                     }
3470                     loggedOn = TRUE;
3471                     continue;
3472                   }
3473                 }
3474
3475                 if (looking_at(buf, &i, "* s-shouts: ") ||
3476                     looking_at(buf, &i, "* c-shouts: ")) {
3477                     if (appData.colorize) {
3478                         if (oldi > next_out) {
3479                             SendToPlayer(&buf[next_out], oldi - next_out);
3480                             next_out = oldi;
3481                         }
3482                         Colorize(ColorSShout, FALSE);
3483                         curColor = ColorSShout;
3484                     }
3485                     loggedOn = TRUE;
3486                     started = STARTED_CHATTER;
3487                     continue;
3488                 }
3489
3490                 if (looking_at(buf, &i, "--->")) {
3491                     loggedOn = TRUE;
3492                     continue;
3493                 }
3494
3495                 if (looking_at(buf, &i, "* shouts: ") ||
3496                     looking_at(buf, &i, "--> ")) {
3497                     if (appData.colorize) {
3498                         if (oldi > next_out) {
3499                             SendToPlayer(&buf[next_out], oldi - next_out);
3500                             next_out = oldi;
3501                         }
3502                         Colorize(ColorShout, FALSE);
3503                         curColor = ColorShout;
3504                     }
3505                     loggedOn = TRUE;
3506                     started = STARTED_CHATTER;
3507                     continue;
3508                 }
3509
3510                 if (looking_at( buf, &i, "Challenge:")) {
3511                     if (appData.colorize) {
3512                         if (oldi > next_out) {
3513                             SendToPlayer(&buf[next_out], oldi - next_out);
3514                             next_out = oldi;
3515                         }
3516                         Colorize(ColorChallenge, FALSE);
3517                         curColor = ColorChallenge;
3518                     }
3519                     loggedOn = TRUE;
3520                     continue;
3521                 }
3522
3523                 if (looking_at(buf, &i, "* offers you") ||
3524                     looking_at(buf, &i, "* offers to be") ||
3525                     looking_at(buf, &i, "* would like to") ||
3526                     looking_at(buf, &i, "* requests to") ||
3527                     looking_at(buf, &i, "Your opponent offers") ||
3528                     looking_at(buf, &i, "Your opponent requests")) {
3529
3530                     if (appData.colorize) {
3531                         if (oldi > next_out) {
3532                             SendToPlayer(&buf[next_out], oldi - next_out);
3533                             next_out = oldi;
3534                         }
3535                         Colorize(ColorRequest, FALSE);
3536                         curColor = ColorRequest;
3537                     }
3538                     continue;
3539                 }
3540
3541                 if (looking_at(buf, &i, "* (*) seeking")) {
3542                     if (appData.colorize) {
3543                         if (oldi > next_out) {
3544                             SendToPlayer(&buf[next_out], oldi - next_out);
3545                             next_out = oldi;
3546                         }
3547                         Colorize(ColorSeek, FALSE);
3548                         curColor = ColorSeek;
3549                     }
3550                     continue;
3551             }
3552
3553           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3554
3555             if (looking_at(buf, &i, "\\   ")) {
3556                 if (prevColor != ColorNormal) {
3557                     if (oldi > next_out) {
3558                         SendToPlayer(&buf[next_out], oldi - next_out);
3559                         next_out = oldi;
3560                     }
3561                     Colorize(prevColor, TRUE);
3562                     curColor = prevColor;
3563                 }
3564                 if (savingComment) {
3565                     parse_pos = i - oldi;
3566                     memcpy(parse, &buf[oldi], parse_pos);
3567                     parse[parse_pos] = NULLCHAR;
3568                     started = STARTED_COMMENT;
3569                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3570                         chattingPartner = savingComment - 3; // kludge to remember the box
3571                 } else {
3572                     started = STARTED_CHATTER;
3573                 }
3574                 continue;
3575             }
3576
3577             if (looking_at(buf, &i, "Black Strength :") ||
3578                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3579                 looking_at(buf, &i, "<10>") ||
3580                 looking_at(buf, &i, "#@#")) {
3581                 /* Wrong board style */
3582                 loggedOn = TRUE;
3583                 SendToICS(ics_prefix);
3584                 SendToICS("set style 12\n");
3585                 SendToICS(ics_prefix);
3586                 SendToICS("refresh\n");
3587                 continue;
3588             }
3589
3590             if (looking_at(buf, &i, "login:")) {
3591               if (!have_sent_ICS_logon) {
3592                 if(ICSInitScript())
3593                   have_sent_ICS_logon = 1;
3594                 else // no init script was found
3595                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3596               } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3597                   have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3598               }
3599                 continue;
3600             }
3601
3602             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3603                 (looking_at(buf, &i, "\n<12> ") ||
3604                  looking_at(buf, &i, "<12> "))) {
3605                 loggedOn = TRUE;
3606                 if (oldi > next_out) {
3607                     SendToPlayer(&buf[next_out], oldi - next_out);
3608                 }
3609                 next_out = i;
3610                 started = STARTED_BOARD;
3611                 parse_pos = 0;
3612                 continue;
3613             }
3614
3615             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3616                 looking_at(buf, &i, "<b1> ")) {
3617                 if (oldi > next_out) {
3618                     SendToPlayer(&buf[next_out], oldi - next_out);
3619                 }
3620                 next_out = i;
3621                 started = STARTED_HOLDINGS;
3622                 parse_pos = 0;
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3627                 loggedOn = TRUE;
3628                 /* Header for a move list -- first line */
3629
3630                 switch (ics_getting_history) {
3631                   case H_FALSE:
3632                     switch (gameMode) {
3633                       case IcsIdle:
3634                       case BeginningOfGame:
3635                         /* User typed "moves" or "oldmoves" while we
3636                            were idle.  Pretend we asked for these
3637                            moves and soak them up so user can step
3638                            through them and/or save them.
3639                            */
3640                         Reset(FALSE, TRUE);
3641                         gameMode = IcsObserving;
3642                         ModeHighlight();
3643                         ics_gamenum = -1;
3644                         ics_getting_history = H_GOT_UNREQ_HEADER;
3645                         break;
3646                       case EditGame: /*?*/
3647                       case EditPosition: /*?*/
3648                         /* Should above feature work in these modes too? */
3649                         /* For now it doesn't */
3650                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3651                         break;
3652                       default:
3653                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3654                         break;
3655                     }
3656                     break;
3657                   case H_REQUESTED:
3658                     /* Is this the right one? */
3659                     if (gameInfo.white && gameInfo.black &&
3660                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3661                         strcmp(gameInfo.black, star_match[2]) == 0) {
3662                         /* All is well */
3663                         ics_getting_history = H_GOT_REQ_HEADER;
3664                     }
3665                     break;
3666                   case H_GOT_REQ_HEADER:
3667                   case H_GOT_UNREQ_HEADER:
3668                   case H_GOT_UNWANTED_HEADER:
3669                   case H_GETTING_MOVES:
3670                     /* Should not happen */
3671                     DisplayError(_("Error gathering move list: two headers"), 0);
3672                     ics_getting_history = H_FALSE;
3673                     break;
3674                 }
3675
3676                 /* Save player ratings into gameInfo if needed */
3677                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3678                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3679                     (gameInfo.whiteRating == -1 ||
3680                      gameInfo.blackRating == -1)) {
3681
3682                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3683                     gameInfo.blackRating = string_to_rating(star_match[3]);
3684                     if (appData.debugMode)
3685                       fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3686                               gameInfo.whiteRating, gameInfo.blackRating);
3687                 }
3688                 continue;
3689             }
3690
3691             if (looking_at(buf, &i,
3692               "* * match, initial time: * minute*, increment: * second")) {
3693                 /* Header for a move list -- second line */
3694                 /* Initial board will follow if this is a wild game */
3695                 if (gameInfo.event != NULL) free(gameInfo.event);
3696                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3697                 gameInfo.event = StrSave(str);
3698                 /* [HGM] we switched variant. Translate boards if needed. */
3699                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3700                 continue;
3701             }
3702
3703             if (looking_at(buf, &i, "Move  ")) {
3704                 /* Beginning of a move list */
3705                 switch (ics_getting_history) {
3706                   case H_FALSE:
3707                     /* Normally should not happen */
3708                     /* Maybe user hit reset while we were parsing */
3709                     break;
3710                   case H_REQUESTED:
3711                     /* Happens if we are ignoring a move list that is not
3712                      * the one we just requested.  Common if the user
3713                      * tries to observe two games without turning off
3714                      * getMoveList */
3715                     break;
3716                   case H_GETTING_MOVES:
3717                     /* Should not happen */
3718                     DisplayError(_("Error gathering move list: nested"), 0);
3719                     ics_getting_history = H_FALSE;
3720                     break;
3721                   case H_GOT_REQ_HEADER:
3722                     ics_getting_history = H_GETTING_MOVES;
3723                     started = STARTED_MOVES;
3724                     parse_pos = 0;
3725                     if (oldi > next_out) {
3726                         SendToPlayer(&buf[next_out], oldi - next_out);
3727                     }
3728                     break;
3729                   case H_GOT_UNREQ_HEADER:
3730                     ics_getting_history = H_GETTING_MOVES;
3731                     started = STARTED_MOVES_NOHIDE;
3732                     parse_pos = 0;
3733                     break;
3734                   case H_GOT_UNWANTED_HEADER:
3735                     ics_getting_history = H_FALSE;
3736                     break;
3737                 }
3738                 continue;
3739             }
3740
3741             if (looking_at(buf, &i, "% ") ||
3742                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3743                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3744                 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3745                     soughtPending = FALSE;
3746                     seekGraphUp = TRUE;
3747                     DrawSeekGraph();
3748                 }
3749                 if(suppressKibitz) next_out = i;
3750                 savingComment = FALSE;
3751                 suppressKibitz = 0;
3752                 switch (started) {
3753                   case STARTED_MOVES:
3754                   case STARTED_MOVES_NOHIDE:
3755                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3756                     parse[parse_pos + i - oldi] = NULLCHAR;
3757                     ParseGameHistory(parse);
3758 #if ZIPPY
3759                     if (appData.zippyPlay && first.initDone) {
3760                         FeedMovesToProgram(&first, forwardMostMove);
3761                         if (gameMode == IcsPlayingWhite) {
3762                             if (WhiteOnMove(forwardMostMove)) {
3763                                 if (first.sendTime) {
3764                                   if (first.useColors) {
3765                                     SendToProgram("black\n", &first);
3766                                   }
3767                                   SendTimeRemaining(&first, TRUE);
3768                                 }
3769                                 if (first.useColors) {
3770                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3771                                 }
3772                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3773                                 first.maybeThinking = TRUE;
3774                             } else {
3775                                 if (first.usePlayother) {
3776                                   if (first.sendTime) {
3777                                     SendTimeRemaining(&first, TRUE);
3778                                   }
3779                                   SendToProgram("playother\n", &first);
3780                                   firstMove = FALSE;
3781                                 } else {
3782                                   firstMove = TRUE;
3783                                 }
3784                             }
3785                         } else if (gameMode == IcsPlayingBlack) {
3786                             if (!WhiteOnMove(forwardMostMove)) {
3787                                 if (first.sendTime) {
3788                                   if (first.useColors) {
3789                                     SendToProgram("white\n", &first);
3790                                   }
3791                                   SendTimeRemaining(&first, FALSE);
3792                                 }
3793                                 if (first.useColors) {
3794                                   SendToProgram("black\n", &first);
3795                                 }
3796                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3797                                 first.maybeThinking = TRUE;
3798                             } else {
3799                                 if (first.usePlayother) {
3800                                   if (first.sendTime) {
3801                                     SendTimeRemaining(&first, FALSE);
3802                                   }
3803                                   SendToProgram("playother\n", &first);
3804                                   firstMove = FALSE;
3805                                 } else {
3806                                   firstMove = TRUE;
3807                                 }
3808                             }
3809                         }
3810                     }
3811 #endif
3812                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3813                         /* Moves came from oldmoves or moves command
3814                            while we weren't doing anything else.
3815                            */
3816                         currentMove = forwardMostMove;
3817                         ClearHighlights();/*!!could figure this out*/
3818                         flipView = appData.flipView;
3819                         DrawPosition(TRUE, boards[currentMove]);
3820                         DisplayBothClocks();
3821                         snprintf(str, MSG_SIZ, "%s %s %s",
3822                                 gameInfo.white, _("vs."),  gameInfo.black);
3823                         DisplayTitle(str);
3824                         gameMode = IcsIdle;
3825                     } else {
3826                         /* Moves were history of an active game */
3827                         if (gameInfo.resultDetails != NULL) {
3828                             free(gameInfo.resultDetails);
3829                             gameInfo.resultDetails = NULL;
3830                         }
3831                     }
3832                     HistorySet(parseList, backwardMostMove,
3833                                forwardMostMove, currentMove-1);
3834                     DisplayMove(currentMove - 1);
3835                     if (started == STARTED_MOVES) next_out = i;
3836                     started = STARTED_NONE;
3837                     ics_getting_history = H_FALSE;
3838                     break;
3839
3840                   case STARTED_OBSERVE:
3841                     started = STARTED_NONE;
3842                     SendToICS(ics_prefix);
3843                     SendToICS("refresh\n");
3844                     break;
3845
3846                   default:
3847                     break;
3848                 }
3849                 if(bookHit) { // [HGM] book: simulate book reply
3850                     static char bookMove[MSG_SIZ]; // a bit generous?
3851
3852                     programStats.nodes = programStats.depth = programStats.time =
3853                     programStats.score = programStats.got_only_move = 0;
3854                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3855
3856                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3857                     strcat(bookMove, bookHit);
3858                     HandleMachineMove(bookMove, &first);
3859                 }
3860                 continue;
3861             }
3862
3863             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3864                  started == STARTED_HOLDINGS ||
3865                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3866                 /* Accumulate characters in move list or board */
3867                 parse[parse_pos++] = buf[i];
3868             }
3869
3870             /* Start of game messages.  Mostly we detect start of game
3871                when the first board image arrives.  On some versions
3872                of the ICS, though, we need to do a "refresh" after starting
3873                to observe in order to get the current board right away. */
3874             if (looking_at(buf, &i, "Adding game * to observation list")) {
3875                 started = STARTED_OBSERVE;
3876                 continue;
3877             }
3878
3879             /* Handle auto-observe */
3880             if (appData.autoObserve &&
3881                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3882                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3883                 char *player;
3884                 /* Choose the player that was highlighted, if any. */
3885                 if (star_match[0][0] == '\033' ||
3886                     star_match[1][0] != '\033') {
3887                     player = star_match[0];
3888                 } else {
3889                     player = star_match[2];
3890                 }
3891                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3892                         ics_prefix, StripHighlightAndTitle(player));
3893                 SendToICS(str);
3894
3895                 /* Save ratings from notify string */
3896                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3897                 player1Rating = string_to_rating(star_match[1]);
3898                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3899                 player2Rating = string_to_rating(star_match[3]);
3900
3901                 if (appData.debugMode)
3902                   fprintf(debugFP,
3903                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3904                           player1Name, player1Rating,
3905                           player2Name, player2Rating);
3906
3907                 continue;
3908             }
3909
3910             /* Deal with automatic examine mode after a game,
3911                and with IcsObserving -> IcsExamining transition */
3912             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3913                 looking_at(buf, &i, "has made you an examiner of game *")) {
3914
3915                 int gamenum = atoi(star_match[0]);
3916                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3917                     gamenum == ics_gamenum) {
3918                     /* We were already playing or observing this game;
3919                        no need to refetch history */
3920                     gameMode = IcsExamining;
3921                     if (pausing) {
3922                         pauseExamForwardMostMove = forwardMostMove;
3923                     } else if (currentMove < forwardMostMove) {
3924                         ForwardInner(forwardMostMove);
3925                     }
3926                 } else {
3927                     /* I don't think this case really can happen */
3928                     SendToICS(ics_prefix);
3929                     SendToICS("refresh\n");
3930                 }
3931                 continue;
3932             }
3933
3934             /* Error messages */
3935 //          if (ics_user_moved) {
3936             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3937                 if (looking_at(buf, &i, "Illegal move") ||
3938                     looking_at(buf, &i, "Not a legal move") ||
3939                     looking_at(buf, &i, "Your king is in check") ||
3940                     looking_at(buf, &i, "It isn't your turn") ||
3941                     looking_at(buf, &i, "It is not your move")) {
3942                     /* Illegal move */
3943                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3944                         currentMove = forwardMostMove-1;
3945                         DisplayMove(currentMove - 1); /* before DMError */
3946                         DrawPosition(FALSE, boards[currentMove]);
3947                         SwitchClocks(forwardMostMove-1); // [HGM] race
3948                         DisplayBothClocks();
3949                     }
3950                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3951                     ics_user_moved = 0;
3952                     continue;
3953                 }
3954             }
3955
3956             if (looking_at(buf, &i, "still have time") ||
3957                 looking_at(buf, &i, "not out of time") ||
3958                 looking_at(buf, &i, "either player is out of time") ||
3959                 looking_at(buf, &i, "has timeseal; checking")) {
3960                 /* We must have called his flag a little too soon */
3961                 whiteFlag = blackFlag = FALSE;
3962                 continue;
3963             }
3964
3965             if (looking_at(buf, &i, "added * seconds to") ||
3966                 looking_at(buf, &i, "seconds were added to")) {
3967                 /* Update the clocks */
3968                 SendToICS(ics_prefix);
3969                 SendToICS("refresh\n");
3970                 continue;
3971             }
3972
3973             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3974                 ics_clock_paused = TRUE;
3975                 StopClocks();
3976                 continue;
3977             }
3978
3979             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3980                 ics_clock_paused = FALSE;
3981                 StartClocks();
3982                 continue;
3983             }
3984
3985             /* Grab player ratings from the Creating: message.
3986                Note we have to check for the special case when
3987                the ICS inserts things like [white] or [black]. */
3988             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3989                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3990                 /* star_matches:
3991                    0    player 1 name (not necessarily white)
3992                    1    player 1 rating
3993                    2    empty, white, or black (IGNORED)
3994                    3    player 2 name (not necessarily black)
3995                    4    player 2 rating
3996
3997                    The names/ratings are sorted out when the game
3998                    actually starts (below).
3999                 */
4000                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
4001                 player1Rating = string_to_rating(star_match[1]);
4002                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
4003                 player2Rating = string_to_rating(star_match[4]);
4004
4005                 if (appData.debugMode)
4006                   fprintf(debugFP,
4007                           "Ratings from 'Creating:' %s %d, %s %d\n",
4008                           player1Name, player1Rating,
4009                           player2Name, player2Rating);
4010
4011                 continue;
4012             }
4013
4014             /* Improved generic start/end-of-game messages */
4015             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
4016                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
4017                 /* If tkind == 0: */
4018                 /* star_match[0] is the game number */
4019                 /*           [1] is the white player's name */
4020                 /*           [2] is the black player's name */
4021                 /* For end-of-game: */
4022                 /*           [3] is the reason for the game end */
4023                 /*           [4] is a PGN end game-token, preceded by " " */
4024                 /* For start-of-game: */
4025                 /*           [3] begins with "Creating" or "Continuing" */
4026                 /*           [4] is " *" or empty (don't care). */
4027                 int gamenum = atoi(star_match[0]);
4028                 char *whitename, *blackname, *why, *endtoken;
4029                 ChessMove endtype = EndOfFile;
4030
4031                 if (tkind == 0) {
4032                   whitename = star_match[1];
4033                   blackname = star_match[2];
4034                   why = star_match[3];
4035                   endtoken = star_match[4];
4036                 } else {
4037                   whitename = star_match[1];
4038                   blackname = star_match[3];
4039                   why = star_match[5];
4040                   endtoken = star_match[6];
4041                 }
4042
4043                 /* Game start messages */
4044                 if (strncmp(why, "Creating ", 9) == 0 ||
4045                     strncmp(why, "Continuing ", 11) == 0) {
4046                     gs_gamenum = gamenum;
4047                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
4048                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
4049                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
4050 #if ZIPPY
4051                     if (appData.zippyPlay) {
4052                         ZippyGameStart(whitename, blackname);
4053                     }
4054 #endif /*ZIPPY*/
4055                     partnerBoardValid = FALSE; // [HGM] bughouse
4056                     continue;
4057                 }
4058
4059                 /* Game end messages */
4060                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
4061                     ics_gamenum != gamenum) {
4062                     continue;
4063                 }
4064                 while (endtoken[0] == ' ') endtoken++;
4065                 switch (endtoken[0]) {
4066                   case '*':
4067                   default:
4068                     endtype = GameUnfinished;
4069                     break;
4070                   case '0':
4071                     endtype = BlackWins;
4072                     break;
4073                   case '1':
4074                     if (endtoken[1] == '/')
4075                       endtype = GameIsDrawn;
4076                     else
4077                       endtype = WhiteWins;
4078                     break;
4079                 }
4080                 GameEnds(endtype, why, GE_ICS);
4081 #if ZIPPY
4082                 if (appData.zippyPlay && first.initDone) {
4083                     ZippyGameEnd(endtype, why);
4084                     if (first.pr == NoProc) {
4085                       /* Start the next process early so that we'll
4086                          be ready for the next challenge */
4087                       StartChessProgram(&first);
4088                     }
4089                     /* Send "new" early, in case this command takes
4090                        a long time to finish, so that we'll be ready
4091                        for the next challenge. */
4092                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
4093                     Reset(TRUE, TRUE);
4094                 }
4095 #endif /*ZIPPY*/
4096                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
4097                 continue;
4098             }
4099
4100             if (looking_at(buf, &i, "Removing game * from observation") ||
4101                 looking_at(buf, &i, "no longer observing game *") ||
4102                 looking_at(buf, &i, "Game * (*) has no examiners")) {
4103                 if (gameMode == IcsObserving &&
4104                     atoi(star_match[0]) == ics_gamenum)
4105                   {
4106                       /* icsEngineAnalyze */
4107                       if (appData.icsEngineAnalyze) {
4108                             ExitAnalyzeMode();
4109                             ModeHighlight();
4110                       }
4111                       StopClocks();
4112                       gameMode = IcsIdle;
4113                       ics_gamenum = -1;
4114                       ics_user_moved = FALSE;
4115                   }
4116                 continue;
4117             }
4118
4119             if (looking_at(buf, &i, "no longer examining game *")) {
4120                 if (gameMode == IcsExamining &&
4121                     atoi(star_match[0]) == ics_gamenum)
4122                   {
4123                       gameMode = IcsIdle;
4124                       ics_gamenum = -1;
4125                       ics_user_moved = FALSE;
4126                   }
4127                 continue;
4128             }
4129
4130             /* Advance leftover_start past any newlines we find,
4131                so only partial lines can get reparsed */
4132             if (looking_at(buf, &i, "\n")) {
4133                 prevColor = curColor;
4134                 if (curColor != ColorNormal) {
4135                     if (oldi > next_out) {
4136                         SendToPlayer(&buf[next_out], oldi - next_out);
4137                         next_out = oldi;
4138                     }
4139                     Colorize(ColorNormal, FALSE);
4140                     curColor = ColorNormal;
4141                 }
4142                 if (started == STARTED_BOARD) {
4143                     started = STARTED_NONE;
4144                     parse[parse_pos] = NULLCHAR;
4145                     ParseBoard12(parse);
4146                     ics_user_moved = 0;
4147
4148                     /* Send premove here */
4149                     if (appData.premove) {
4150                       char str[MSG_SIZ];
4151                       if (currentMove == 0 &&
4152                           gameMode == IcsPlayingWhite &&
4153                           appData.premoveWhite) {
4154                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
4155                         if (appData.debugMode)
4156                           fprintf(debugFP, "Sending premove:\n");
4157                         SendToICS(str);
4158                       } else if (currentMove == 1 &&
4159                                  gameMode == IcsPlayingBlack &&
4160                                  appData.premoveBlack) {
4161                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
4162                         if (appData.debugMode)
4163                           fprintf(debugFP, "Sending premove:\n");
4164                         SendToICS(str);
4165                       } else if (gotPremove) {
4166                         gotPremove = 0;
4167                         ClearPremoveHighlights();
4168                         if (appData.debugMode)
4169                           fprintf(debugFP, "Sending premove:\n");
4170                           UserMoveEvent(premoveFromX, premoveFromY,
4171                                         premoveToX, premoveToY,
4172                                         premovePromoChar);
4173                       }
4174                     }
4175
4176                     /* Usually suppress following prompt */
4177                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
4178                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
4179                         if (looking_at(buf, &i, "*% ")) {
4180                             savingComment = FALSE;
4181                             suppressKibitz = 0;
4182                         }
4183                     }
4184                     next_out = i;
4185                 } else if (started == STARTED_HOLDINGS) {
4186                     int gamenum;
4187                     char new_piece[MSG_SIZ];
4188                     started = STARTED_NONE;
4189                     parse[parse_pos] = NULLCHAR;
4190                     if (appData.debugMode)
4191                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4192                                                         parse, currentMove);
4193                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4194                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4195                         if (gameInfo.variant == VariantNormal) {
4196                           /* [HGM] We seem to switch variant during a game!
4197                            * Presumably no holdings were displayed, so we have
4198                            * to move the position two files to the right to
4199                            * create room for them!
4200                            */
4201                           VariantClass newVariant;
4202                           switch(gameInfo.boardWidth) { // base guess on board width
4203                                 case 9:  newVariant = VariantShogi; break;
4204                                 case 10: newVariant = VariantGreat; break;
4205                                 default: newVariant = VariantCrazyhouse; break;
4206                           }
4207                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4208                           /* Get a move list just to see the header, which
4209                              will tell us whether this is really bug or zh */
4210                           if (ics_getting_history == H_FALSE) {
4211                             ics_getting_history = H_REQUESTED;
4212                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4213                             SendToICS(str);
4214                           }
4215                         }
4216                         new_piece[0] = NULLCHAR;
4217                         sscanf(parse, "game %d white [%s black [%s <- %s",
4218                                &gamenum, white_holding, black_holding,
4219                                new_piece);
4220                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4221                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4222                         /* [HGM] copy holdings to board holdings area */
4223                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4224                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4225                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4226 #if ZIPPY
4227                         if (appData.zippyPlay && first.initDone) {
4228                             ZippyHoldings(white_holding, black_holding,
4229                                           new_piece);
4230                         }
4231 #endif /*ZIPPY*/
4232                         if (tinyLayout || smallLayout) {
4233                             char wh[16], bh[16];
4234                             PackHolding(wh, white_holding);
4235                             PackHolding(bh, black_holding);
4236                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4237                                     gameInfo.white, gameInfo.black);
4238                         } else {
4239                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4240                                     gameInfo.white, white_holding, _("vs."),
4241                                     gameInfo.black, black_holding);
4242                         }
4243                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4244                         DrawPosition(FALSE, boards[currentMove]);
4245                         DisplayTitle(str);
4246                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4247                         sscanf(parse, "game %d white [%s black [%s <- %s",
4248                                &gamenum, white_holding, black_holding,
4249                                new_piece);
4250                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4251                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4252                         /* [HGM] copy holdings to partner-board holdings area */
4253                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4254                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4255                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4256                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4257                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4258                       }
4259                     }
4260                     /* Suppress following prompt */
4261                     if (looking_at(buf, &i, "*% ")) {
4262                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4263                         savingComment = FALSE;
4264                         suppressKibitz = 0;
4265                     }
4266                     next_out = i;
4267                 }
4268                 continue;
4269             }
4270
4271             i++;                /* skip unparsed character and loop back */
4272         }
4273
4274         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4275 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4276 //          SendToPlayer(&buf[next_out], i - next_out);
4277             started != STARTED_HOLDINGS && leftover_start > next_out) {
4278             SendToPlayer(&buf[next_out], leftover_start - next_out);
4279             next_out = i;
4280         }
4281
4282         leftover_len = buf_len - leftover_start;
4283         /* if buffer ends with something we couldn't parse,
4284            reparse it after appending the next read */
4285
4286     } else if (count == 0) {
4287         RemoveInputSource(isr);
4288         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4289     } else {
4290         DisplayFatalError(_("Error reading from ICS"), error, 1);
4291     }
4292 }
4293
4294
4295 /* Board style 12 looks like this:
4296
4297    <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
4298
4299  * The "<12> " is stripped before it gets to this routine.  The two
4300  * trailing 0's (flip state and clock ticking) are later addition, and
4301  * some chess servers may not have them, or may have only the first.
4302  * Additional trailing fields may be added in the future.
4303  */
4304
4305 #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"
4306
4307 #define RELATION_OBSERVING_PLAYED    0
4308 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4309 #define RELATION_PLAYING_MYMOVE      1
4310 #define RELATION_PLAYING_NOTMYMOVE  -1
4311 #define RELATION_EXAMINING           2
4312 #define RELATION_ISOLATED_BOARD     -3
4313 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4314
4315 void
4316 ParseBoard12 (char *string)
4317 {
4318 #if ZIPPY
4319     int i, takeback;
4320     char *bookHit = NULL; // [HGM] book
4321 #endif
4322     GameMode newGameMode;
4323     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
4324     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
4325     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4326     char to_play, board_chars[200];
4327     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4328     char black[32], white[32];
4329     Board board;
4330     int prevMove = currentMove;
4331     int ticking = 2;
4332     ChessMove moveType;
4333     int fromX, fromY, toX, toY;
4334     char promoChar;
4335     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4336     Boolean weird = FALSE, reqFlag = FALSE;
4337
4338     fromX = fromY = toX = toY = -1;
4339
4340     newGame = FALSE;
4341
4342     if (appData.debugMode)
4343       fprintf(debugFP, "Parsing board: %s\n", string);
4344
4345     move_str[0] = NULLCHAR;
4346     elapsed_time[0] = NULLCHAR;
4347     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4348         int  i = 0, j;
4349         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4350             if(string[i] == ' ') { ranks++; files = 0; }
4351             else files++;
4352             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4353             i++;
4354         }
4355         for(j = 0; j <i; j++) board_chars[j] = string[j];
4356         board_chars[i] = '\0';
4357         string += i + 1;
4358     }
4359     n = sscanf(string, PATTERN, &to_play, &double_push,
4360                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4361                &gamenum, white, black, &relation, &basetime, &increment,
4362                &white_stren, &black_stren, &white_time, &black_time,
4363                &moveNum, str, elapsed_time, move_str, &ics_flip,
4364                &ticking);
4365
4366     if (n < 21) {
4367         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4368         DisplayError(str, 0);
4369         return;
4370     }
4371
4372     /* Convert the move number to internal form */
4373     moveNum = (moveNum - 1) * 2;
4374     if (to_play == 'B') moveNum++;
4375     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4376       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4377                         0, 1);
4378       return;
4379     }
4380
4381     switch (relation) {
4382       case RELATION_OBSERVING_PLAYED:
4383       case RELATION_OBSERVING_STATIC:
4384         if (gamenum == -1) {
4385             /* Old ICC buglet */
4386             relation = RELATION_OBSERVING_STATIC;
4387         }
4388         newGameMode = IcsObserving;
4389         break;
4390       case RELATION_PLAYING_MYMOVE:
4391       case RELATION_PLAYING_NOTMYMOVE:
4392         newGameMode =
4393           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4394             IcsPlayingWhite : IcsPlayingBlack;
4395         soughtPending =FALSE; // [HGM] seekgraph: solve race condition
4396         break;
4397       case RELATION_EXAMINING:
4398         newGameMode = IcsExamining;
4399         break;
4400       case RELATION_ISOLATED_BOARD:
4401       default:
4402         /* Just display this board.  If user was doing something else,
4403            we will forget about it until the next board comes. */
4404         newGameMode = IcsIdle;
4405         break;
4406       case RELATION_STARTING_POSITION:
4407         newGameMode = gameMode;
4408         break;
4409     }
4410
4411     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
4412         gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games
4413          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4414       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4415       int fac = strchr(elapsed_time, '.') ? 1 : 1000;
4416       static int lastBgGame = -1;
4417       char *toSqr;
4418       for (k = 0; k < ranks; k++) {
4419         for (j = 0; j < files; j++)
4420           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4421         if(gameInfo.holdingsWidth > 1) {
4422              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4423              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4424         }
4425       }
4426       CopyBoard(partnerBoard, board);
4427       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4428         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4429         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4430       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4431       if(toSqr = strchr(str, '-')) {
4432         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4433         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4434       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4435       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4436       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4437       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4438       if(twoBoards) {
4439           DisplayWhiteClock(white_time*fac, to_play == 'W');
4440           DisplayBlackClock(black_time*fac, to_play != 'W');
4441           activePartner = to_play;
4442           if(gamenum != lastBgGame) {
4443               char buf[MSG_SIZ];
4444               snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black);
4445               DisplayTitle(buf);
4446           }
4447           lastBgGame = gamenum;
4448           activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
4449                       partnerUp = 0; flipView = !flipView; } // [HGM] dual
4450       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
4451                  (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
4452       if(!twoBoards) DisplayMessage(partnerStatus, "");
4453         partnerBoardValid = TRUE;
4454       return;
4455     }
4456
4457     if(appData.dualBoard && appData.bgObserve) {
4458         if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1)
4459             SendToICS(ics_prefix), SendToICS("pobserve\n");
4460         else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
4461             char buf[MSG_SIZ];
4462             snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white);
4463             SendToICS(buf);
4464         }
4465     }
4466
4467     /* Modify behavior for initial board display on move listing
4468        of wild games.
4469        */
4470     switch (ics_getting_history) {
4471       case H_FALSE:
4472       case H_REQUESTED:
4473         break;
4474       case H_GOT_REQ_HEADER:
4475       case H_GOT_UNREQ_HEADER:
4476         /* This is the initial position of the current game */
4477         gamenum = ics_gamenum;
4478         moveNum = 0;            /* old ICS bug workaround */
4479         if (to_play == 'B') {
4480           startedFromSetupPosition = TRUE;
4481           blackPlaysFirst = TRUE;
4482           moveNum = 1;
4483           if (forwardMostMove == 0) forwardMostMove = 1;
4484           if (backwardMostMove == 0) backwardMostMove = 1;
4485           if (currentMove == 0) currentMove = 1;
4486         }
4487         newGameMode = gameMode;
4488         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4489         break;
4490       case H_GOT_UNWANTED_HEADER:
4491         /* This is an initial board that we don't want */
4492         return;
4493       case H_GETTING_MOVES:
4494         /* Should not happen */
4495         DisplayError(_("Error gathering move list: extra board"), 0);
4496         ics_getting_history = H_FALSE;
4497         return;
4498     }
4499
4500    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4501                                         move_str[1] == '@' && !gameInfo.holdingsWidth ||
4502                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4503      /* [HGM] We seem to have switched variant unexpectedly
4504       * Try to guess new variant from board size
4505       */
4506           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4507           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4508           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4509           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4510           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4511           if(ranks == 10 && files == 10) newVariant = VariantGrand; else
4512           if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
4513           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4514           /* Get a move list just to see the header, which
4515              will tell us whether this is really bug or zh */
4516           if (ics_getting_history == H_FALSE) {
4517             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4518             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4519             SendToICS(str);
4520           }
4521     }
4522
4523     /* Take action if this is the first board of a new game, or of a
4524        different game than is currently being displayed.  */
4525     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4526         relation == RELATION_ISOLATED_BOARD) {
4527
4528         /* Forget the old game and get the history (if any) of the new one */
4529         if (gameMode != BeginningOfGame) {
4530           Reset(TRUE, TRUE);
4531         }
4532         newGame = TRUE;
4533         if (appData.autoRaiseBoard) BoardToTop();
4534         prevMove = -3;
4535         if (gamenum == -1) {
4536             newGameMode = IcsIdle;
4537         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4538                    appData.getMoveList && !reqFlag) {
4539             /* Need to get game history */
4540             ics_getting_history = H_REQUESTED;
4541             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4542             SendToICS(str);
4543         }
4544
4545         /* Initially flip the board to have black on the bottom if playing
4546            black or if the ICS flip flag is set, but let the user change
4547            it with the Flip View button. */
4548         flipView = appData.autoFlipView ?
4549           (newGameMode == IcsPlayingBlack) || ics_flip :
4550           appData.flipView;
4551
4552         /* Done with values from previous mode; copy in new ones */
4553         gameMode = newGameMode;
4554         ModeHighlight();
4555         ics_gamenum = gamenum;
4556         if (gamenum == gs_gamenum) {
4557             int klen = strlen(gs_kind);
4558             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4559             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4560             gameInfo.event = StrSave(str);
4561         } else {
4562             gameInfo.event = StrSave("ICS game");
4563         }
4564         gameInfo.site = StrSave(appData.icsHost);
4565         gameInfo.date = PGNDate();
4566         gameInfo.round = StrSave("-");
4567         gameInfo.white = StrSave(white);
4568         gameInfo.black = StrSave(black);
4569         timeControl = basetime * 60 * 1000;
4570         timeControl_2 = 0;
4571         timeIncrement = increment * 1000;
4572         movesPerSession = 0;
4573         gameInfo.timeControl = TimeControlTagValue();
4574         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4575   if (appData.debugMode) {
4576     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4577     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4578     setbuf(debugFP, NULL);
4579   }
4580
4581         gameInfo.outOfBook = NULL;
4582
4583         /* Do we have the ratings? */
4584         if (strcmp(player1Name, white) == 0 &&
4585             strcmp(player2Name, black) == 0) {
4586             if (appData.debugMode)
4587               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4588                       player1Rating, player2Rating);
4589             gameInfo.whiteRating = player1Rating;
4590             gameInfo.blackRating = player2Rating;
4591         } else if (strcmp(player2Name, white) == 0 &&
4592                    strcmp(player1Name, black) == 0) {
4593             if (appData.debugMode)
4594               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4595                       player2Rating, player1Rating);
4596             gameInfo.whiteRating = player2Rating;
4597             gameInfo.blackRating = player1Rating;
4598         }
4599         player1Name[0] = player2Name[0] = NULLCHAR;
4600
4601         /* Silence shouts if requested */
4602         if (appData.quietPlay &&
4603             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4604             SendToICS(ics_prefix);
4605             SendToICS("set shout 0\n");
4606         }
4607     }
4608
4609     /* Deal with midgame name changes */
4610     if (!newGame) {
4611         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4612             if (gameInfo.white) free(gameInfo.white);
4613             gameInfo.white = StrSave(white);
4614         }
4615         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4616             if (gameInfo.black) free(gameInfo.black);
4617             gameInfo.black = StrSave(black);
4618         }
4619     }
4620
4621     /* Throw away game result if anything actually changes in examine mode */
4622     if (gameMode == IcsExamining && !newGame) {
4623         gameInfo.result = GameUnfinished;
4624         if (gameInfo.resultDetails != NULL) {
4625             free(gameInfo.resultDetails);
4626             gameInfo.resultDetails = NULL;
4627         }
4628     }
4629
4630     /* In pausing && IcsExamining mode, we ignore boards coming
4631        in if they are in a different variation than we are. */
4632     if (pauseExamInvalid) return;
4633     if (pausing && gameMode == IcsExamining) {
4634         if (moveNum <= pauseExamForwardMostMove) {
4635             pauseExamInvalid = TRUE;
4636             forwardMostMove = pauseExamForwardMostMove;
4637             return;
4638         }
4639     }
4640
4641   if (appData.debugMode) {
4642     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4643   }
4644     /* Parse the board */
4645     for (k = 0; k < ranks; k++) {
4646       for (j = 0; j < files; j++)
4647         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4648       if(gameInfo.holdingsWidth > 1) {
4649            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4650            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4651       }
4652     }
4653     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4654       board[5][BOARD_RGHT+1] = WhiteAngel;
4655       board[6][BOARD_RGHT+1] = WhiteMarshall;
4656       board[1][0] = BlackMarshall;
4657       board[2][0] = BlackAngel;
4658       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4659     }
4660     CopyBoard(boards[moveNum], board);
4661     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4662     if (moveNum == 0) {
4663         startedFromSetupPosition =
4664           !CompareBoards(board, initialPosition);
4665         if(startedFromSetupPosition)
4666             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4667     }
4668
4669     /* [HGM] Set castling rights. Take the outermost Rooks,
4670        to make it also work for FRC opening positions. Note that board12
4671        is really defective for later FRC positions, as it has no way to
4672        indicate which Rook can castle if they are on the same side of King.
4673        For the initial position we grant rights to the outermost Rooks,
4674        and remember thos rights, and we then copy them on positions
4675        later in an FRC game. This means WB might not recognize castlings with
4676        Rooks that have moved back to their original position as illegal,
4677        but in ICS mode that is not its job anyway.
4678     */
4679     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4680     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4681
4682         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4683             if(board[0][i] == WhiteRook) j = i;
4684         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4685         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4686             if(board[0][i] == WhiteRook) j = i;
4687         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4688         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4689             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4690         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4691         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4692             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4693         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4694
4695         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4696         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4697         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4698             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4699         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4700             if(board[BOARD_HEIGHT-1][k] == bKing)
4701                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4702         if(gameInfo.variant == VariantTwoKings) {
4703             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4704             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4705             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4706         }
4707     } else { int r;
4708         r = boards[moveNum][CASTLING][0] = initialRights[0];
4709         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4710         r = boards[moveNum][CASTLING][1] = initialRights[1];
4711         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4712         r = boards[moveNum][CASTLING][3] = initialRights[3];
4713         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4714         r = boards[moveNum][CASTLING][4] = initialRights[4];
4715         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4716         /* wildcastle kludge: always assume King has rights */
4717         r = boards[moveNum][CASTLING][2] = initialRights[2];
4718         r = boards[moveNum][CASTLING][5] = initialRights[5];
4719     }
4720     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4721     boards[moveNum][EP_STATUS] = EP_NONE;
4722     if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE;
4723     if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE;
4724     if(double_push !=  -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT;
4725
4726
4727     if (ics_getting_history == H_GOT_REQ_HEADER ||
4728         ics_getting_history == H_GOT_UNREQ_HEADER) {
4729         /* This was an initial position from a move list, not
4730            the current position */
4731         return;
4732     }
4733
4734     /* Update currentMove and known move number limits */
4735     newMove = newGame || moveNum > forwardMostMove;
4736
4737     if (newGame) {
4738         forwardMostMove = backwardMostMove = currentMove = moveNum;
4739         if (gameMode == IcsExamining && moveNum == 0) {
4740           /* Workaround for ICS limitation: we are not told the wild
4741              type when starting to examine a game.  But if we ask for
4742              the move list, the move list header will tell us */
4743             ics_getting_history = H_REQUESTED;
4744             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4745             SendToICS(str);
4746         }
4747     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4748                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4749 #if ZIPPY
4750         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4751         /* [HGM] applied this also to an engine that is silently watching        */
4752         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4753             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4754             gameInfo.variant == currentlyInitializedVariant) {
4755           takeback = forwardMostMove - moveNum;
4756           for (i = 0; i < takeback; i++) {
4757             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4758             SendToProgram("undo\n", &first);
4759           }
4760         }
4761 #endif
4762
4763         forwardMostMove = moveNum;
4764         if (!pausing || currentMove > forwardMostMove)
4765           currentMove = forwardMostMove;
4766     } else {
4767         /* New part of history that is not contiguous with old part */
4768         if (pausing && gameMode == IcsExamining) {
4769             pauseExamInvalid = TRUE;
4770             forwardMostMove = pauseExamForwardMostMove;
4771             return;
4772         }
4773         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4774 #if ZIPPY
4775             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4776                 // [HGM] when we will receive the move list we now request, it will be
4777                 // fed to the engine from the first move on. So if the engine is not
4778                 // in the initial position now, bring it there.
4779                 InitChessProgram(&first, 0);
4780             }
4781 #endif
4782             ics_getting_history = H_REQUESTED;
4783             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4784             SendToICS(str);
4785         }
4786         forwardMostMove = backwardMostMove = currentMove = moveNum;
4787     }
4788
4789     /* Update the clocks */
4790     if (strchr(elapsed_time, '.')) {
4791       /* Time is in ms */
4792       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4793       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4794     } else {
4795       /* Time is in seconds */
4796       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4797       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4798     }
4799
4800
4801 #if ZIPPY
4802     if (appData.zippyPlay && newGame &&
4803         gameMode != IcsObserving && gameMode != IcsIdle &&
4804         gameMode != IcsExamining)
4805       ZippyFirstBoard(moveNum, basetime, increment);
4806 #endif
4807
4808     /* Put the move on the move list, first converting
4809        to canonical algebraic form. */
4810     if (moveNum > 0) {
4811   if (appData.debugMode) {
4812     int f = forwardMostMove;
4813     fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4814             boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4815             boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4816     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4817     fprintf(debugFP, "moveNum = %d\n", moveNum);
4818     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4819     setbuf(debugFP, NULL);
4820   }
4821         if (moveNum <= backwardMostMove) {
4822             /* We don't know what the board looked like before
4823                this move.  Punt. */
4824           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4825             strcat(parseList[moveNum - 1], " ");
4826             strcat(parseList[moveNum - 1], elapsed_time);
4827             moveList[moveNum - 1][0] = NULLCHAR;
4828         } else if (strcmp(move_str, "none") == 0) {
4829             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4830             /* Again, we don't know what the board looked like;
4831                this is really the start of the game. */
4832             parseList[moveNum - 1][0] = NULLCHAR;
4833             moveList[moveNum - 1][0] = NULLCHAR;
4834             backwardMostMove = moveNum;
4835             startedFromSetupPosition = TRUE;
4836             fromX = fromY = toX = toY = -1;
4837         } else {
4838           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4839           //                 So we parse the long-algebraic move string in stead of the SAN move
4840           int valid; char buf[MSG_SIZ], *prom;
4841
4842           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4843                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4844           // str looks something like "Q/a1-a2"; kill the slash
4845           if(str[1] == '/')
4846             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4847           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4848           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4849                 strcat(buf, prom); // long move lacks promo specification!
4850           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4851                 if(appData.debugMode)
4852                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4853                 safeStrCpy(move_str, buf, MSG_SIZ);
4854           }
4855           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4856                                 &fromX, &fromY, &toX, &toY, &promoChar)
4857                || ParseOneMove(buf, moveNum - 1, &moveType,
4858                                 &fromX, &fromY, &toX, &toY, &promoChar);
4859           // end of long SAN patch
4860           if (valid) {
4861             (void) CoordsToAlgebraic(boards[moveNum - 1],
4862                                      PosFlags(moveNum - 1),
4863                                      fromY, fromX, toY, toX, promoChar,
4864                                      parseList[moveNum-1]);
4865             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4866               case MT_NONE:
4867               case MT_STALEMATE:
4868               default:
4869                 break;
4870               case MT_CHECK:
4871                 if(!IS_SHOGI(gameInfo.variant))
4872                     strcat(parseList[moveNum - 1], "+");
4873                 break;
4874               case MT_CHECKMATE:
4875               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4876                 strcat(parseList[moveNum - 1], "#");
4877                 break;
4878             }
4879             strcat(parseList[moveNum - 1], " ");
4880             strcat(parseList[moveNum - 1], elapsed_time);
4881             /* currentMoveString is set as a side-effect of ParseOneMove */
4882             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4883             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4884             strcat(moveList[moveNum - 1], "\n");
4885
4886             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4887                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4888               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4889                 ChessSquare old, new = boards[moveNum][k][j];
4890                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4891                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4892                   if(old == new) continue;
4893                   if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
4894                   else if(new == WhiteWazir || new == BlackWazir) {
4895                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4896                            boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
4897                       else boards[moveNum][k][j] = old; // preserve type of Gold
4898                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4899                       boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
4900               }
4901           } else {
4902             /* Move from ICS was illegal!?  Punt. */
4903             if (appData.debugMode) {
4904               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4905               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4906             }
4907             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4908             strcat(parseList[moveNum - 1], " ");
4909             strcat(parseList[moveNum - 1], elapsed_time);
4910             moveList[moveNum - 1][0] = NULLCHAR;
4911             fromX = fromY = toX = toY = -1;
4912           }
4913         }
4914   if (appData.debugMode) {
4915     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4916     setbuf(debugFP, NULL);
4917   }
4918
4919 #if ZIPPY
4920         /* Send move to chess program (BEFORE animating it). */
4921         if (appData.zippyPlay && !newGame && newMove &&
4922            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4923
4924             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4925                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4926                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4927                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4928                             move_str);
4929                     DisplayError(str, 0);
4930                 } else {
4931                     if (first.sendTime) {
4932                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4933                     }
4934                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4935                     if (firstMove && !bookHit) {
4936                         firstMove = FALSE;
4937                         if (first.useColors) {
4938                           SendToProgram(gameMode == IcsPlayingWhite ?
4939                                         "white\ngo\n" :
4940                                         "black\ngo\n", &first);
4941                         } else {
4942                           SendToProgram("go\n", &first);
4943                         }
4944                         first.maybeThinking = TRUE;
4945                     }
4946                 }
4947             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4948               if (moveList[moveNum - 1][0] == NULLCHAR) {
4949                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4950                 DisplayError(str, 0);
4951               } else {
4952                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4953                 SendMoveToProgram(moveNum - 1, &first);
4954               }
4955             }
4956         }
4957 #endif
4958     }
4959
4960     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4961         /* If move comes from a remote source, animate it.  If it
4962            isn't remote, it will have already been animated. */
4963         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4964             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4965         }
4966         if (!pausing && appData.highlightLastMove) {
4967             SetHighlights(fromX, fromY, toX, toY);
4968         }
4969     }
4970
4971     /* Start the clocks */
4972     whiteFlag = blackFlag = FALSE;
4973     appData.clockMode = !(basetime == 0 && increment == 0);
4974     if (ticking == 0) {
4975       ics_clock_paused = TRUE;
4976       StopClocks();
4977     } else if (ticking == 1) {
4978       ics_clock_paused = FALSE;
4979     }
4980     if (gameMode == IcsIdle ||
4981         relation == RELATION_OBSERVING_STATIC ||
4982         relation == RELATION_EXAMINING ||
4983         ics_clock_paused)
4984       DisplayBothClocks();
4985     else
4986       StartClocks();
4987
4988     /* Display opponents and material strengths */
4989     if (gameInfo.variant != VariantBughouse &&
4990         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4991         if (tinyLayout || smallLayout) {
4992             if(gameInfo.variant == VariantNormal)
4993               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4994                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4995                     basetime, increment);
4996             else
4997               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4998                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4999                     basetime, increment, (int) gameInfo.variant);
5000         } else {
5001             if(gameInfo.variant == VariantNormal)
5002               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
5003                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5004                     basetime, increment);
5005             else
5006               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
5007                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
5008                     basetime, increment, VariantName(gameInfo.variant));
5009         }
5010         DisplayTitle(str);
5011   if (appData.debugMode) {
5012     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
5013   }
5014     }
5015
5016
5017     /* Display the board */
5018     if (!pausing && !appData.noGUI) {
5019
5020       if (appData.premove)
5021           if (!gotPremove ||
5022              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
5023              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
5024               ClearPremoveHighlights();
5025
5026       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
5027         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
5028       DrawPosition(j, boards[currentMove]);
5029
5030       DisplayMove(moveNum - 1);
5031       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
5032             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
5033               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
5034         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
5035       }
5036     }
5037
5038     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
5039 #if ZIPPY
5040     if(bookHit) { // [HGM] book: simulate book reply
5041         static char bookMove[MSG_SIZ]; // a bit generous?
5042
5043         programStats.nodes = programStats.depth = programStats.time =
5044         programStats.score = programStats.got_only_move = 0;
5045         sprintf(programStats.movelist, "%s (xbook)", bookHit);
5046
5047         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
5048         strcat(bookMove, bookHit);
5049         HandleMachineMove(bookMove, &first);
5050     }
5051 #endif
5052 }
5053
5054 void
5055 GetMoveListEvent ()
5056 {
5057     char buf[MSG_SIZ];
5058     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
5059         ics_getting_history = H_REQUESTED;
5060         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
5061         SendToICS(buf);
5062     }
5063 }
5064
5065 void
5066 SendToBoth (char *msg)
5067 {   // to make it easy to keep two engines in step in dual analysis
5068     SendToProgram(msg, &first);
5069     if(second.analyzing) SendToProgram(msg, &second);
5070 }
5071
5072 void
5073 AnalysisPeriodicEvent (int force)
5074 {
5075     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
5076          && !force) || !appData.periodicUpdates)
5077       return;
5078
5079     /* Send . command to Crafty to collect stats */
5080     SendToBoth(".\n");
5081
5082     /* Don't send another until we get a response (this makes
5083        us stop sending to old Crafty's which don't understand
5084        the "." command (sending illegal cmds resets node count & time,
5085        which looks bad)) */
5086     programStats.ok_to_send = 0;
5087 }
5088
5089 void
5090 ics_update_width (int new_width)
5091 {
5092         ics_printf("set width %d\n", new_width);
5093 }
5094
5095 void
5096 SendMoveToProgram (int moveNum, ChessProgramState *cps)
5097 {
5098     char buf[MSG_SIZ];
5099
5100     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
5101         if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
5102             sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
5103             SendToProgram(buf, cps);
5104             return;
5105         }
5106         // null move in variant where engine does not understand it (for analysis purposes)
5107         SendBoard(cps, moveNum + 1); // send position after move in stead.
5108         return;
5109     }
5110     if (cps->useUsermove) {
5111       SendToProgram("usermove ", cps);
5112     }
5113     if (cps->useSAN) {
5114       char *space;
5115       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
5116         int len = space - parseList[moveNum];
5117         memcpy(buf, parseList[moveNum], len);
5118         buf[len++] = '\n';
5119         buf[len] = NULLCHAR;
5120       } else {
5121         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
5122       }
5123       SendToProgram(buf, cps);
5124     } else {
5125       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
5126         AlphaRank(moveList[moveNum], 4);
5127         SendToProgram(moveList[moveNum], cps);
5128         AlphaRank(moveList[moveNum], 4); // and back
5129       } else
5130       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
5131        * the engine. It would be nice to have a better way to identify castle
5132        * moves here. */
5133       if(appData.fischerCastling && cps->useOOCastle) {
5134         int fromX = moveList[moveNum][0] - AAA;
5135         int fromY = moveList[moveNum][1] - ONE;
5136         int toX = moveList[moveNum][2] - AAA;
5137         int toY = moveList[moveNum][3] - ONE;
5138         if((boards[moveNum][fromY][fromX] == WhiteKing
5139             && boards[moveNum][toY][toX] == WhiteRook)
5140            || (boards[moveNum][fromY][fromX] == BlackKing
5141                && boards[moveNum][toY][toX] == BlackRook)) {
5142           if(toX > fromX) SendToProgram("O-O\n", cps);
5143           else SendToProgram("O-O-O\n", cps);
5144         }
5145         else SendToProgram(moveList[moveNum], cps);
5146       } else
5147       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
5148         char *m = moveList[moveNum];
5149         if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
5150           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5151                                                m[2], m[3] - '0',
5152                                                m[5], m[6] - '0',
5153                                                m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
5154         else
5155           snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
5156                                                m[5], m[6] - '0',
5157                                                m[5], m[6] - '0',
5158                                                m[2], m[3] - '0');
5159           SendToProgram(buf, cps);
5160       } else
5161       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
5162         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
5163           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
5164           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
5165                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5166         } else
5167           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
5168                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
5169         SendToProgram(buf, cps);
5170       }
5171       else SendToProgram(moveList[moveNum], cps);
5172       /* End of additions by Tord */
5173     }
5174
5175     /* [HGM] setting up the opening has brought engine in force mode! */
5176     /*       Send 'go' if we are in a mode where machine should play. */
5177     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
5178         (gameMode == TwoMachinesPlay   ||
5179 #if ZIPPY
5180          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
5181 #endif
5182          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
5183         SendToProgram("go\n", cps);
5184   if (appData.debugMode) {
5185     fprintf(debugFP, "(extra)\n");
5186   }
5187     }
5188     setboardSpoiledMachineBlack = 0;
5189 }
5190
5191 void
5192 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
5193 {
5194     char user_move[MSG_SIZ];
5195     char suffix[4];
5196
5197     if(gameInfo.variant == VariantSChess && promoChar) {
5198         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
5199         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
5200     } else suffix[0] = NULLCHAR;
5201
5202     switch (moveType) {
5203       default:
5204         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
5205                 (int)moveType, fromX, fromY, toX, toY);
5206         DisplayError(user_move + strlen("say "), 0);
5207         break;
5208       case WhiteKingSideCastle:
5209       case BlackKingSideCastle:
5210       case WhiteQueenSideCastleWild:
5211       case BlackQueenSideCastleWild:
5212       /* PUSH Fabien */
5213       case WhiteHSideCastleFR:
5214       case BlackHSideCastleFR:
5215       /* POP Fabien */
5216         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
5217         break;
5218       case WhiteQueenSideCastle:
5219       case BlackQueenSideCastle:
5220       case WhiteKingSideCastleWild:
5221       case BlackKingSideCastleWild:
5222       /* PUSH Fabien */
5223       case WhiteASideCastleFR:
5224       case BlackASideCastleFR:
5225       /* POP Fabien */
5226         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
5227         break;
5228       case WhiteNonPromotion:
5229       case BlackNonPromotion:
5230         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5231         break;
5232       case WhitePromotion:
5233       case BlackPromotion:
5234         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
5235            gameInfo.variant == VariantMakruk)
5236           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5237                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5238                 PieceToChar(WhiteFerz));
5239         else if(gameInfo.variant == VariantGreat)
5240           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
5241                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5242                 PieceToChar(WhiteMan));
5243         else
5244           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5245                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5246                 promoChar);
5247         break;
5248       case WhiteDrop:
5249       case BlackDrop:
5250       drop:
5251         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5252                  ToUpper(PieceToChar((ChessSquare) fromX)),
5253                  AAA + toX, ONE + toY);
5254         break;
5255       case IllegalMove:  /* could be a variant we don't quite understand */
5256         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5257       case NormalMove:
5258       case WhiteCapturesEnPassant:
5259       case BlackCapturesEnPassant:
5260         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5261                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5262         break;
5263     }
5264     SendToICS(user_move);
5265     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5266         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5267 }
5268
5269 void
5270 UploadGameEvent ()
5271 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5272     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5273     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5274     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5275       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5276       return;
5277     }
5278     if(gameMode != IcsExamining) { // is this ever not the case?
5279         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5280
5281         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5282           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5283         } else { // on FICS we must first go to general examine mode
5284           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5285         }
5286         if(gameInfo.variant != VariantNormal) {
5287             // try figure out wild number, as xboard names are not always valid on ICS
5288             for(i=1; i<=36; i++) {
5289               snprintf(buf, MSG_SIZ, "wild/%d", i);
5290                 if(StringToVariant(buf) == gameInfo.variant) break;
5291             }
5292             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5293             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5294             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5295         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5296         SendToICS(ics_prefix);
5297         SendToICS(buf);
5298         if(startedFromSetupPosition || backwardMostMove != 0) {
5299           fen = PositionToFEN(backwardMostMove, NULL, 1);
5300           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5301             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5302             SendToICS(buf);
5303           } else { // FICS: everything has to set by separate bsetup commands
5304             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5305             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5306             SendToICS(buf);
5307             if(!WhiteOnMove(backwardMostMove)) {
5308                 SendToICS("bsetup tomove black\n");
5309             }
5310             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5311             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5312             SendToICS(buf);
5313             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5314             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5315             SendToICS(buf);
5316             i = boards[backwardMostMove][EP_STATUS];
5317             if(i >= 0) { // set e.p.
5318               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5319                 SendToICS(buf);
5320             }
5321             bsetup++;
5322           }
5323         }
5324       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5325             SendToICS("bsetup done\n"); // switch to normal examining.
5326     }
5327     for(i = backwardMostMove; i<last; i++) {
5328         char buf[20];
5329         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5330         if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
5331             int len = strlen(moveList[i]);
5332             snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
5333             if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
5334         }
5335         SendToICS(buf);
5336     }
5337     SendToICS(ics_prefix);
5338     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5339 }
5340
5341 int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
5342 int legNr = 1;
5343
5344 void
5345 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
5346 {
5347     if (rf == DROP_RANK) {
5348       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5349       sprintf(move, "%c@%c%c\n",
5350                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5351     } else {
5352         if (promoChar == 'x' || promoChar == NULLCHAR) {
5353           sprintf(move, "%c%c%c%c\n",
5354                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5355           if(killX >= 0 && killY >= 0) {
5356             sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
5357             if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
5358           }
5359         } else {
5360             sprintf(move, "%c%c%c%c%c\n",
5361                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5362         }
5363     }
5364 }
5365
5366 void
5367 ProcessICSInitScript (FILE *f)
5368 {
5369     char buf[MSG_SIZ];
5370
5371     while (fgets(buf, MSG_SIZ, f)) {
5372         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5373     }
5374
5375     fclose(f);
5376 }
5377
5378
5379 static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
5380 int dragging;
5381 static ClickType lastClickType;
5382
5383 int
5384 Partner (ChessSquare *p)
5385 { // change piece into promotion partner if one shogi-promotes to the other
5386   ChessSquare partner = promoPartner[*p];
5387   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
5388   *p = partner;
5389   return 1;
5390 }
5391
5392 void
5393 Sweep (int step)
5394 {
5395     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5396     static int toggleFlag;
5397     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5398     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5399     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5400     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5401     if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
5402     if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
5403     do {
5404         if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
5405         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5406         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5407         else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
5408         else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
5409         if(!step) step = -1;
5410     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
5411             !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
5412             promoRestrict[0] ? !strchr(promoRestrict, ToUpper(PieceToChar(promoSweep))) : // if choice set available, use it 
5413             promoSweep == pawn ||
5414             appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
5415             (promoSweep == WhiteLion || promoSweep == BlackLion)));
5416     if(toX >= 0) {
5417         int victim = boards[currentMove][toY][toX];
5418         boards[currentMove][toY][toX] = promoSweep;
5419         DrawPosition(FALSE, boards[currentMove]);
5420         boards[currentMove][toY][toX] = victim;
5421     } else
5422     ChangeDragPiece(promoSweep);
5423 }
5424
5425 int
5426 PromoScroll (int x, int y)
5427 {
5428   int step = 0;
5429
5430   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5431   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5432   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5433   if(!step) return FALSE;
5434   lastX = x; lastY = y;
5435   if((promoSweep < BlackPawn) == flipView) step = -step;
5436   if(step > 0) selectFlag = 1;
5437   if(!selectFlag) Sweep(step);
5438   return FALSE;
5439 }
5440
5441 void
5442 NextPiece (int step)
5443 {
5444     ChessSquare piece = boards[currentMove][toY][toX];
5445     do {
5446         pieceSweep -= step;
5447         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5448         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5449         if(!step) step = -1;
5450     } while(PieceToChar(pieceSweep) == '.');
5451     boards[currentMove][toY][toX] = pieceSweep;
5452     DrawPosition(FALSE, boards[currentMove]);
5453     boards[currentMove][toY][toX] = piece;
5454 }
5455 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5456 void
5457 AlphaRank (char *move, int n)
5458 {
5459 //    char *p = move, c; int x, y;
5460
5461     if (appData.debugMode) {
5462         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5463     }
5464
5465     if(move[1]=='*' &&
5466        move[2]>='0' && move[2]<='9' &&
5467        move[3]>='a' && move[3]<='x'    ) {
5468         move[1] = '@';
5469         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5470         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5471     } else
5472     if(move[0]>='0' && move[0]<='9' &&
5473        move[1]>='a' && move[1]<='x' &&
5474        move[2]>='0' && move[2]<='9' &&
5475        move[3]>='a' && move[3]<='x'    ) {
5476         /* input move, Shogi -> normal */
5477         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5478         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5479         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5480         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5481     } else
5482     if(move[1]=='@' &&
5483        move[3]>='0' && move[3]<='9' &&
5484        move[2]>='a' && move[2]<='x'    ) {
5485         move[1] = '*';
5486         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5487         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5488     } else
5489     if(
5490        move[0]>='a' && move[0]<='x' &&
5491        move[3]>='0' && move[3]<='9' &&
5492        move[2]>='a' && move[2]<='x'    ) {
5493          /* output move, normal -> Shogi */
5494         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5495         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5496         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5497         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5498         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5499     }
5500     if (appData.debugMode) {
5501         fprintf(debugFP, "   out = '%s'\n", move);
5502     }
5503 }
5504
5505 char yy_textstr[8000];
5506
5507 /* Parser for moves from gnuchess, ICS, or user typein box */
5508 Boolean
5509 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5510 {
5511     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5512
5513     switch (*moveType) {
5514       case WhitePromotion:
5515       case BlackPromotion:
5516       case WhiteNonPromotion:
5517       case BlackNonPromotion:
5518       case NormalMove:
5519       case FirstLeg:
5520       case WhiteCapturesEnPassant:
5521       case BlackCapturesEnPassant:
5522       case WhiteKingSideCastle:
5523       case WhiteQueenSideCastle:
5524       case BlackKingSideCastle:
5525       case BlackQueenSideCastle:
5526       case WhiteKingSideCastleWild:
5527       case WhiteQueenSideCastleWild:
5528       case BlackKingSideCastleWild:
5529       case BlackQueenSideCastleWild:
5530       /* Code added by Tord: */
5531       case WhiteHSideCastleFR:
5532       case WhiteASideCastleFR:
5533       case BlackHSideCastleFR:
5534       case BlackASideCastleFR:
5535       /* End of code added by Tord */
5536       case IllegalMove:         /* bug or odd chess variant */
5537         if(currentMoveString[1] == '@') { // illegal drop
5538           *fromX = WhiteOnMove(moveNum) ?
5539             (int) CharToPiece(ToUpper(currentMoveString[0])) :
5540             (int) CharToPiece(ToLower(currentMoveString[0]));
5541           goto drop;
5542         }
5543         *fromX = currentMoveString[0] - AAA;
5544         *fromY = currentMoveString[1] - ONE;
5545         *toX = currentMoveString[2] - AAA;
5546         *toY = currentMoveString[3] - ONE;
5547         *promoChar = currentMoveString[4];
5548         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5549             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5550     if (appData.debugMode) {
5551         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5552     }
5553             *fromX = *fromY = *toX = *toY = 0;
5554             return FALSE;
5555         }
5556         if (appData.testLegality) {
5557           return (*moveType != IllegalMove);
5558         } else {
5559           return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5560                          // [HGM] lion: if this is a double move we are less critical
5561                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5562         }
5563
5564       case WhiteDrop:
5565       case BlackDrop:
5566         *fromX = *moveType == WhiteDrop ?
5567           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5568           (int) CharToPiece(ToLower(currentMoveString[0]));
5569       drop:
5570         *fromY = DROP_RANK;
5571         *toX = currentMoveString[2] - AAA;
5572         *toY = currentMoveString[3] - ONE;
5573         *promoChar = NULLCHAR;
5574         return TRUE;
5575
5576       case AmbiguousMove:
5577       case ImpossibleMove:
5578       case EndOfFile:
5579       case ElapsedTime:
5580       case Comment:
5581       case PGNTag:
5582       case NAG:
5583       case WhiteWins:
5584       case BlackWins:
5585       case GameIsDrawn:
5586       default:
5587     if (appData.debugMode) {
5588         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5589     }
5590         /* bug? */
5591         *fromX = *fromY = *toX = *toY = 0;
5592         *promoChar = NULLCHAR;
5593         return FALSE;
5594     }
5595 }
5596
5597 Boolean pushed = FALSE;
5598 char *lastParseAttempt;
5599
5600 void
5601 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5602 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5603   int fromX, fromY, toX, toY; char promoChar;
5604   ChessMove moveType;
5605   Boolean valid;
5606   int nr = 0;
5607
5608   lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
5609   if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
5610     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5611     pushed = TRUE;
5612   }
5613   endPV = forwardMostMove;
5614   do {
5615     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5616     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5617     lastParseAttempt = pv;
5618     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5619     if(!valid && nr == 0 &&
5620        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5621         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5622         // Hande case where played move is different from leading PV move
5623         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5624         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5625         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5626         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5627           endPV += 2; // if position different, keep this
5628           moveList[endPV-1][0] = fromX + AAA;
5629           moveList[endPV-1][1] = fromY + ONE;
5630           moveList[endPV-1][2] = toX + AAA;
5631           moveList[endPV-1][3] = toY + ONE;
5632           parseList[endPV-1][0] = NULLCHAR;
5633           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5634         }
5635       }
5636     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5637     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5638     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5639     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5640         valid++; // allow comments in PV
5641         continue;
5642     }
5643     nr++;
5644     if(endPV+1 > framePtr) break; // no space, truncate
5645     if(!valid) break;
5646     endPV++;
5647     CopyBoard(boards[endPV], boards[endPV-1]);
5648     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5649     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5650     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5651     CoordsToAlgebraic(boards[endPV - 1],
5652                              PosFlags(endPV - 1),
5653                              fromY, fromX, toY, toX, promoChar,
5654                              parseList[endPV - 1]);
5655   } while(valid);
5656   if(atEnd == 2) return; // used hidden, for PV conversion
5657   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5658   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5659   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5660                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5661   DrawPosition(TRUE, boards[currentMove]);
5662 }
5663
5664 int
5665 MultiPV (ChessProgramState *cps, int kind)
5666 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5667         int i;
5668         for(i=0; i<cps->nrOptions; i++) {
5669             char *s = cps->option[i].name;
5670             if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
5671             if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
5672                           && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
5673         }
5674         return -1;
5675 }
5676
5677 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
5678 static int multi, pv_margin;
5679 static ChessProgramState *activeCps;
5680
5681 Boolean
5682 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
5683 {
5684         int startPV, lineStart, origIndex = index;
5685         char *p, buf2[MSG_SIZ];
5686         ChessProgramState *cps = (pane ? &second : &first);
5687
5688         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5689         lastX = x; lastY = y;
5690         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5691         lineStart = startPV = index;
5692         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5693         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5694         index = startPV;
5695         do{ while(buf[index] && buf[index] != '\n') index++;
5696         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5697         buf[index] = 0;
5698         if(lineStart == 0 && gameMode == AnalyzeMode) {
5699             int n = 0;
5700             if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
5701             if(n == 0) { // click not on "fewer" or "more"
5702                 if((multi = -2 - MultiPV(cps, 2)) >= 0) {
5703                     pv_margin = cps->option[multi].value;
5704                     activeCps = cps; // non-null signals margin adjustment
5705                 }
5706             } else if((multi = MultiPV(cps, 1)) >= 0) {
5707                 n += cps->option[multi].value; if(n < 1) n = 1;
5708                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5709                 if(cps->option[multi].value != n) SendToProgram(buf2, cps);
5710                 cps->option[multi].value = n;
5711                 *start = *end = 0;
5712                 return FALSE;
5713             }
5714         } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
5715                 ExcludeClick(origIndex - lineStart);
5716                 return FALSE;
5717         } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
5718                 Collapse(origIndex - lineStart);
5719                 return FALSE;
5720         }
5721         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5722         *start = startPV; *end = index-1;
5723         extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
5724         return TRUE;
5725 }
5726
5727 char *
5728 PvToSAN (char *pv)
5729 {
5730         static char buf[10*MSG_SIZ];
5731         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5732         *buf = NULLCHAR;
5733         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
5734         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5735         for(i = forwardMostMove; i<endPV; i++){
5736             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5737             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5738             k += strlen(buf+k);
5739         }
5740         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5741         if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
5742         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5743         endPV = savedEnd;
5744         return buf;
5745 }
5746
5747 Boolean
5748 LoadPV (int x, int y)
5749 { // called on right mouse click to load PV
5750   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5751   lastX = x; lastY = y;
5752   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5753   extendGame = FALSE;
5754   return TRUE;
5755 }
5756
5757 void
5758 UnLoadPV ()
5759 {
5760   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5761   if(activeCps) {
5762     if(pv_margin != activeCps->option[multi].value) {
5763       char buf[MSG_SIZ];
5764       snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
5765       SendToProgram(buf, activeCps);
5766       activeCps->option[multi].value = pv_margin;
5767     }
5768     activeCps = NULL;
5769     return;
5770   }
5771   if(endPV < 0) return;
5772   if(appData.autoCopyPV) CopyFENToClipboard();
5773   endPV = -1;
5774   if(extendGame && currentMove > forwardMostMove) {
5775         Boolean saveAnimate = appData.animate;
5776         if(pushed) {
5777             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5778                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5779             } else storedGames--; // abandon shelved tail of original game
5780         }
5781         pushed = FALSE;
5782         forwardMostMove = currentMove;
5783         currentMove = oldFMM;
5784         appData.animate = FALSE;
5785         ToNrEvent(forwardMostMove);
5786         appData.animate = saveAnimate;
5787   }
5788   currentMove = forwardMostMove;
5789   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5790   ClearPremoveHighlights();
5791   DrawPosition(TRUE, boards[currentMove]);
5792 }
5793
5794 void
5795 MovePV (int x, int y, int h)
5796 { // step through PV based on mouse coordinates (called on mouse move)
5797   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5798
5799   if(activeCps) { // adjusting engine's multi-pv margin
5800     if(x > lastX) pv_margin++; else
5801     if(x < lastX) pv_margin -= (pv_margin > 0);
5802     if(x != lastX) {
5803       char buf[MSG_SIZ];
5804       snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
5805       DisplayMessage(buf, "");
5806     }
5807     lastX = x;
5808     return;
5809   }
5810   // we must somehow check if right button is still down (might be released off board!)
5811   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5812   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5813   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5814   if(!step) return;
5815   lastX = x; lastY = y;
5816
5817   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5818   if(endPV < 0) return;
5819   if(y < margin) step = 1; else
5820   if(y > h - margin) step = -1;
5821   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5822   currentMove += step;
5823   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5824   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5825                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5826   DrawPosition(FALSE, boards[currentMove]);
5827 }
5828
5829
5830 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5831 // All positions will have equal probability, but the current method will not provide a unique
5832 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5833 #define DARK 1
5834 #define LITE 2
5835 #define ANY 3
5836
5837 int squaresLeft[4];
5838 int piecesLeft[(int)BlackPawn];
5839 int seed, nrOfShuffles;
5840
5841 void
5842 GetPositionNumber ()
5843 {       // sets global variable seed
5844         int i;
5845
5846         seed = appData.defaultFrcPosition;
5847         if(seed < 0) { // randomize based on time for negative FRC position numbers
5848                 for(i=0; i<50; i++) seed += random();
5849                 seed = random() ^ random() >> 8 ^ random() << 8;
5850                 if(seed<0) seed = -seed;
5851         }
5852 }
5853
5854 int
5855 put (Board board, int pieceType, int rank, int n, int shade)
5856 // put the piece on the (n-1)-th empty squares of the given shade
5857 {
5858         int i;
5859
5860         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5861                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5862                         board[rank][i] = (ChessSquare) pieceType;
5863                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5864                         squaresLeft[ANY]--;
5865                         piecesLeft[pieceType]--;
5866                         return i;
5867                 }
5868         }
5869         return -1;
5870 }
5871
5872
5873 void
5874 AddOnePiece (Board board, int pieceType, int rank, int shade)
5875 // calculate where the next piece goes, (any empty square), and put it there
5876 {
5877         int i;
5878
5879         i = seed % squaresLeft[shade];
5880         nrOfShuffles *= squaresLeft[shade];
5881         seed /= squaresLeft[shade];
5882         put(board, pieceType, rank, i, shade);
5883 }
5884
5885 void
5886 AddTwoPieces (Board board, int pieceType, int rank)
5887 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5888 {
5889         int i, n=squaresLeft[ANY], j=n-1, k;
5890
5891         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5892         i = seed % k;  // pick one
5893         nrOfShuffles *= k;
5894         seed /= k;
5895         while(i >= j) i -= j--;
5896         j = n - 1 - j; i += j;
5897         put(board, pieceType, rank, j, ANY);
5898         put(board, pieceType, rank, i, ANY);
5899 }
5900
5901 void
5902 SetUpShuffle (Board board, int number)
5903 {
5904         int i, p, first=1;
5905
5906         GetPositionNumber(); nrOfShuffles = 1;
5907
5908         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5909         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5910         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5911
5912         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5913
5914         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5915             p = (int) board[0][i];
5916             if(p < (int) BlackPawn) piecesLeft[p] ++;
5917             board[0][i] = EmptySquare;
5918         }
5919
5920         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5921             // shuffles restricted to allow normal castling put KRR first
5922             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5923                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5924             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5925                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5926             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5927                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5928             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5929                 put(board, WhiteRook, 0, 0, ANY);
5930             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5931         }
5932
5933         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5934             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5935             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5936                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5937                 while(piecesLeft[p] >= 2) {
5938                     AddOnePiece(board, p, 0, LITE);
5939                     AddOnePiece(board, p, 0, DARK);
5940                 }
5941                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5942             }
5943
5944         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5945             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5946             // but we leave King and Rooks for last, to possibly obey FRC restriction
5947             if(p == (int)WhiteRook) continue;
5948             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5949             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5950         }
5951
5952         // now everything is placed, except perhaps King (Unicorn) and Rooks
5953
5954         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5955             // Last King gets castling rights
5956             while(piecesLeft[(int)WhiteUnicorn]) {
5957                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5958                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5959             }
5960
5961             while(piecesLeft[(int)WhiteKing]) {
5962                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5963                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5964             }
5965
5966
5967         } else {
5968             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5969             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5970         }
5971
5972         // Only Rooks can be left; simply place them all
5973         while(piecesLeft[(int)WhiteRook]) {
5974                 i = put(board, WhiteRook, 0, 0, ANY);
5975                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5976                         if(first) {
5977                                 first=0;
5978                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5979                         }
5980                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5981                 }
5982         }
5983         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5984             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5985         }
5986
5987         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5988 }
5989
5990 int
5991 ptclen (const char *s, char *escapes)
5992 {
5993     int n = 0;
5994     if(!*escapes) return strlen(s);
5995     while(*s) n += (*s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
5996     return n;
5997 }
5998
5999 static int pieceOrder[] = {
6000   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, // P N B R Q F E A C W M
6001  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // O H I J G D V L S U Lion
6002  45, 23, 24, 25, 26, 27, 28, 29, 46, 31, 32, // Sword Zebra Camel Tower Wolf Dragon Duck Axe Leopard Gnu Cub
6003  44, 51, 56, 57, 58, 59, 60, 61, 62, 63, 34, // Whale Pegasus Wizard Copper Iron Viking Flag Amazon Wheel Shield Claw
6004  33, 55, 53, 42, 37, 48, 39, 40, 41, 22, 30, // +P +N =B =R +L +S +E +Ph +Kn Butterfly Hat
6005  38, 43, 35, 36, 49, 47, 52, 50, 54, 64, 65 // +V +M =H =D Princess HSword +GB HCrown Wheer Shierd King
6006 };
6007
6008 int
6009 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
6010 /* [HGM] moved here from winboard.c because of its general usefulness */
6011 /*       Basically a safe strcpy that uses the last character as King */
6012 {
6013     int result = FALSE; int NrPieces;
6014     unsigned char partner[EmptySquare];
6015
6016     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
6017                     && NrPieces >= 12 && !(NrPieces&1)) {
6018         int i, ii, j = 0; /* [HGM] Accept even length from 12 to 88 */
6019
6020         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
6021         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6022             char *p, c=0;
6023             i = pieceOrder[ii];
6024             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6025             table[i] = map[j++];
6026             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6027             if(c) partner[i] = table[i], table[i] = c;
6028         }
6029         table[(int) WhiteKing]  = map[j++];
6030         for( ii=0; ii<NrPieces/2-1; ii++ ) {
6031             char *p, c=0;
6032             i = WHITE_TO_BLACK pieceOrder[ii];
6033             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
6034             table[i] = map[j++];
6035             if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
6036             if(c) partner[i] = table[i], table[i] = c;
6037         }
6038         table[(int) BlackKing]  = map[j++];
6039
6040
6041         if(*escapes) { // set up promotion pairing
6042             for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
6043             // pieceToChar entirely filled, so we can look up specified partners
6044             for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
6045                 int c = table[i];
6046                 if(c == '^' || c == '-') { // has specified partner
6047                     int p;
6048                     for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
6049                     if(c == '^') table[i] = '+';
6050                     if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
6051                 } else if(c == '*') promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
6052             }
6053         }
6054
6055         result = TRUE;
6056     }
6057
6058     return result;
6059 }
6060
6061 int
6062 SetCharTable (unsigned char *table, const char * map)
6063 {
6064     return SetCharTableEsc(table, map, "");
6065 }
6066
6067 void
6068 Prelude (Board board)
6069 {       // [HGM] superchess: random selection of exo-pieces
6070         int i, j, k; ChessSquare p;
6071         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
6072
6073         GetPositionNumber(); // use FRC position number
6074
6075         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
6076             SetCharTable(pieceToChar, appData.pieceToCharTable);
6077             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
6078                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
6079         }
6080
6081         j = seed%4;                 seed /= 4;
6082         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6083         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6084         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6085         j = seed%3 + (seed%3 >= j); seed /= 3;
6086         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
6087         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6088         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6089         j = seed%3;                 seed /= 3;
6090         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6091         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6092         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6093         j = seed%2 + (seed%2 >= j); seed /= 2;
6094         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
6095         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
6096         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
6097         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
6098         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
6099         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
6100         put(board, exoPieces[0],    0, 0, ANY);
6101         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
6102 }
6103
6104 void
6105 InitPosition (int redraw)
6106 {
6107     ChessSquare (* pieces)[BOARD_FILES];
6108     int i, j, pawnRow=1, pieceRows=1, overrule,
6109     oldx = gameInfo.boardWidth,
6110     oldy = gameInfo.boardHeight,
6111     oldh = gameInfo.holdingsWidth;
6112     static int oldv;
6113
6114     if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
6115
6116     /* [AS] Initialize pv info list [HGM] and game status */
6117     {
6118         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
6119             pvInfoList[i].depth = 0;
6120             boards[i][EP_STATUS] = EP_NONE;
6121             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
6122         }
6123
6124         initialRulePlies = 0; /* 50-move counter start */
6125
6126         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
6127         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
6128     }
6129
6130
6131     /* [HGM] logic here is completely changed. In stead of full positions */
6132     /* the initialized data only consist of the two backranks. The switch */
6133     /* selects which one we will use, which is than copied to the Board   */
6134     /* initialPosition, which for the rest is initialized by Pawns and    */
6135     /* empty squares. This initial position is then copied to boards[0],  */
6136     /* possibly after shuffling, so that it remains available.            */
6137
6138     gameInfo.holdingsWidth = 0; /* default board sizes */
6139     gameInfo.boardWidth    = 8;
6140     gameInfo.boardHeight   = 8;
6141     gameInfo.holdingsSize  = 0;
6142     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
6143     for(i=0; i<BOARD_FILES-6; i++)
6144       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
6145     initialPosition[EP_STATUS] = EP_NONE;
6146     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
6147     SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
6148     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
6149          SetCharTable(pieceNickName, appData.pieceNickNames);
6150     else SetCharTable(pieceNickName, "............");
6151     pieces = FIDEArray;
6152
6153     switch (gameInfo.variant) {
6154     case VariantFischeRandom:
6155       shuffleOpenings = TRUE;
6156       appData.fischerCastling = TRUE;
6157     default:
6158       break;
6159     case VariantShatranj:
6160       pieces = ShatranjArray;
6161       nrCastlingRights = 0;
6162       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
6163       break;
6164     case VariantMakruk:
6165       pieces = makrukArray;
6166       nrCastlingRights = 0;
6167       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
6168       break;
6169     case VariantASEAN:
6170       pieces = aseanArray;
6171       nrCastlingRights = 0;
6172       SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk");
6173       break;
6174     case VariantTwoKings:
6175       pieces = twoKingsArray;
6176       break;
6177     case VariantGrand:
6178       pieces = GrandArray;
6179       nrCastlingRights = 0;
6180       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6181       gameInfo.boardWidth = 10;
6182       gameInfo.boardHeight = 10;
6183       gameInfo.holdingsSize = 7;
6184       break;
6185     case VariantCapaRandom:
6186       shuffleOpenings = TRUE;
6187       appData.fischerCastling = TRUE;
6188     case VariantCapablanca:
6189       pieces = CapablancaArray;
6190       gameInfo.boardWidth = 10;
6191       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6192       break;
6193     case VariantGothic:
6194       pieces = GothicArray;
6195       gameInfo.boardWidth = 10;
6196       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
6197       break;
6198     case VariantSChess:
6199       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
6200       gameInfo.holdingsSize = 7;
6201       for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
6202       break;
6203     case VariantJanus:
6204       pieces = JanusArray;
6205       gameInfo.boardWidth = 10;
6206       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
6207       nrCastlingRights = 6;
6208         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6209         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6210         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
6211         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6212         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6213         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
6214       break;
6215     case VariantFalcon:
6216       pieces = FalconArray;
6217       gameInfo.boardWidth = 10;
6218       SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
6219       break;
6220     case VariantXiangqi:
6221       pieces = XiangqiArray;
6222       gameInfo.boardWidth  = 9;
6223       gameInfo.boardHeight = 10;
6224       nrCastlingRights = 0;
6225       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
6226       break;
6227     case VariantShogi:
6228       pieces = ShogiArray;
6229       gameInfo.boardWidth  = 9;
6230       gameInfo.boardHeight = 9;
6231       gameInfo.holdingsSize = 7;
6232       nrCastlingRights = 0;
6233       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
6234       break;
6235     case VariantChu:
6236       pieces = ChuArray; pieceRows = 3;
6237       gameInfo.boardWidth  = 12;
6238       gameInfo.boardHeight = 12;
6239       nrCastlingRights = 0;
6240       SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
6241                                    "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
6242       break;
6243     case VariantCourier:
6244       pieces = CourierArray;
6245       gameInfo.boardWidth  = 12;
6246       nrCastlingRights = 0;
6247       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
6248       break;
6249     case VariantKnightmate:
6250       pieces = KnightmateArray;
6251       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
6252       break;
6253     case VariantSpartan:
6254       pieces = SpartanArray;
6255       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
6256       break;
6257     case VariantLion:
6258       pieces = lionArray;
6259       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
6260       break;
6261     case VariantChuChess:
6262       pieces = ChuChessArray;
6263       gameInfo.boardWidth = 10;
6264       gameInfo.boardHeight = 10;
6265       SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
6266       break;
6267     case VariantFairy:
6268       pieces = fairyArray;
6269       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
6270       break;
6271     case VariantGreat:
6272       pieces = GreatArray;
6273       gameInfo.boardWidth = 10;
6274       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
6275       gameInfo.holdingsSize = 8;
6276       break;
6277     case VariantSuper:
6278       pieces = FIDEArray;
6279       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
6280       gameInfo.holdingsSize = 8;
6281       startedFromSetupPosition = TRUE;
6282       break;
6283     case VariantCrazyhouse:
6284     case VariantBughouse:
6285       pieces = FIDEArray;
6286       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
6287       gameInfo.holdingsSize = 5;
6288       break;
6289     case VariantWildCastle:
6290       pieces = FIDEArray;
6291       /* !!?shuffle with kings guaranteed to be on d or e file */
6292       shuffleOpenings = 1;
6293       break;
6294     case VariantNoCastle:
6295       pieces = FIDEArray;
6296       nrCastlingRights = 0;
6297       /* !!?unconstrained back-rank shuffle */
6298       shuffleOpenings = 1;
6299       break;
6300     }
6301
6302     overrule = 0;
6303     if(appData.NrFiles >= 0) {
6304         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
6305         gameInfo.boardWidth = appData.NrFiles;
6306     }
6307     if(appData.NrRanks >= 0) {
6308         gameInfo.boardHeight = appData.NrRanks;
6309     }
6310     if(appData.holdingsSize >= 0) {
6311         i = appData.holdingsSize;
6312         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
6313         gameInfo.holdingsSize = i;
6314     }
6315     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
6316     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
6317         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
6318
6319     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
6320     if(pawnRow < 1) pawnRow = 1;
6321     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
6322        gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
6323     if(gameInfo.variant == VariantChu) pawnRow = 3;
6324
6325     /* User pieceToChar list overrules defaults */
6326     if(appData.pieceToCharTable != NULL)
6327         SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
6328
6329     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
6330
6331         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
6332             s = (ChessSquare) 0; /* account holding counts in guard band */
6333         for( i=0; i<BOARD_HEIGHT; i++ )
6334             initialPosition[i][j] = s;
6335
6336         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
6337         initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
6338         initialPosition[pawnRow][j] = WhitePawn;
6339         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
6340         if(gameInfo.variant == VariantXiangqi) {
6341             if(j&1) {
6342                 initialPosition[pawnRow][j] =
6343                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
6344                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
6345                    initialPosition[2][j] = WhiteCannon;
6346                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
6347                 }
6348             }
6349         }
6350         if(gameInfo.variant == VariantChu) {
6351              if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3)
6352                initialPosition[pawnRow+1][j] = WhiteCobra,
6353                initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra;
6354              for(i=1; i<pieceRows; i++) {
6355                initialPosition[i][j] = pieces[2*i][j-gameInfo.holdingsWidth];
6356                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
6357              }
6358         }
6359         if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6360             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
6361                initialPosition[0][j] = WhiteRook;
6362                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
6363             }
6364         }
6365         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
6366     }
6367     if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
6368     if( (gameInfo.variant == VariantShogi) && !overrule ) {
6369
6370             j=BOARD_LEFT+1;
6371             initialPosition[1][j] = WhiteBishop;
6372             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
6373             j=BOARD_RGHT-2;
6374             initialPosition[1][j] = WhiteRook;
6375             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
6376     }
6377
6378     if( nrCastlingRights == -1) {
6379         /* [HGM] Build normal castling rights (must be done after board sizing!) */
6380         /*       This sets default castling rights from none to normal corners   */
6381         /* Variants with other castling rights must set them themselves above    */
6382         nrCastlingRights = 6;
6383
6384         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
6385         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
6386         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
6387         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
6388         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
6389         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
6390      }
6391
6392      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
6393      if(gameInfo.variant == VariantGreat) { // promotion commoners
6394         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
6395         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
6396         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
6397         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
6398      }
6399      if( gameInfo.variant == VariantSChess ) {
6400       initialPosition[1][0] = BlackMarshall;
6401       initialPosition[2][0] = BlackAngel;
6402       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
6403       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
6404       initialPosition[1][1] = initialPosition[2][1] =
6405       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
6406      }
6407   if (appData.debugMode) {
6408     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
6409   }
6410     if(shuffleOpenings) {
6411         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
6412         startedFromSetupPosition = TRUE;
6413     }
6414     if(startedFromPositionFile) {
6415       /* [HGM] loadPos: use PositionFile for every new game */
6416       CopyBoard(initialPosition, filePosition);
6417       for(i=0; i<nrCastlingRights; i++)
6418           initialRights[i] = filePosition[CASTLING][i];
6419       startedFromSetupPosition = TRUE;
6420     }
6421
6422     CopyBoard(boards[0], initialPosition);
6423
6424     if(oldx != gameInfo.boardWidth ||
6425        oldy != gameInfo.boardHeight ||
6426        oldv != gameInfo.variant ||
6427        oldh != gameInfo.holdingsWidth
6428                                          )
6429             InitDrawingSizes(-2 ,0);
6430
6431     oldv = gameInfo.variant;
6432     if (redraw)
6433       DrawPosition(TRUE, boards[currentMove]);
6434 }
6435
6436 void
6437 SendBoard (ChessProgramState *cps, int moveNum)
6438 {
6439     char message[MSG_SIZ];
6440
6441     if (cps->useSetboard) {
6442       char* fen = PositionToFEN(moveNum, cps->fenOverride, 1);
6443       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6444       SendToProgram(message, cps);
6445       free(fen);
6446
6447     } else {
6448       ChessSquare *bp;
6449       int i, j, left=0, right=BOARD_WIDTH;
6450       /* Kludge to set black to move, avoiding the troublesome and now
6451        * deprecated "black" command.
6452        */
6453       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6454         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6455
6456       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6457
6458       SendToProgram("edit\n", cps);
6459       SendToProgram("#\n", cps);
6460       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6461         bp = &boards[moveNum][i][left];
6462         for (j = left; j < right; j++, bp++) {
6463           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6464           if ((int) *bp < (int) BlackPawn) {
6465             if(j == BOARD_RGHT+1)
6466                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6467             else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
6468             if(message[0] == '+' || message[0] == '~') {
6469               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6470                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6471                         AAA + j, ONE + i - '0');
6472             }
6473             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6474                 message[1] = BOARD_RGHT   - 1 - j + '1';
6475                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6476             }
6477             SendToProgram(message, cps);
6478           }
6479         }
6480       }
6481
6482       SendToProgram("c\n", cps);
6483       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6484         bp = &boards[moveNum][i][left];
6485         for (j = left; j < right; j++, bp++) {
6486           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6487           if (((int) *bp != (int) EmptySquare)
6488               && ((int) *bp >= (int) BlackPawn)) {
6489             if(j == BOARD_LEFT-2)
6490                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6491             else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
6492                     AAA + j, ONE + i - '0');
6493             if(message[0] == '+' || message[0] == '~') {
6494               snprintf(message, MSG_SIZ,"%c%c%d+\n",
6495                         PieceToChar((ChessSquare)(DEMOTED(*bp))),
6496                         AAA + j, ONE + i - '0');
6497             }
6498             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6499                 message[1] = BOARD_RGHT   - 1 - j + '1';
6500                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6501             }
6502             SendToProgram(message, cps);
6503           }
6504         }
6505       }
6506
6507       SendToProgram(".\n", cps);
6508     }
6509     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6510 }
6511
6512 char exclusionHeader[MSG_SIZ];
6513 int exCnt, excludePtr;
6514 typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion;
6515 static Exclusion excluTab[200];
6516 static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves
6517
6518 static void
6519 WriteMap (int s)
6520 {
6521     int j;
6522     for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s;
6523     exclusionHeader[19] = s ? '-' : '+'; // update tail state
6524 }
6525
6526 static void
6527 ClearMap ()
6528 {
6529     safeStrCpy(exclusionHeader, "exclude: none best +tail                                          \n", MSG_SIZ);
6530     excludePtr = 24; exCnt = 0;
6531     WriteMap(0);
6532 }
6533
6534 static void
6535 UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6536 {   // search given move in table of header moves, to know where it is listed (and add if not there), and update state
6537     char buf[2*MOVE_LEN], *p;
6538     Exclusion *e = excluTab;
6539     int i;
6540     for(i=0; i<exCnt; i++)
6541         if(e[i].ff == fromX && e[i].fr == fromY &&
6542            e[i].tf == toX   && e[i].tr == toY && e[i].pc == promoChar) break;
6543     if(i == exCnt) { // was not in exclude list; add it
6544         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, buf);
6545         if(strlen(exclusionHeader + excludePtr) < strlen(buf)) { // no space to write move
6546             if(state != exclusionHeader[19]) exclusionHeader[19] = '*'; // tail is now in mixed state
6547             return; // abort
6548         }
6549         e[i].ff = fromX; e[i].fr = fromY; e[i].tf = toX; e[i].tr = toY; e[i].pc = promoChar;
6550         excludePtr++; e[i].mark = excludePtr++;
6551         for(p=buf; *p; p++) exclusionHeader[excludePtr++] = *p; // copy move
6552         exCnt++;
6553     }
6554     exclusionHeader[e[i].mark] = state;
6555 }
6556
6557 static int
6558 ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state)
6559 {   // include or exclude the given move, as specified by state ('+' or '-'), or toggle
6560     char buf[MSG_SIZ];
6561     int j, k;
6562     ChessMove moveType;
6563     if((signed char)promoChar == -1) { // kludge to indicate best move
6564         if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV
6565             return 1; // if unparsable, abort
6566     }
6567     // update exclusion map (resolving toggle by consulting existing state)
6568     k=(BOARD_FILES*fromY+fromX)*BOARD_RANKS*BOARD_FILES + (BOARD_FILES*toY+toX);
6569     j = k%8; k >>= 3;
6570     if(state == '*') state = (excludeMap[k] & 1<<j ? '+' : '-'); // toggle
6571     if(state == '-' && !promoChar) // only non-promotions get marked as excluded, to allow exclusion of under-promotions
6572          excludeMap[k] |=   1<<j;
6573     else excludeMap[k] &= ~(1<<j);
6574     // update header
6575     UpdateExcludeHeader(fromY, fromX, toY, toX, promoChar, state);
6576     // inform engine
6577     snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex");
6578     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8);
6579     SendToBoth(buf);
6580     return (state == '+');
6581 }
6582
6583 static void
6584 ExcludeClick (int index)
6585 {
6586     int i, j;
6587     Exclusion *e = excluTab;
6588     if(index < 25) { // none, best or tail clicked
6589         if(index < 13) { // none: include all
6590             WriteMap(0); // clear map
6591             for(i=0; i<exCnt; i++) exclusionHeader[excluTab[i].mark] = '+'; // and moves
6592             SendToBoth("include all\n"); // and inform engine
6593         } else if(index > 18) { // tail
6594             if(exclusionHeader[19] == '-') { // tail was excluded
6595                 SendToBoth("include all\n");
6596                 WriteMap(0); // clear map completely
6597                 // now re-exclude selected moves
6598                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '-')
6599                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '-');
6600             } else { // tail was included or in mixed state
6601                 SendToBoth("exclude all\n");
6602                 WriteMap(0xFF); // fill map completely
6603                 // now re-include selected moves
6604                 j = 0; // count them
6605                 for(i=0; i<exCnt; i++) if(exclusionHeader[e[i].mark] == '+')
6606                     ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, '+'), j++;
6607                 if(!j) ExcludeOneMove(0, 0, 0, 0, -1, '+'); // if no moves were selected, keep best
6608             }
6609         } else { // best
6610             ExcludeOneMove(0, 0, 0, 0, -1, '-'); // exclude it
6611         }
6612     } else {
6613         for(i=0; i<exCnt; i++) if(i == exCnt-1 || excluTab[i+1].mark > index) {
6614             char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!)
6615             ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+');
6616             break;
6617         }
6618     }
6619 }
6620
6621 ChessSquare
6622 DefaultPromoChoice (int white)
6623 {
6624     ChessSquare result;
6625     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6626        gameInfo.variant == VariantMakruk)
6627         result = WhiteFerz; // no choice
6628     else if(gameInfo.variant == VariantASEAN)
6629         result = WhiteRook; // no choice
6630     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6631         result= WhiteKing; // in Suicide Q is the last thing we want
6632     else if(gameInfo.variant == VariantSpartan)
6633         result = white ? WhiteQueen : WhiteAngel;
6634     else result = WhiteQueen;
6635     if(!white) result = WHITE_TO_BLACK result;
6636     return result;
6637 }
6638
6639 static int autoQueen; // [HGM] oneclick
6640
6641 int
6642 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6643 {
6644     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6645     /* [HGM] add Shogi promotions */
6646     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6647     ChessSquare piece, partner;
6648     ChessMove moveType;
6649     Boolean premove;
6650
6651     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6652     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6653
6654     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6655       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6656         return FALSE;
6657
6658     piece = boards[currentMove][fromY][fromX];
6659     if(gameInfo.variant == VariantChu) {
6660         promotionZoneSize = BOARD_HEIGHT/3;
6661         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
6662     } else if(gameInfo.variant == VariantShogi) {
6663         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
6664         highestPromotingPiece = (int)WhiteAlfil;
6665     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
6666         promotionZoneSize = 3;
6667     }
6668
6669     // Treat Lance as Pawn when it is not representing Amazon or Lance
6670     if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
6671         if(piece == WhiteLance) piece = WhitePawn; else
6672         if(piece == BlackLance) piece = BlackPawn;
6673     }
6674
6675     // next weed out all moves that do not touch the promotion zone at all
6676     if((int)piece >= BlackPawn) {
6677         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6678              return FALSE;
6679         if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
6680         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6681     } else {
6682         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6683            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6684         if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
6685              return FALSE;
6686     }
6687
6688     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6689
6690     // weed out mandatory Shogi promotions
6691     if(gameInfo.variant == VariantShogi) {
6692         if(piece >= BlackPawn) {
6693             if(toY == 0 && piece == BlackPawn ||
6694                toY == 0 && piece == BlackQueen ||
6695                toY <= 1 && piece == BlackKnight) {
6696                 *promoChoice = '+';
6697                 return FALSE;
6698             }
6699         } else {
6700             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6701                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6702                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6703                 *promoChoice = '+';
6704                 return FALSE;
6705             }
6706         }
6707     }
6708
6709     // weed out obviously illegal Pawn moves
6710     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6711         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6712         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6713         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6714         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6715         // note we are not allowed to test for valid (non-)capture, due to premove
6716     }
6717
6718     // we either have a choice what to promote to, or (in Shogi) whether to promote
6719     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6720        gameInfo.variant == VariantMakruk) {
6721         ChessSquare p=BlackFerz;  // no choice
6722         while(p < EmptySquare) {  //but make sure we use piece that exists
6723             *promoChoice = PieceToChar(p++);
6724             if(*promoChoice != '.') break;
6725         }
6726         return FALSE;
6727     }
6728     // no sense asking what we must promote to if it is going to explode...
6729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6731         return FALSE;
6732     }
6733     // give caller the default choice even if we will not make it
6734     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6735     partner = piece; // pieces can promote if the pieceToCharTable says so
6736     if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
6737     else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
6738     if(        sweepSelect && gameInfo.variant != VariantGreat
6739                            && gameInfo.variant != VariantGrand
6740                            && gameInfo.variant != VariantSuper) return FALSE;
6741     if(autoQueen) return FALSE; // predetermined
6742
6743     // suppress promotion popup on illegal moves that are not premoves
6744     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6745               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6746     if(appData.testLegality && !premove) {
6747         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6748                         fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
6749         if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
6750         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6751             return FALSE;
6752     }
6753
6754     return TRUE;
6755 }
6756
6757 int
6758 InPalace (int row, int column)
6759 {   /* [HGM] for Xiangqi */
6760     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6761          column < (BOARD_WIDTH + 4)/2 &&
6762          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6763     return FALSE;
6764 }
6765
6766 int
6767 PieceForSquare (int x, int y)
6768 {
6769   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6770      return -1;
6771   else
6772      return boards[currentMove][y][x];
6773 }
6774
6775 int
6776 OKToStartUserMove (int x, int y)
6777 {
6778     ChessSquare from_piece;
6779     int white_piece;
6780
6781     if (matchMode) return FALSE;
6782     if (gameMode == EditPosition) return TRUE;
6783
6784     if (x >= 0 && y >= 0)
6785       from_piece = boards[currentMove][y][x];
6786     else
6787       from_piece = EmptySquare;
6788
6789     if (from_piece == EmptySquare) return FALSE;
6790
6791     white_piece = (int)from_piece >= (int)WhitePawn &&
6792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6793
6794     switch (gameMode) {
6795       case AnalyzeFile:
6796       case TwoMachinesPlay:
6797       case EndOfGame:
6798         return FALSE;
6799
6800       case IcsObserving:
6801       case IcsIdle:
6802         return FALSE;
6803
6804       case MachinePlaysWhite:
6805       case IcsPlayingBlack:
6806         if (appData.zippyPlay) return FALSE;
6807         if (white_piece) {
6808             DisplayMoveError(_("You are playing Black"));
6809             return FALSE;
6810         }
6811         break;
6812
6813       case MachinePlaysBlack:
6814       case IcsPlayingWhite:
6815         if (appData.zippyPlay) return FALSE;
6816         if (!white_piece) {
6817             DisplayMoveError(_("You are playing White"));
6818             return FALSE;
6819         }
6820         break;
6821
6822       case PlayFromGameFile:
6823             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6824       case EditGame:
6825         if (!white_piece && WhiteOnMove(currentMove)) {
6826             DisplayMoveError(_("It is White's turn"));
6827             return FALSE;
6828         }
6829         if (white_piece && !WhiteOnMove(currentMove)) {
6830             DisplayMoveError(_("It is Black's turn"));
6831             return FALSE;
6832         }
6833         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6834             /* Editing correspondence game history */
6835             /* Could disallow this or prompt for confirmation */
6836             cmailOldMove = -1;
6837         }
6838         break;
6839
6840       case BeginningOfGame:
6841         if (appData.icsActive) return FALSE;
6842         if (!appData.noChessProgram) {
6843             if (!white_piece) {
6844                 DisplayMoveError(_("You are playing White"));
6845                 return FALSE;
6846             }
6847         }
6848         break;
6849
6850       case Training:
6851         if (!white_piece && WhiteOnMove(currentMove)) {
6852             DisplayMoveError(_("It is White's turn"));
6853             return FALSE;
6854         }
6855         if (white_piece && !WhiteOnMove(currentMove)) {
6856             DisplayMoveError(_("It is Black's turn"));
6857             return FALSE;
6858         }
6859         break;
6860
6861       default:
6862       case IcsExamining:
6863         break;
6864     }
6865     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6866         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6867         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6868         && gameMode != AnalyzeFile && gameMode != Training) {
6869         DisplayMoveError(_("Displayed position is not current"));
6870         return FALSE;
6871     }
6872     return TRUE;
6873 }
6874
6875 Boolean
6876 OnlyMove (int *x, int *y, Boolean captures)
6877 {
6878     DisambiguateClosure cl;
6879     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6880     switch(gameMode) {
6881       case MachinePlaysBlack:
6882       case IcsPlayingWhite:
6883       case BeginningOfGame:
6884         if(!WhiteOnMove(currentMove)) return FALSE;
6885         break;
6886       case MachinePlaysWhite:
6887       case IcsPlayingBlack:
6888         if(WhiteOnMove(currentMove)) return FALSE;
6889         break;
6890       case EditGame:
6891         break;
6892       default:
6893         return FALSE;
6894     }
6895     cl.pieceIn = EmptySquare;
6896     cl.rfIn = *y;
6897     cl.ffIn = *x;
6898     cl.rtIn = -1;
6899     cl.ftIn = -1;
6900     cl.promoCharIn = NULLCHAR;
6901     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6902     if( cl.kind == NormalMove ||
6903         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6904         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6905         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6906       fromX = cl.ff;
6907       fromY = cl.rf;
6908       *x = cl.ft;
6909       *y = cl.rt;
6910       return TRUE;
6911     }
6912     if(cl.kind != ImpossibleMove) return FALSE;
6913     cl.pieceIn = EmptySquare;
6914     cl.rfIn = -1;
6915     cl.ffIn = -1;
6916     cl.rtIn = *y;
6917     cl.ftIn = *x;
6918     cl.promoCharIn = NULLCHAR;
6919     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6920     if( cl.kind == NormalMove ||
6921         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6922         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6923         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6924       fromX = cl.ff;
6925       fromY = cl.rf;
6926       *x = cl.ft;
6927       *y = cl.rt;
6928       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6929       return TRUE;
6930     }
6931     return FALSE;
6932 }
6933
6934 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6935 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6936 int lastLoadGameUseList = FALSE;
6937 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6938 ChessMove lastLoadGameStart = EndOfFile;
6939 int doubleClick;
6940 Boolean addToBookFlag;
6941
6942 void
6943 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6944 {
6945     ChessMove moveType;
6946     ChessSquare pup;
6947     int ff=fromX, rf=fromY, ft=toX, rt=toY;
6948
6949     /* Check if the user is playing in turn.  This is complicated because we
6950        let the user "pick up" a piece before it is his turn.  So the piece he
6951        tried to pick up may have been captured by the time he puts it down!
6952        Therefore we use the color the user is supposed to be playing in this
6953        test, not the color of the piece that is currently on the starting
6954        square---except in EditGame mode, where the user is playing both
6955        sides; fortunately there the capture race can't happen.  (It can
6956        now happen in IcsExamining mode, but that's just too bad.  The user
6957        will get a somewhat confusing message in that case.)
6958        */
6959
6960     switch (gameMode) {
6961       case AnalyzeFile:
6962       case TwoMachinesPlay:
6963       case EndOfGame:
6964       case IcsObserving:
6965       case IcsIdle:
6966         /* We switched into a game mode where moves are not accepted,
6967            perhaps while the mouse button was down. */
6968         return;
6969
6970       case MachinePlaysWhite:
6971         /* User is moving for Black */
6972         if (WhiteOnMove(currentMove)) {
6973             DisplayMoveError(_("It is White's turn"));
6974             return;
6975         }
6976         break;
6977
6978       case MachinePlaysBlack:
6979         /* User is moving for White */
6980         if (!WhiteOnMove(currentMove)) {
6981             DisplayMoveError(_("It is Black's turn"));
6982             return;
6983         }
6984         break;
6985
6986       case PlayFromGameFile:
6987             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6988       case EditGame:
6989       case IcsExamining:
6990       case BeginningOfGame:
6991       case AnalyzeMode:
6992       case Training:
6993         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6994         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6995             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6996             /* User is moving for Black */
6997             if (WhiteOnMove(currentMove)) {
6998                 DisplayMoveError(_("It is White's turn"));
6999                 return;
7000             }
7001         } else {
7002             /* User is moving for White */
7003             if (!WhiteOnMove(currentMove)) {
7004                 DisplayMoveError(_("It is Black's turn"));
7005                 return;
7006             }
7007         }
7008         break;
7009
7010       case IcsPlayingBlack:
7011         /* User is moving for Black */
7012         if (WhiteOnMove(currentMove)) {
7013             if (!appData.premove) {
7014                 DisplayMoveError(_("It is White's turn"));
7015             } else if (toX >= 0 && toY >= 0) {
7016                 premoveToX = toX;
7017                 premoveToY = toY;
7018                 premoveFromX = fromX;
7019                 premoveFromY = fromY;
7020                 premovePromoChar = promoChar;
7021                 gotPremove = 1;
7022                 if (appData.debugMode)
7023                     fprintf(debugFP, "Got premove: fromX %d,"
7024                             "fromY %d, toX %d, toY %d\n",
7025                             fromX, fromY, toX, toY);
7026             }
7027             return;
7028         }
7029         break;
7030
7031       case IcsPlayingWhite:
7032         /* User is moving for White */
7033         if (!WhiteOnMove(currentMove)) {
7034             if (!appData.premove) {
7035                 DisplayMoveError(_("It is Black's turn"));
7036             } else if (toX >= 0 && toY >= 0) {
7037                 premoveToX = toX;
7038                 premoveToY = toY;
7039                 premoveFromX = fromX;
7040                 premoveFromY = fromY;
7041                 premovePromoChar = promoChar;
7042                 gotPremove = 1;
7043                 if (appData.debugMode)
7044                     fprintf(debugFP, "Got premove: fromX %d,"
7045                             "fromY %d, toX %d, toY %d\n",
7046                             fromX, fromY, toX, toY);
7047             }
7048             return;
7049         }
7050         break;
7051
7052       default:
7053         break;
7054
7055       case EditPosition:
7056         /* EditPosition, empty square, or different color piece;
7057            click-click move is possible */
7058         if (toX == -2 || toY == -2) {
7059             boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
7060             DrawPosition(FALSE, boards[currentMove]);
7061             return;
7062         } else if (toX >= 0 && toY >= 0) {
7063             if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
7064                 ChessSquare q, p = boards[0][rf][ff];
7065                 if(p >= BlackPawn) p = BLACK_TO_WHITE p;
7066                 if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
7067                 else p = CHUDEMOTED (q = boards[0][rf][ff]);
7068                 if(PieceToChar(q) == '+') gatingPiece = p;
7069             }
7070             boards[0][toY][toX] = boards[0][fromY][fromX];
7071             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
7072                 if(boards[0][fromY][0] != EmptySquare) {
7073                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
7074                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
7075                 }
7076             } else
7077             if(fromX == BOARD_RGHT+1) {
7078                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
7079                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
7080                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
7081                 }
7082             } else
7083             boards[0][fromY][fromX] = gatingPiece;
7084             DrawPosition(FALSE, boards[currentMove]);
7085             return;
7086         }
7087         return;
7088     }
7089
7090     if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
7091     pup = boards[currentMove][toY][toX];
7092
7093     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
7094     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
7095          if( pup != EmptySquare ) return;
7096          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
7097            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
7098                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
7099            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
7100            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
7101            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
7102            while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
7103          fromY = DROP_RANK;
7104     }
7105
7106     /* [HGM] always test for legality, to get promotion info */
7107     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
7108                                          fromY, fromX, toY, toX, promoChar);
7109
7110     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
7111
7112     if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
7113
7114     /* [HGM] but possibly ignore an IllegalMove result */
7115     if (appData.testLegality) {
7116         if (moveType == IllegalMove || moveType == ImpossibleMove) {
7117             DisplayMoveError(_("Illegal move"));
7118             return;
7119         }
7120     }
7121
7122     if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
7123         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
7124              ClearPremoveHighlights(); // was included
7125         else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
7126         return;
7127     }
7128
7129     if(addToBookFlag) { // adding moves to book
7130         char buf[MSG_SIZ], move[MSG_SIZ];
7131         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
7132         if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
7133         snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
7134         AddBookMove(buf);
7135         addToBookFlag = FALSE;
7136         ClearHighlights();
7137         return;
7138     }
7139
7140     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
7141 }
7142
7143 /* Common tail of UserMoveEvent and DropMenuEvent */
7144 int
7145 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
7146 {
7147     char *bookHit = 0;
7148
7149     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
7150         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
7151         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7152         if(WhiteOnMove(currentMove)) {
7153             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
7154         } else {
7155             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
7156         }
7157     }
7158
7159     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
7160        move type in caller when we know the move is a legal promotion */
7161     if(moveType == NormalMove && promoChar)
7162         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
7163
7164     /* [HGM] <popupFix> The following if has been moved here from
7165        UserMoveEvent(). Because it seemed to belong here (why not allow
7166        piece drops in training games?), and because it can only be
7167        performed after it is known to what we promote. */
7168     if (gameMode == Training) {
7169       /* compare the move played on the board to the next move in the
7170        * game. If they match, display the move and the opponent's response.
7171        * If they don't match, display an error message.
7172        */
7173       int saveAnimate;
7174       Board testBoard;
7175       CopyBoard(testBoard, boards[currentMove]);
7176       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
7177
7178       if (CompareBoards(testBoard, boards[currentMove+1])) {
7179         ForwardInner(currentMove+1);
7180
7181         /* Autoplay the opponent's response.
7182          * if appData.animate was TRUE when Training mode was entered,
7183          * the response will be animated.
7184          */
7185         saveAnimate = appData.animate;
7186         appData.animate = animateTraining;
7187         ForwardInner(currentMove+1);
7188         appData.animate = saveAnimate;
7189
7190         /* check for the end of the game */
7191         if (currentMove >= forwardMostMove) {
7192           gameMode = PlayFromGameFile;
7193           ModeHighlight();
7194           SetTrainingModeOff();
7195           DisplayInformation(_("End of game"));
7196         }
7197       } else {
7198         DisplayError(_("Incorrect move"), 0);
7199       }
7200       return 1;
7201     }
7202
7203   /* Ok, now we know that the move is good, so we can kill
7204      the previous line in Analysis Mode */
7205   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
7206                                 && currentMove < forwardMostMove) {
7207     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
7208     else forwardMostMove = currentMove;
7209   }
7210
7211   ClearMap();
7212
7213   /* If we need the chess program but it's dead, restart it */
7214   ResurrectChessProgram();
7215
7216   /* A user move restarts a paused game*/
7217   if (pausing)
7218     PauseEvent();
7219
7220   thinkOutput[0] = NULLCHAR;
7221
7222   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
7223
7224   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
7225     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7226     return 1;
7227   }
7228
7229   if (gameMode == BeginningOfGame) {
7230     if (appData.noChessProgram) {
7231       gameMode = EditGame;
7232       SetGameInfo();
7233     } else {
7234       char buf[MSG_SIZ];
7235       gameMode = MachinePlaysBlack;
7236       StartClocks();
7237       SetGameInfo();
7238       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
7239       DisplayTitle(buf);
7240       if (first.sendName) {
7241         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
7242         SendToProgram(buf, &first);
7243       }
7244       StartClocks();
7245     }
7246     ModeHighlight();
7247   }
7248
7249   /* Relay move to ICS or chess engine */
7250   if (appData.icsActive) {
7251     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
7252         gameMode == IcsExamining) {
7253       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7254         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7255         SendToICS("draw ");
7256         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7257       }
7258       // also send plain move, in case ICS does not understand atomic claims
7259       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7260       ics_user_moved = 1;
7261     }
7262   } else {
7263     if (first.sendTime && (gameMode == BeginningOfGame ||
7264                            gameMode == MachinePlaysWhite ||
7265                            gameMode == MachinePlaysBlack)) {
7266       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
7267     }
7268     if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) {
7269          // [HGM] book: if program might be playing, let it use book
7270         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
7271         first.maybeThinking = TRUE;
7272     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
7273         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
7274         SendBoard(&first, currentMove+1);
7275         if(second.analyzing) {
7276             if(!second.useSetboard) SendToProgram("undo\n", &second);
7277             SendBoard(&second, currentMove+1);
7278         }
7279     } else {
7280         SendMoveToProgram(forwardMostMove-1, &first);
7281         if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second);
7282     }
7283     if (currentMove == cmailOldMove + 1) {
7284       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
7285     }
7286   }
7287
7288   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7289
7290   switch (gameMode) {
7291   case EditGame:
7292     if(appData.testLegality)
7293     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
7294     case MT_NONE:
7295     case MT_CHECK:
7296       break;
7297     case MT_CHECKMATE:
7298     case MT_STAINMATE:
7299       if (WhiteOnMove(currentMove)) {
7300         GameEnds(BlackWins, "Black mates", GE_PLAYER);
7301       } else {
7302         GameEnds(WhiteWins, "White mates", GE_PLAYER);
7303       }
7304       break;
7305     case MT_STALEMATE:
7306       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
7307       break;
7308     }
7309     break;
7310
7311   case MachinePlaysBlack:
7312   case MachinePlaysWhite:
7313     /* disable certain menu options while machine is thinking */
7314     SetMachineThinkingEnables();
7315     break;
7316
7317   default:
7318     break;
7319   }
7320
7321   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
7322   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
7323
7324   if(bookHit) { // [HGM] book: simulate book reply
7325         static char bookMove[MSG_SIZ]; // a bit generous?
7326
7327         programStats.nodes = programStats.depth = programStats.time =
7328         programStats.score = programStats.got_only_move = 0;
7329         sprintf(programStats.movelist, "%s (xbook)", bookHit);
7330
7331         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7332         strcat(bookMove, bookHit);
7333         HandleMachineMove(bookMove, &first);
7334   }
7335   return 1;
7336 }
7337
7338 void
7339 MarkByFEN(char *fen)
7340 {
7341         int r, f;
7342         if(!appData.markers || !appData.highlightDragging) return;
7343         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
7344         r=BOARD_HEIGHT-1; f=BOARD_LEFT;
7345         while(*fen) {
7346             int s = 0;
7347             marker[r][f] = 0;
7348             if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
7349             if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
7350             if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
7351             if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
7352             if(*fen == 'T') marker[r][f++] = 0; else
7353             if(*fen == 'Y') marker[r][f++] = 1; else
7354             if(*fen == 'G') marker[r][f++] = 3; else
7355             if(*fen == 'B') marker[r][f++] = 4; else
7356             if(*fen == 'C') marker[r][f++] = 5; else
7357             if(*fen == 'M') marker[r][f++] = 6; else
7358             if(*fen == 'W') marker[r][f++] = 7; else
7359             if(*fen == 'D') marker[r][f++] = 8; else
7360             if(*fen == 'R') marker[r][f++] = 2; else {
7361                 while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
7362               f += s; fen -= s>0;
7363             }
7364             while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--;
7365             if(r < 0) break;
7366             fen++;
7367         }
7368         DrawPosition(TRUE, NULL);
7369 }
7370
7371 static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
7372
7373 void
7374 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
7375 {
7376     typedef char Markers[BOARD_RANKS][BOARD_FILES];
7377     Markers *m = (Markers *) closure;
7378     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
7379         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
7380                          || kind == WhiteCapturesEnPassant
7381                          || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
7382     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
7383 }
7384
7385 static int hoverSavedValid;
7386
7387 void
7388 MarkTargetSquares (int clear)
7389 {
7390   int x, y, sum=0;
7391   if(clear) { // no reason to ever suppress clearing
7392     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
7393     hoverSavedValid = 0;
7394     if(!sum) return; // nothing was cleared,no redraw needed
7395   } else {
7396     int capt = 0;
7397     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
7398        !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
7399     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
7400     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
7401       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
7402       if(capt)
7403       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
7404     }
7405   }
7406   DrawPosition(FALSE, NULL);
7407 }
7408
7409 int
7410 Explode (Board board, int fromX, int fromY, int toX, int toY)
7411 {
7412     if(gameInfo.variant == VariantAtomic &&
7413        (board[toY][toX] != EmptySquare ||                     // capture?
7414         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
7415                          board[fromY][fromX] == BlackPawn   )
7416       )) {
7417         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
7418         return TRUE;
7419     }
7420     return FALSE;
7421 }
7422
7423 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
7424
7425 int
7426 CanPromote (ChessSquare piece, int y)
7427 {
7428         int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
7429         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
7430         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
7431         if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
7432            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
7433            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
7434          gameInfo.variant == VariantMakruk) return FALSE;
7435         return (piece == BlackPawn && y <= zone ||
7436                 piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
7437                 piece == BlackLance && y <= zone ||
7438                 piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
7439 }
7440
7441 void
7442 HoverEvent (int xPix, int yPix, int x, int y)
7443 {
7444         static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
7445         int r, f;
7446         if(!first.highlight) return;
7447         if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
7448         if(x == oldX && y == oldY) return; // only do something if we enter new square
7449         oldFromX = fromX; oldFromY = fromY;
7450         if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
7451           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7452             baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
7453           hoverSavedValid = 1;
7454         } else if(oldX != x || oldY != y) {
7455           // [HGM] lift: entered new to-square; redraw arrow, and inform engine
7456           if(hoverSavedValid) // don't restore markers that are supposed to be cleared
7457           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7458             marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
7459           if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
7460             char buf[MSG_SIZ];
7461             snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
7462             SendToProgram(buf, &first);
7463           }
7464           oldX = x; oldY = y;
7465 //        SetHighlights(fromX, fromY, x, y);
7466         }
7467 }
7468
7469 void ReportClick(char *action, int x, int y)
7470 {
7471         char buf[MSG_SIZ]; // Inform engine of what user does
7472         int r, f;
7473         if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
7474           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
7475             legal[r][f] = !pieceDefs || !appData.markers, marker[r][f] = 0;
7476         if(!first.highlight || gameMode == EditPosition) return;
7477         snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
7478         SendToProgram(buf, &first);
7479 }
7480
7481 Boolean right; // instructs front-end to use button-1 events as if they were button 3
7482
7483 void
7484 LeftClick (ClickType clickType, int xPix, int yPix)
7485 {
7486     int x, y;
7487     Boolean saveAnimate;
7488     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
7489     char promoChoice = NULLCHAR;
7490     ChessSquare piece;
7491     static TimeMark lastClickTime, prevClickTime;
7492
7493     x = EventToSquare(xPix, BOARD_WIDTH);
7494     y = EventToSquare(yPix, BOARD_HEIGHT);
7495     if (!flipView && y >= 0) {
7496         y = BOARD_HEIGHT - 1 - y;
7497     }
7498     if (flipView && x >= 0) {
7499         x = BOARD_WIDTH - 1 - x;
7500     }
7501
7502     if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
7503         static int dummy;
7504         RightClick(clickType, xPix, yPix, &dummy, &dummy);
7505         right = TRUE;
7506         return;
7507     }
7508
7509     if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
7510
7511     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
7512
7513     if (clickType == Press) ErrorPopDown();
7514     lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
7515
7516     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
7517         defaultPromoChoice = promoSweep;
7518         promoSweep = EmptySquare;   // terminate sweep
7519         promoDefaultAltered = TRUE;
7520         if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
7521     }
7522
7523     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
7524         if(clickType == Release) return; // ignore upclick of click-click destination
7525         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
7526         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
7527         if(gameInfo.holdingsWidth &&
7528                 (WhiteOnMove(currentMove)
7529                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
7530                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
7531             // click in right holdings, for determining promotion piece
7532             ChessSquare p = boards[currentMove][y][x];
7533             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
7534             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
7535             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
7536                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
7537                 fromX = fromY = -1;
7538                 return;
7539             }
7540         }
7541         DrawPosition(FALSE, boards[currentMove]);
7542         return;
7543     }
7544
7545     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
7546     if(clickType == Press
7547             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
7548               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
7549               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
7550         return;
7551
7552     if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) {
7553         // could be static click on premove from-square: abort premove
7554         gotPremove = 0;
7555         ClearPremoveHighlights();
7556     }
7557
7558     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200)
7559         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
7560
7561     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
7562         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
7563                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
7564         defaultPromoChoice = DefaultPromoChoice(side);
7565     }
7566
7567     autoQueen = appData.alwaysPromoteToQueen;
7568
7569     if (fromX == -1) {
7570       int originalY = y;
7571       gatingPiece = EmptySquare;
7572       if (clickType != Press) {
7573         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
7574             DragPieceEnd(xPix, yPix); dragging = 0;
7575             DrawPosition(FALSE, NULL);
7576         }
7577         return;
7578       }
7579       doubleClick = FALSE;
7580       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
7581         doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
7582       }
7583       fromX = x; fromY = y; toX = toY = killX = killY = -1;
7584       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
7585          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
7586          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
7587             /* First square */
7588             if (OKToStartUserMove(fromX, fromY)) {
7589                 second = 0;
7590                 ReportClick("lift", x, y);
7591                 MarkTargetSquares(0);
7592                 if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
7593                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7594                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
7595                     promoSweep = defaultPromoChoice;
7596                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7597                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7598                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
7599                 }
7600                 if (appData.highlightDragging) {
7601                     SetHighlights(fromX, fromY, -1, -1);
7602                 } else {
7603                     ClearHighlights();
7604                 }
7605             } else fromX = fromY = -1;
7606             return;
7607         }
7608     }
7609
7610     /* fromX != -1 */
7611     if (clickType == Press && gameMode != EditPosition) {
7612         ChessSquare fromP;
7613         ChessSquare toP;
7614         int frc;
7615
7616         // ignore off-board to clicks
7617         if(y < 0 || x < 0) return;
7618
7619         /* Check if clicking again on the same color piece */
7620         fromP = boards[currentMove][fromY][fromX];
7621         toP = boards[currentMove][y][x];
7622         frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
7623         if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
7624             marker[y][x] == 0 && // if engine told we can move to here, do it even if own piece
7625            ((WhitePawn <= fromP && fromP <= WhiteKing &&
7626              WhitePawn <= toP && toP <= WhiteKing &&
7627              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
7628              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
7629             (BlackPawn <= fromP && fromP <= BlackKing &&
7630              BlackPawn <= toP && toP <= BlackKing &&
7631              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
7632              !(fromP == BlackKing && toP == BlackRook && frc)))) {
7633             /* Clicked again on same color piece -- changed his mind */
7634             second = (x == fromX && y == fromY);
7635             killX = killY = -1;
7636             if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7637                 second = FALSE; // first double-click rather than scond click
7638                 doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
7639             }
7640             promoDefaultAltered = FALSE;
7641             MarkTargetSquares(1);
7642            if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
7643             if (appData.highlightDragging) {
7644                 SetHighlights(x, y, -1, -1);
7645             } else {
7646                 ClearHighlights();
7647             }
7648             if (OKToStartUserMove(x, y)) {
7649                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
7650                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
7651                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
7652                  gatingPiece = boards[currentMove][fromY][fromX];
7653                 else gatingPiece = doubleClick ? fromP : EmptySquare;
7654                 fromX = x;
7655                 fromY = y; dragging = 1;
7656                 if(!second) ReportClick("lift", x, y);
7657                 MarkTargetSquares(0);
7658                 DragPieceBegin(xPix, yPix, FALSE);
7659                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
7660                     promoSweep = defaultPromoChoice;
7661                     selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
7662                     Sweep(0); // Pawn that is going to promote: preview promotion piece
7663                 }
7664             }
7665            }
7666            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
7667            second = FALSE;
7668         }
7669         // ignore clicks on holdings
7670         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
7671     }
7672
7673     if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
7674         gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
7675         DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
7676         return;
7677     }
7678
7679     if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
7680         DragPieceEnd(xPix, yPix); dragging = 0;
7681         if(clearFlag) {
7682             // a deferred attempt to click-click move an empty square on top of a piece
7683             boards[currentMove][y][x] = EmptySquare;
7684             ClearHighlights();
7685             DrawPosition(FALSE, boards[currentMove]);
7686             fromX = fromY = -1; clearFlag = 0;
7687             return;
7688         }
7689         if (appData.animateDragging) {
7690             /* Undo animation damage if any */
7691             DrawPosition(FALSE, NULL);
7692         }
7693         if (second) {
7694             /* Second up/down in same square; just abort move */
7695             second = 0;
7696             fromX = fromY = -1;
7697             gatingPiece = EmptySquare;
7698             MarkTargetSquares(1);
7699             ClearHighlights();
7700             gotPremove = 0;
7701             ClearPremoveHighlights();
7702         } else {
7703             /* First upclick in same square; start click-click mode */
7704             SetHighlights(x, y, -1, -1);
7705         }
7706         return;
7707     }
7708
7709     clearFlag = 0;
7710
7711     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
7712        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
7713         if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
7714         DisplayMessage(_("only marked squares are legal"),"");
7715         DrawPosition(TRUE, NULL);
7716         return; // ignore to-click
7717     }
7718
7719     /* we now have a different from- and (possibly off-board) to-square */
7720     /* Completed move */
7721     if(!sweepSelecting) {
7722         toX = x;
7723         toY = y;
7724     }
7725
7726     piece = boards[currentMove][fromY][fromX];
7727
7728     saveAnimate = appData.animate;
7729     if (clickType == Press) {
7730         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7731         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7732             // must be Edit Position mode with empty-square selected
7733             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7734             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7735             return;
7736         }
7737         if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
7738             return;
7739         }
7740         if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
7741             killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
7742         } else
7743         if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
7744         if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7745           if(appData.sweepSelect) {
7746             promoSweep = defaultPromoChoice;
7747             if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
7748             selectFlag = 0; lastX = xPix; lastY = yPix;
7749             ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
7750             Sweep(0); // Pawn that is going to promote: preview promotion piece
7751             sweepSelecting = 1;
7752             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7753             MarkTargetSquares(1);
7754           }
7755           return; // promo popup appears on up-click
7756         }
7757         /* Finish clickclick move */
7758         if (appData.animate || appData.highlightLastMove) {
7759             SetHighlights(fromX, fromY, toX, toY);
7760         } else {
7761             ClearHighlights();
7762         }
7763     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
7764         sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
7765         *promoRestrict = 0;
7766         if (appData.animate || appData.highlightLastMove) {
7767             SetHighlights(fromX, fromY, toX, toY);
7768         } else {
7769             ClearHighlights();
7770         }
7771     } else {
7772 #if 0
7773 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
7774         /* Finish drag move */
7775         if (appData.highlightLastMove) {
7776             SetHighlights(fromX, fromY, toX, toY);
7777         } else {
7778             ClearHighlights();
7779         }
7780 #endif
7781         if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
7782         if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
7783           dragging *= 2;            // flag button-less dragging if we are dragging
7784           MarkTargetSquares(1);
7785           if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
7786           else {
7787             kill2X = killX; kill2Y = killY;
7788             killX = x; killY = y;     //remeber this square as intermediate
7789             ReportClick("put", x, y); // and inform engine
7790             ReportClick("lift", x, y);
7791             MarkTargetSquares(0);
7792             return;
7793           }
7794         }
7795         DragPieceEnd(xPix, yPix); dragging = 0;
7796         /* Don't animate move and drag both */
7797         appData.animate = FALSE;
7798     }
7799
7800     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7801     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7802         ChessSquare piece = boards[currentMove][fromY][fromX];
7803         if(gameMode == EditPosition && piece != EmptySquare &&
7804            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7805             int n;
7806
7807             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7808                 n = PieceToNumber(piece - (int)BlackPawn);
7809                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7810                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7811                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7812             } else
7813             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7814                 n = PieceToNumber(piece);
7815                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7816                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7817                 boards[currentMove][n][BOARD_WIDTH-2]++;
7818             }
7819             boards[currentMove][fromY][fromX] = EmptySquare;
7820         }
7821         ClearHighlights();
7822         fromX = fromY = -1;
7823         MarkTargetSquares(1);
7824         DrawPosition(TRUE, boards[currentMove]);
7825         return;
7826     }
7827
7828     // off-board moves should not be highlighted
7829     if(x < 0 || y < 0) ClearHighlights();
7830     else ReportClick("put", x, y);
7831
7832     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
7833
7834     if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
7835
7836     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7837         SetHighlights(fromX, fromY, toX, toY);
7838         MarkTargetSquares(1);
7839         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7840             // [HGM] super: promotion to captured piece selected from holdings
7841             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7842             promotionChoice = TRUE;
7843             // kludge follows to temporarily execute move on display, without promoting yet
7844             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7845             boards[currentMove][toY][toX] = p;
7846             DrawPosition(FALSE, boards[currentMove]);
7847             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7848             boards[currentMove][toY][toX] = q;
7849             DisplayMessage("Click in holdings to choose piece", "");
7850             return;
7851         }
7852         PromotionPopUp(promoChoice);
7853     } else {
7854         int oldMove = currentMove;
7855         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7856         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7857         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7858         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7859            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7860             DrawPosition(TRUE, boards[currentMove]);
7861         MarkTargetSquares(1);
7862         fromX = fromY = -1;
7863     }
7864     appData.animate = saveAnimate;
7865     if (appData.animate || appData.animateDragging) {
7866         /* Undo animation damage if needed */
7867         DrawPosition(FALSE, NULL);
7868     }
7869 }
7870
7871 int
7872 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7873 {   // front-end-free part taken out of PieceMenuPopup
7874     int whichMenu; int xSqr, ySqr;
7875
7876     if(seekGraphUp) { // [HGM] seekgraph
7877         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7878         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7879         return -2;
7880     }
7881
7882     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7883          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7884         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7885         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7886         if(action == Press)   {
7887             originalFlip = flipView;
7888             flipView = !flipView; // temporarily flip board to see game from partners perspective
7889             DrawPosition(TRUE, partnerBoard);
7890             DisplayMessage(partnerStatus, "");
7891             partnerUp = TRUE;
7892         } else if(action == Release) {
7893             flipView = originalFlip;
7894             DrawPosition(TRUE, boards[currentMove]);
7895             partnerUp = FALSE;
7896         }
7897         return -2;
7898     }
7899
7900     xSqr = EventToSquare(x, BOARD_WIDTH);
7901     ySqr = EventToSquare(y, BOARD_HEIGHT);
7902     if (action == Release) {
7903         if(pieceSweep != EmptySquare) {
7904             EditPositionMenuEvent(pieceSweep, toX, toY);
7905             pieceSweep = EmptySquare;
7906         } else UnLoadPV(); // [HGM] pv
7907     }
7908     if (action != Press) return -2; // return code to be ignored
7909     switch (gameMode) {
7910       case IcsExamining:
7911         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7912       case EditPosition:
7913         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7914         if (xSqr < 0 || ySqr < 0) return -1;
7915         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7916         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7917         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7918         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7919         NextPiece(0);
7920         return 2; // grab
7921       case IcsObserving:
7922         if(!appData.icsEngineAnalyze) return -1;
7923       case IcsPlayingWhite:
7924       case IcsPlayingBlack:
7925         if(!appData.zippyPlay) goto noZip;
7926       case AnalyzeMode:
7927       case AnalyzeFile:
7928       case MachinePlaysWhite:
7929       case MachinePlaysBlack:
7930       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7931         if (!appData.dropMenu) {
7932           LoadPV(x, y);
7933           return 2; // flag front-end to grab mouse events
7934         }
7935         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7936            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7937       case EditGame:
7938       noZip:
7939         if (xSqr < 0 || ySqr < 0) return -1;
7940         if (!appData.dropMenu || appData.testLegality &&
7941             gameInfo.variant != VariantBughouse &&
7942             gameInfo.variant != VariantCrazyhouse) return -1;
7943         whichMenu = 1; // drop menu
7944         break;
7945       default:
7946         return -1;
7947     }
7948
7949     if (((*fromX = xSqr) < 0) ||
7950         ((*fromY = ySqr) < 0)) {
7951         *fromX = *fromY = -1;
7952         return -1;
7953     }
7954     if (flipView)
7955       *fromX = BOARD_WIDTH - 1 - *fromX;
7956     else
7957       *fromY = BOARD_HEIGHT - 1 - *fromY;
7958
7959     return whichMenu;
7960 }
7961
7962 void
7963 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7964 {
7965 //    char * hint = lastHint;
7966     FrontEndProgramStats stats;
7967
7968     stats.which = cps == &first ? 0 : 1;
7969     stats.depth = cpstats->depth;
7970     stats.nodes = cpstats->nodes;
7971     stats.score = cpstats->score;
7972     stats.time = cpstats->time;
7973     stats.pv = cpstats->movelist;
7974     stats.hint = lastHint;
7975     stats.an_move_index = 0;
7976     stats.an_move_count = 0;
7977
7978     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7979         stats.hint = cpstats->move_name;
7980         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7981         stats.an_move_count = cpstats->nr_moves;
7982     }
7983
7984     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
7985
7986     SetProgramStats( &stats );
7987 }
7988
7989 void
7990 ClearEngineOutputPane (int which)
7991 {
7992     static FrontEndProgramStats dummyStats;
7993     dummyStats.which = which;
7994     dummyStats.pv = "#";
7995     SetProgramStats( &dummyStats );
7996 }
7997
7998 #define MAXPLAYERS 500
7999
8000 char *
8001 TourneyStandings (int display)
8002 {
8003     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
8004     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
8005     char result, *p, *names[MAXPLAYERS];
8006
8007     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
8008         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
8009     names[0] = p = strdup(appData.participants);
8010     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
8011
8012     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
8013
8014     while(result = appData.results[nr]) {
8015         color = Pairing(nr, nPlayers, &w, &b, &dummy);
8016         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
8017         wScore = bScore = 0;
8018         switch(result) {
8019           case '+': wScore = 2; break;
8020           case '-': bScore = 2; break;
8021           case '=': wScore = bScore = 1; break;
8022           case ' ':
8023           case '*': return strdup("busy"); // tourney not finished
8024         }
8025         score[w] += wScore;
8026         score[b] += bScore;
8027         games[w]++;
8028         games[b]++;
8029         nr++;
8030     }
8031     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
8032     for(w=0; w<nPlayers; w++) {
8033         bScore = -1;
8034         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
8035         ranking[w] = b; points[w] = bScore; score[b] = -2;
8036     }
8037     p = malloc(nPlayers*34+1);
8038     for(w=0; w<nPlayers && w<display; w++)
8039         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
8040     free(names[0]);
8041     return p;
8042 }
8043
8044 void
8045 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
8046 {       // count all piece types
8047         int p, f, r;
8048         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
8049         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
8050         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8051                 p = board[r][f];
8052                 pCnt[p]++;
8053                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
8054                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
8055                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
8056                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
8057                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
8058                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
8059         }
8060 }
8061
8062 int
8063 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
8064 {
8065         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
8066         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
8067
8068         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
8069         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
8070         if(myPawns == 2 && nMine == 3) // KPP
8071             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
8072         if(myPawns == 1 && nMine == 2) // KP
8073             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
8074         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
8075             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
8076         if(myPawns) return FALSE;
8077         if(pCnt[WhiteRook+side])
8078             return pCnt[BlackRook-side] ||
8079                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
8080                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
8081                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
8082         if(pCnt[WhiteCannon+side]) {
8083             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
8084             return majorDefense || pCnt[BlackAlfil-side] >= 2;
8085         }
8086         if(pCnt[WhiteKnight+side])
8087             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
8088         return FALSE;
8089 }
8090
8091 int
8092 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
8093 {
8094         VariantClass v = gameInfo.variant;
8095
8096         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
8097         if(v == VariantShatranj) return TRUE; // always winnable through baring
8098         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
8099         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
8100
8101         if(v == VariantXiangqi) {
8102                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
8103
8104                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
8105                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
8106                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
8107                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
8108                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
8109                 if(stale) // we have at least one last-rank P plus perhaps C
8110                     return majors // KPKX
8111                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
8112                 else // KCA*E*
8113                     return pCnt[WhiteFerz+side] // KCAK
8114                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
8115                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
8116                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
8117
8118         } else if(v == VariantKnightmate) {
8119                 if(nMine == 1) return FALSE;
8120                 if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
8121         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
8122                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
8123
8124                 if(nMine == 1) return FALSE; // bare King
8125                 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
8126                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
8127                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
8128                 // by now we have King + 1 piece (or multiple Bishops on the same color)
8129                 if(pCnt[WhiteKnight+side])
8130                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
8131                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
8132                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
8133                 if(nBishops)
8134                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
8135                 if(pCnt[WhiteAlfil+side])
8136                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
8137                 if(pCnt[WhiteWazir+side])
8138                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
8139         }
8140
8141         return TRUE;
8142 }
8143
8144 int
8145 CompareWithRights (Board b1, Board b2)
8146 {
8147     int rights = 0;
8148     if(!CompareBoards(b1, b2)) return FALSE;
8149     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
8150     /* compare castling rights */
8151     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
8152            rights++; /* King lost rights, while rook still had them */
8153     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
8154         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
8155            rights++; /* but at least one rook lost them */
8156     }
8157     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
8158            rights++;
8159     if( b1[CASTLING][5] != NoRights ) {
8160         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
8161            rights++;
8162     }
8163     return rights == 0;
8164 }
8165
8166 int
8167 Adjudicate (ChessProgramState *cps)
8168 {       // [HGM] some adjudications useful with buggy engines
8169         // [HGM] adjudicate: made into separate routine, which now can be called after every move
8170         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
8171         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
8172         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
8173         int k, drop, count = 0; static int bare = 1;
8174         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
8175         Boolean canAdjudicate = !appData.icsActive;
8176
8177         // most tests only when we understand the game, i.e. legality-checking on
8178             if( appData.testLegality )
8179             {   /* [HGM] Some more adjudications for obstinate engines */
8180                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
8181                 static int moveCount = 6;
8182                 ChessMove result;
8183                 char *reason = NULL;
8184
8185                 /* Count what is on board. */
8186                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
8187
8188                 /* Some material-based adjudications that have to be made before stalemate test */
8189                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
8190                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
8191                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
8192                      if(canAdjudicate && appData.checkMates) {
8193                          if(engineOpponent)
8194                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8195                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
8196                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
8197                          return 1;
8198                      }
8199                 }
8200
8201                 /* Bare King in Shatranj (loses) or Losers (wins) */
8202                 if( nrW == 1 || nrB == 1) {
8203                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
8204                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
8205                      if(canAdjudicate && appData.checkMates) {
8206                          if(engineOpponent)
8207                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
8208                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8209                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8210                          return 1;
8211                      }
8212                   } else
8213                   if( gameInfo.variant == VariantShatranj && --bare < 0)
8214                   {    /* bare King */
8215                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
8216                         if(canAdjudicate && appData.checkMates) {
8217                             /* but only adjudicate if adjudication enabled */
8218                             if(engineOpponent)
8219                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
8220                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
8221                                                         "Xboard adjudication: Bare king", GE_XBOARD );
8222                             return 1;
8223                         }
8224                   }
8225                 } else bare = 1;
8226
8227
8228             // don't wait for engine to announce game end if we can judge ourselves
8229             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8230               case MT_CHECK:
8231                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
8232                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
8233                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
8234                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
8235                             checkCnt++;
8236                         if(checkCnt >= 2) {
8237                             reason = "Xboard adjudication: 3rd check";
8238                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
8239                             break;
8240                         }
8241                     }
8242                 }
8243               case MT_NONE:
8244               default:
8245                 break;
8246               case MT_STEALMATE:
8247               case MT_STALEMATE:
8248               case MT_STAINMATE:
8249                 reason = "Xboard adjudication: Stalemate";
8250                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
8251                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
8252                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
8253                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
8254                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
8255                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
8256                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
8257                                                                         EP_CHECKMATE : EP_WINS);
8258                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi)
8259                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
8260                 }
8261                 break;
8262               case MT_CHECKMATE:
8263                 reason = "Xboard adjudication: Checkmate";
8264                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
8265                 if(gameInfo.variant == VariantShogi) {
8266                     if(forwardMostMove > backwardMostMove
8267                        && moveList[forwardMostMove-1][1] == '@'
8268                        && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) {
8269                         reason = "XBoard adjudication: pawn-drop mate";
8270                         boards[forwardMostMove][EP_STATUS] = EP_WINS;
8271                     }
8272                 }
8273                 break;
8274             }
8275
8276                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
8277                     case EP_STALEMATE:
8278                         result = GameIsDrawn; break;
8279                     case EP_CHECKMATE:
8280                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
8281                     case EP_WINS:
8282                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
8283                     default:
8284                         result = EndOfFile;
8285                 }
8286                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
8287                     if(engineOpponent)
8288                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8289                     GameEnds( result, reason, GE_XBOARD );
8290                     return 1;
8291                 }
8292
8293                 /* Next absolutely insufficient mating material. */
8294                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
8295                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
8296                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
8297
8298                      /* always flag draws, for judging claims */
8299                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
8300
8301                      if(canAdjudicate && appData.materialDraws) {
8302                          /* but only adjudicate them if adjudication enabled */
8303                          if(engineOpponent) {
8304                            SendToProgram("force\n", engineOpponent); // suppress reply
8305                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
8306                          }
8307                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
8308                          return 1;
8309                      }
8310                 }
8311
8312                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
8313                 if(gameInfo.variant == VariantXiangqi ?
8314                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
8315                  : nrW + nrB == 4 &&
8316                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
8317                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
8318                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
8319                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
8320                    ) ) {
8321                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
8322                      {    /* if the first 3 moves do not show a tactical win, declare draw */
8323                           if(engineOpponent) {
8324                             SendToProgram("force\n", engineOpponent); // suppress reply
8325                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8326                           }
8327                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
8328                           return 1;
8329                      }
8330                 } else moveCount = 6;
8331             }
8332
8333         // Repetition draws and 50-move rule can be applied independently of legality testing
8334
8335                 /* Check for rep-draws */
8336                 count = 0;
8337                 drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
8338                                               && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
8339                 for(k = forwardMostMove-2;
8340                     k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
8341                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
8342                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
8343                     k-=2)
8344                 {   int rights=0;
8345                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
8346                         /* compare castling rights */
8347                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
8348                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
8349                                 rights++; /* King lost rights, while rook still had them */
8350                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
8351                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
8352                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
8353                                    rights++; /* but at least one rook lost them */
8354                         }
8355                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
8356                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
8357                                 rights++;
8358                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
8359                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
8360                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
8361                                    rights++;
8362                         }
8363                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
8364                             && appData.drawRepeats > 1) {
8365                              /* adjudicate after user-specified nr of repeats */
8366                              int result = GameIsDrawn;
8367                              char *details = "XBoard adjudication: repetition draw";
8368                              if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) {
8369                                 // [HGM] xiangqi: check for forbidden perpetuals
8370                                 int m, ourPerpetual = 1, hisPerpetual = 1;
8371                                 for(m=forwardMostMove; m>k; m-=2) {
8372                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
8373                                         ourPerpetual = 0; // the current mover did not always check
8374                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
8375                                         hisPerpetual = 0; // the opponent did not always check
8376                                 }
8377                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
8378                                                                         ourPerpetual, hisPerpetual);
8379                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
8380                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8381                                     details = "Xboard adjudication: perpetual checking";
8382                                 } else
8383                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
8384                                     break; // (or we would have caught him before). Abort repetition-checking loop.
8385                                 } else
8386                                 if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws
8387                                     if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins!
8388                                         result = BlackWins;
8389                                         details = "Xboard adjudication: repetition";
8390                                     }
8391                                 } else // it must be XQ
8392                                 // Now check for perpetual chases
8393                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
8394                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
8395                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
8396                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
8397                                         static char resdet[MSG_SIZ];
8398                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8399                                         details = resdet;
8400                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
8401                                     } else
8402                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
8403                                         break; // Abort repetition-checking loop.
8404                                 }
8405                                 // if neither of us is checking or chasing all the time, or both are, it is draw
8406                              }
8407                              if(engineOpponent) {
8408                                SendToProgram("force\n", engineOpponent); // suppress reply
8409                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8410                              }
8411                              GameEnds( result, details, GE_XBOARD );
8412                              return 1;
8413                         }
8414                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
8415                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
8416                     }
8417                 }
8418
8419                 /* Now we test for 50-move draws. Determine ply count */
8420                 count = forwardMostMove;
8421                 /* look for last irreversble move */
8422                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
8423                     count--;
8424                 /* if we hit starting position, add initial plies */
8425                 if( count == backwardMostMove )
8426                     count -= initialRulePlies;
8427                 count = forwardMostMove - count;
8428                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
8429                         // adjust reversible move counter for checks in Xiangqi
8430                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
8431                         if(i < backwardMostMove) i = backwardMostMove;
8432                         while(i <= forwardMostMove) {
8433                                 lastCheck = inCheck; // check evasion does not count
8434                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
8435                                 if(inCheck || lastCheck) count--; // check does not count
8436                                 i++;
8437                         }
8438                 }
8439                 if( count >= 100)
8440                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
8441                          /* this is used to judge if draw claims are legal */
8442                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
8443                          if(engineOpponent) {
8444                            SendToProgram("force\n", engineOpponent); // suppress reply
8445                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8446                          }
8447                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
8448                          return 1;
8449                 }
8450
8451                 /* if draw offer is pending, treat it as a draw claim
8452                  * when draw condition present, to allow engines a way to
8453                  * claim draws before making their move to avoid a race
8454                  * condition occurring after their move
8455                  */
8456                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
8457                          char *p = NULL;
8458                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
8459                              p = "Draw claim: 50-move rule";
8460                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
8461                              p = "Draw claim: 3-fold repetition";
8462                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
8463                              p = "Draw claim: insufficient mating material";
8464                          if( p != NULL && canAdjudicate) {
8465                              if(engineOpponent) {
8466                                SendToProgram("force\n", engineOpponent); // suppress reply
8467                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8468                              }
8469                              GameEnds( GameIsDrawn, p, GE_XBOARD );
8470                              return 1;
8471                          }
8472                 }
8473
8474                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
8475                     if(engineOpponent) {
8476                       SendToProgram("force\n", engineOpponent); // suppress reply
8477                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
8478                     }
8479                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
8480                     return 1;
8481                 }
8482         return 0;
8483 }
8484
8485 typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
8486 typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
8487 static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
8488
8489 static int
8490 BitbaseProbe ()
8491 {
8492     int pieces[10], squares[10], cnt=0, r, f, res;
8493     static int loaded;
8494     static PPROBE_EGBB probeBB;
8495     if(!appData.testLegality) return 10;
8496     if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
8497     if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
8498     if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
8499     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
8500         ChessSquare piece = boards[forwardMostMove][r][f];
8501         int black = (piece >= BlackPawn);
8502         int type = piece - black*BlackPawn;
8503         if(piece == EmptySquare) continue;
8504         if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
8505         if(type == WhiteKing) type = WhiteQueen + 1;
8506         type = egbbCode[type];
8507         squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
8508         pieces[cnt] = type + black*6;
8509         if(++cnt > 5) return 11;
8510     }
8511     pieces[cnt] = squares[cnt] = 0;
8512     // probe EGBB
8513     if(loaded == 2) return 13; // loading failed before
8514     if(loaded == 0) {
8515         char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
8516         HMODULE lib;
8517         PLOAD_EGBB loadBB;
8518         loaded = 2; // prepare for failure
8519         if(!path) return 13; // no egbb installed
8520         strncpy(buf, path + 8, MSG_SIZ);
8521         if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
8522         snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
8523         lib = LoadLibrary(buf);
8524         if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
8525         loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
8526         probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
8527         if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
8528         p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
8529         loaded = 1; // success!
8530     }
8531     res = probeBB(forwardMostMove & 1, pieces, squares);
8532     return res > 0 ? 1 : res < 0 ? -1 : 0;
8533 }
8534
8535 char *
8536 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
8537 {   // [HGM] book: this routine intercepts moves to simulate book replies
8538     char *bookHit = NULL;
8539
8540     if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
8541         char buf[MSG_SIZ];
8542         snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
8543         SendToProgram(buf, cps);
8544     }
8545     //first determine if the incoming move brings opponent into his book
8546     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
8547         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
8548     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
8549     if(bookHit != NULL && !cps->bookSuspend) {
8550         // make sure opponent is not going to reply after receiving move to book position
8551         SendToProgram("force\n", cps);
8552         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
8553     }
8554     if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram
8555     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
8556     // now arrange restart after book miss
8557     if(bookHit) {
8558         // after a book hit we never send 'go', and the code after the call to this routine
8559         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
8560         char buf[MSG_SIZ], *move = bookHit;
8561         if(cps->useSAN) {
8562             int fromX, fromY, toX, toY;
8563             char promoChar;
8564             ChessMove moveType;
8565             move = buf + 30;
8566             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
8567                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8568                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8569                                     PosFlags(forwardMostMove),
8570                                     fromY, fromX, toY, toX, promoChar, move);
8571             } else {
8572                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
8573                 bookHit = NULL;
8574             }
8575         }
8576         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
8577         SendToProgram(buf, cps);
8578         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
8579     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
8580         SendToProgram("go\n", cps);
8581         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
8582     } else { // 'go' might be sent based on 'firstMove' after this routine returns
8583         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
8584             SendToProgram("go\n", cps);
8585         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
8586     }
8587     return bookHit; // notify caller of hit, so it can take action to send move to opponent
8588 }
8589
8590 int
8591 LoadError (char *errmess, ChessProgramState *cps)
8592 {   // unloads engine and switches back to -ncp mode if it was first
8593     if(cps->initDone) return FALSE;
8594     cps->isr = NULL; // this should suppress further error popups from breaking pipes
8595     DestroyChildProcess(cps->pr, 9 ); // just to be sure
8596     cps->pr = NoProc;
8597     if(cps == &first) {
8598         appData.noChessProgram = TRUE;
8599         gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
8600         gameMode = BeginningOfGame; ModeHighlight();
8601         SetNCPMode();
8602     }
8603     if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
8604     DisplayMessage("", ""); // erase waiting message
8605     if(errmess) DisplayError(errmess, 0); // announce reason, if given
8606     return TRUE;
8607 }
8608
8609 char *savedMessage;
8610 ChessProgramState *savedState;
8611 void
8612 DeferredBookMove (void)
8613 {
8614         if(savedState->lastPing != savedState->lastPong)
8615                     ScheduleDelayedEvent(DeferredBookMove, 10);
8616         else
8617         HandleMachineMove(savedMessage, savedState);
8618 }
8619
8620 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
8621 static ChessProgramState *stalledEngine;
8622 static char stashedInputMove[MSG_SIZ], abortEngineThink;
8623
8624 void
8625 HandleMachineMove (char *message, ChessProgramState *cps)
8626 {
8627     static char firstLeg[20];
8628     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
8629     char realname[MSG_SIZ];
8630     int fromX, fromY, toX, toY;
8631     ChessMove moveType;
8632     char promoChar, roar;
8633     char *p, *pv=buf1;
8634     int machineWhite, oldError;
8635     char *bookHit;
8636
8637     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
8638         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
8639         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
8640             DisplayError(_("Invalid pairing from pairing engine"), 0);
8641             return;
8642         }
8643         pairingReceived = 1;
8644         NextMatchGame();
8645         return; // Skim the pairing messages here.
8646     }
8647
8648     oldError = cps->userError; cps->userError = 0;
8649
8650 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
8651     /*
8652      * Kludge to ignore BEL characters
8653      */
8654     while (*message == '\007') message++;
8655
8656     /*
8657      * [HGM] engine debug message: ignore lines starting with '#' character
8658      */
8659     if(cps->debug && *message == '#') return;
8660
8661     /*
8662      * Look for book output
8663      */
8664     if (cps == &first && bookRequested) {
8665         if (message[0] == '\t' || message[0] == ' ') {
8666             /* Part of the book output is here; append it */
8667             strcat(bookOutput, message);
8668             strcat(bookOutput, "  \n");
8669             return;
8670         } else if (bookOutput[0] != NULLCHAR) {
8671             /* All of book output has arrived; display it */
8672             char *p = bookOutput;
8673             while (*p != NULLCHAR) {
8674                 if (*p == '\t') *p = ' ';
8675                 p++;
8676             }
8677             DisplayInformation(bookOutput);
8678             bookRequested = FALSE;
8679             /* Fall through to parse the current output */
8680         }
8681     }
8682
8683     /*
8684      * Look for machine move.
8685      */
8686     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
8687         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
8688     {
8689         if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
8690             if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
8691             safeStrCpy(stashedInputMove, message, MSG_SIZ);
8692             stalledEngine = cps;
8693             if(appData.ponderNextMove) { // bring opponent out of ponder
8694                 if(gameMode == TwoMachinesPlay) {
8695                     if(cps->other->pause)
8696                         PauseEngine(cps->other);
8697                     else
8698                         SendToProgram("easy\n", cps->other);
8699                 }
8700             }
8701             StopClocks();
8702             return;
8703         }
8704
8705       if(cps->usePing) {
8706
8707         /* This method is only useful on engines that support ping */
8708         if(abortEngineThink) {
8709             if (appData.debugMode) {
8710                 fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
8711             }
8712             SendToProgram("undo\n", cps);
8713             return;
8714         }
8715
8716         if (cps->lastPing != cps->lastPong) {
8717             /* Extra move from before last new; ignore */
8718             if (appData.debugMode) {
8719                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8720             }
8721           return;
8722         }
8723
8724       } else {
8725
8726         switch (gameMode) {
8727           case BeginningOfGame:
8728             /* Extra move from before last reset; ignore */
8729             if (appData.debugMode) {
8730                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
8731             }
8732             return;
8733
8734           case EndOfGame:
8735           case IcsIdle:
8736           default:
8737             /* Extra move after we tried to stop.  The mode test is
8738                not a reliable way of detecting this problem, but it's
8739                the best we can do on engines that don't support ping.
8740             */
8741             if (appData.debugMode) {
8742                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
8743                         cps->which, gameMode);
8744             }
8745             SendToProgram("undo\n", cps);
8746             return;
8747
8748           case MachinePlaysWhite:
8749           case IcsPlayingWhite:
8750             machineWhite = TRUE;
8751             break;
8752
8753           case MachinePlaysBlack:
8754           case IcsPlayingBlack:
8755             machineWhite = FALSE;
8756             break;
8757
8758           case TwoMachinesPlay:
8759             machineWhite = (cps->twoMachinesColor[0] == 'w');
8760             break;
8761         }
8762         if (WhiteOnMove(forwardMostMove) != machineWhite) {
8763             if (appData.debugMode) {
8764                 fprintf(debugFP,
8765                         "Ignoring move out of turn by %s, gameMode %d"
8766                         ", forwardMost %d\n",
8767                         cps->which, gameMode, forwardMostMove);
8768             }
8769             return;
8770         }
8771       }
8772
8773         if(cps->alphaRank) AlphaRank(machineMove, 4);
8774
8775         // [HGM] lion: (some very limited) support for Alien protocol
8776         killX = killY = kill2X = kill2Y = -1;
8777         if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
8778             safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
8779             return;
8780         }
8781         if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
8782             safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
8783             safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
8784         }
8785         if(firstLeg[0]) { // there was a previous leg;
8786             // only support case where same piece makes two step
8787             char buf[20], *p = machineMove+1, *q = buf+1, f;
8788             safeStrCpy(buf, machineMove, 20);
8789             while(isdigit(*q)) q++; // find start of to-square
8790             safeStrCpy(machineMove, firstLeg, 20);
8791             while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
8792             if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
8793             safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
8794             sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
8795             firstLeg[0] = NULLCHAR;
8796         }
8797
8798         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
8799                               &fromX, &fromY, &toX, &toY, &promoChar)) {
8800             /* Machine move could not be parsed; ignore it. */
8801           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
8802                     machineMove, _(cps->which));
8803             DisplayMoveError(buf1);
8804             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
8805                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
8806             if (gameMode == TwoMachinesPlay) {
8807               GameEnds(machineWhite ? BlackWins : WhiteWins,
8808                        buf1, GE_XBOARD);
8809             }
8810             return;
8811         }
8812
8813         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
8814         /* So we have to redo legality test with true e.p. status here,  */
8815         /* to make sure an illegal e.p. capture does not slip through,   */
8816         /* to cause a forfeit on a justified illegal-move complaint      */
8817         /* of the opponent.                                              */
8818         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
8819            ChessMove moveType;
8820            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
8821                              fromY, fromX, toY, toX, promoChar);
8822             if(moveType == IllegalMove) {
8823               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
8824                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
8825                 GameEnds(machineWhite ? BlackWins : WhiteWins,
8826                            buf1, GE_XBOARD);
8827                 return;
8828            } else if(!appData.fischerCastling)
8829            /* [HGM] Kludge to handle engines that send FRC-style castling
8830               when they shouldn't (like TSCP-Gothic) */
8831            switch(moveType) {
8832              case WhiteASideCastleFR:
8833              case BlackASideCastleFR:
8834                toX+=2;
8835                currentMoveString[2]++;
8836                break;
8837              case WhiteHSideCastleFR:
8838              case BlackHSideCastleFR:
8839                toX--;
8840                currentMoveString[2]--;
8841                break;
8842              default: ; // nothing to do, but suppresses warning of pedantic compilers
8843            }
8844         }
8845         hintRequested = FALSE;
8846         lastHint[0] = NULLCHAR;
8847         bookRequested = FALSE;
8848         /* Program may be pondering now */
8849         cps->maybeThinking = TRUE;
8850         if (cps->sendTime == 2) cps->sendTime = 1;
8851         if (cps->offeredDraw) cps->offeredDraw--;
8852
8853         /* [AS] Save move info*/
8854         pvInfoList[ forwardMostMove ].score = programStats.score;
8855         pvInfoList[ forwardMostMove ].depth = programStats.depth;
8856         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
8857
8858         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
8859
8860         /* Test suites abort the 'game' after one move */
8861         if(*appData.finger) {
8862            static FILE *f;
8863            char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD
8864            if(!f) f = fopen(appData.finger, "w");
8865            if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f);
8866            else { DisplayFatalError("Bad output file", errno, 0); return; }
8867            free(fen);
8868            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8869         }
8870         if(appData.epd) {
8871            if(solvingTime >= 0) {
8872               snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
8873               totalTime += solvingTime; first.matchWins++;
8874            } else {
8875               snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
8876               second.matchWins++;
8877            }
8878            OutputKibitz(2, buf1);
8879            GameEnds(GameUnfinished, NULL, GE_XBOARD);
8880         }
8881
8882         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
8883         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
8884             int count = 0;
8885
8886             while( count < adjudicateLossPlies ) {
8887                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8888
8889                 if( count & 1 ) {
8890                     score = -score; /* Flip score for winning side */
8891                 }
8892
8893                 if( score > appData.adjudicateLossThreshold ) {
8894                     break;
8895                 }
8896
8897                 count++;
8898             }
8899
8900             if( count >= adjudicateLossPlies ) {
8901                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8902
8903                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8904                     "Xboard adjudication",
8905                     GE_XBOARD );
8906
8907                 return;
8908             }
8909         }
8910
8911         if(Adjudicate(cps)) {
8912             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8913             return; // [HGM] adjudicate: for all automatic game ends
8914         }
8915
8916 #if ZIPPY
8917         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8918             first.initDone) {
8919           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8920                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8921                 SendToICS("draw ");
8922                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8923           }
8924           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8925           ics_user_moved = 1;
8926           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8927                 char buf[3*MSG_SIZ];
8928
8929                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8930                         programStats.score / 100.,
8931                         programStats.depth,
8932                         programStats.time / 100.,
8933                         (unsigned int)programStats.nodes,
8934                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8935                         programStats.movelist);
8936                 SendToICS(buf);
8937           }
8938         }
8939 #endif
8940
8941         /* [AS] Clear stats for next move */
8942         ClearProgramStats();
8943         thinkOutput[0] = NULLCHAR;
8944         hiddenThinkOutputState = 0;
8945
8946         bookHit = NULL;
8947         if (gameMode == TwoMachinesPlay) {
8948             /* [HGM] relaying draw offers moved to after reception of move */
8949             /* and interpreting offer as claim if it brings draw condition */
8950             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8951                 SendToProgram("draw\n", cps->other);
8952             }
8953             if (cps->other->sendTime) {
8954                 SendTimeRemaining(cps->other,
8955                                   cps->other->twoMachinesColor[0] == 'w');
8956             }
8957             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8958             if (firstMove && !bookHit) {
8959                 firstMove = FALSE;
8960                 if (cps->other->useColors) {
8961                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8962                 }
8963                 SendToProgram("go\n", cps->other);
8964             }
8965             cps->other->maybeThinking = TRUE;
8966         }
8967
8968         roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
8969
8970         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8971
8972         if (!pausing && appData.ringBellAfterMoves) {
8973             if(!roar) RingBell();
8974         }
8975
8976         /*
8977          * Reenable menu items that were disabled while
8978          * machine was thinking
8979          */
8980         if (gameMode != TwoMachinesPlay)
8981             SetUserThinkingEnables();
8982
8983         // [HGM] book: after book hit opponent has received move and is now in force mode
8984         // force the book reply into it, and then fake that it outputted this move by jumping
8985         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8986         if(bookHit) {
8987                 static char bookMove[MSG_SIZ]; // a bit generous?
8988
8989                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8990                 strcat(bookMove, bookHit);
8991                 message = bookMove;
8992                 cps = cps->other;
8993                 programStats.nodes = programStats.depth = programStats.time =
8994                 programStats.score = programStats.got_only_move = 0;
8995                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8996
8997                 if(cps->lastPing != cps->lastPong) {
8998                     savedMessage = message; // args for deferred call
8999                     savedState = cps;
9000                     ScheduleDelayedEvent(DeferredBookMove, 10);
9001                     return;
9002                 }
9003                 goto FakeBookMove;
9004         }
9005
9006         return;
9007     }
9008
9009     /* Set special modes for chess engines.  Later something general
9010      *  could be added here; for now there is just one kludge feature,
9011      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
9012      *  when "xboard" is given as an interactive command.
9013      */
9014     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
9015         cps->useSigint = FALSE;
9016         cps->useSigterm = FALSE;
9017     }
9018     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
9019       ParseFeatures(message+8, cps);
9020       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
9021     }
9022
9023     if (!strncmp(message, "setup ", 6) && 
9024         (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
9025           NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
9026                                         ) { // [HGM] allow first engine to define opening position
9027       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
9028       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
9029       *buf = NULLCHAR;
9030       if(sscanf(message, "setup (%s", buf) == 1) {
9031         s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
9032         ASSIGN(appData.pieceToCharTable, buf);
9033       }
9034       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
9035       if(dummy >= 3) {
9036         while(message[s] && message[s++] != ' ');
9037         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
9038            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
9039             appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
9040             if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
9041           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
9042           if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // do again, for it was spoiled by InitPosition
9043           startedFromSetupPosition = FALSE;
9044         }
9045       }
9046       if(startedFromSetupPosition) return;
9047       ParseFEN(boards[0], &dummy, message+s, FALSE);
9048       DrawPosition(TRUE, boards[0]);
9049       CopyBoard(initialPosition, boards[0]);
9050       startedFromSetupPosition = TRUE;
9051       return;
9052     }
9053     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
9054       ChessSquare piece = WhitePawn;
9055       char *p=message+6, *q, *s = SUFFIXES, ID = *p;
9056       if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
9057       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
9058       piece += CharToPiece(ID & 255) - WhitePawn;
9059       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
9060       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
9061       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
9062       /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
9063       /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
9064       /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
9065                                                && gameInfo.variant != VariantGreat
9066                                                && gameInfo.variant != VariantFairy    ) return;
9067       if(piece < EmptySquare) {
9068         pieceDefs = TRUE;
9069         ASSIGN(pieceDesc[piece], buf1);
9070         if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
9071       }
9072       return;
9073     }
9074     if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
9075       promoSweep = PieceToChar(forwardMostMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
9076       Sweep(0);
9077       return;
9078     }
9079     /* [HGM] Allow engine to set up a position. Don't ask me why one would
9080      * want this, I was asked to put it in, and obliged.
9081      */
9082     if (!strncmp(message, "setboard ", 9)) {
9083         Board initial_position;
9084
9085         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
9086
9087         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) {
9088             DisplayError(_("Bad FEN received from engine"), 0);
9089             return ;
9090         } else {
9091            Reset(TRUE, FALSE);
9092            CopyBoard(boards[0], initial_position);
9093            initialRulePlies = FENrulePlies;
9094            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
9095            else gameMode = MachinePlaysBlack;
9096            DrawPosition(FALSE, boards[currentMove]);
9097         }
9098         return;
9099     }
9100
9101     /*
9102      * Look for communication commands
9103      */
9104     if (!strncmp(message, "telluser ", 9)) {
9105         if(message[9] == '\\' && message[10] == '\\')
9106             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
9107         PlayTellSound();
9108         DisplayNote(message + 9);
9109         return;
9110     }
9111     if (!strncmp(message, "tellusererror ", 14)) {
9112         cps->userError = 1;
9113         if(message[14] == '\\' && message[15] == '\\')
9114             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
9115         PlayTellSound();
9116         DisplayError(message + 14, 0);
9117         return;
9118     }
9119     if (!strncmp(message, "tellopponent ", 13)) {
9120       if (appData.icsActive) {
9121         if (loggedOn) {
9122           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
9123           SendToICS(buf1);
9124         }
9125       } else {
9126         DisplayNote(message + 13);
9127       }
9128       return;
9129     }
9130     if (!strncmp(message, "tellothers ", 11)) {
9131       if (appData.icsActive) {
9132         if (loggedOn) {
9133           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
9134           SendToICS(buf1);
9135         }
9136       } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment
9137       return;
9138     }
9139     if (!strncmp(message, "tellall ", 8)) {
9140       if (appData.icsActive) {
9141         if (loggedOn) {
9142           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
9143           SendToICS(buf1);
9144         }
9145       } else {
9146         DisplayNote(message + 8);
9147       }
9148       return;
9149     }
9150     if (strncmp(message, "warning", 7) == 0) {
9151         /* Undocumented feature, use tellusererror in new code */
9152         DisplayError(message, 0);
9153         return;
9154     }
9155     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
9156         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
9157         strcat(realname, " query");
9158         AskQuestion(realname, buf2, buf1, cps->pr);
9159         return;
9160     }
9161     /* Commands from the engine directly to ICS.  We don't allow these to be
9162      *  sent until we are logged on. Crafty kibitzes have been known to
9163      *  interfere with the login process.
9164      */
9165     if (loggedOn) {
9166         if (!strncmp(message, "tellics ", 8)) {
9167             SendToICS(message + 8);
9168             SendToICS("\n");
9169             return;
9170         }
9171         if (!strncmp(message, "tellicsnoalias ", 15)) {
9172             SendToICS(ics_prefix);
9173             SendToICS(message + 15);
9174             SendToICS("\n");
9175             return;
9176         }
9177         /* The following are for backward compatibility only */
9178         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
9179             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
9180             SendToICS(ics_prefix);
9181             SendToICS(message);
9182             SendToICS("\n");
9183             return;
9184         }
9185     }
9186     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
9187         if(initPing == cps->lastPong) {
9188             if(gameInfo.variant == VariantUnknown) {
9189                 DisplayError(_("Engine did not send setup for non-standard variant"), 0);
9190                 *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
9191                 GameEnds(GameUnfinished, NULL, GE_XBOARD);
9192             }
9193             initPing = -1;
9194         }
9195         if(cps->lastPing == cps->lastPong && abortEngineThink) {
9196             abortEngineThink = FALSE;
9197             DisplayMessage("", "");
9198             ThawUI();
9199         }
9200         return;
9201     }
9202     if(!strncmp(message, "highlight ", 10)) {
9203         if(appData.testLegality && !*engineVariant && appData.markers) return;
9204         MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
9205         return;
9206     }
9207     if(!strncmp(message, "click ", 6)) {
9208         char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
9209         if(appData.testLegality || !appData.oneClick) return;
9210         sscanf(message+6, "%c%d%c", &f, &y, &c);
9211         x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
9212         if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
9213         x = x*squareSize + (x+1)*lineGap + squareSize/2;
9214         y = y*squareSize + (y+1)*lineGap + squareSize/2;
9215         f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
9216         if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
9217             LeftClick(Release, lastLeftX, lastLeftY);
9218         controlKey  = (c == ',');
9219         LeftClick(Press, x, y);
9220         LeftClick(Release, x, y);
9221         first.highlight = f;
9222         return;
9223     }
9224     /*
9225      * If the move is illegal, cancel it and redraw the board.
9226      * Also deal with other error cases.  Matching is rather loose
9227      * here to accommodate engines written before the spec.
9228      */
9229     if (strncmp(message + 1, "llegal move", 11) == 0 ||
9230         strncmp(message, "Error", 5) == 0) {
9231         if (StrStr(message, "name") ||
9232             StrStr(message, "rating") || StrStr(message, "?") ||
9233             StrStr(message, "result") || StrStr(message, "board") ||
9234             StrStr(message, "bk") || StrStr(message, "computer") ||
9235             StrStr(message, "variant") || StrStr(message, "hint") ||
9236             StrStr(message, "random") || StrStr(message, "depth") ||
9237             StrStr(message, "accepted")) {
9238             return;
9239         }
9240         if (StrStr(message, "protover")) {
9241           /* Program is responding to input, so it's apparently done
9242              initializing, and this error message indicates it is
9243              protocol version 1.  So we don't need to wait any longer
9244              for it to initialize and send feature commands. */
9245           FeatureDone(cps, 1);
9246           cps->protocolVersion = 1;
9247           return;
9248         }
9249         cps->maybeThinking = FALSE;
9250
9251         if (StrStr(message, "draw")) {
9252             /* Program doesn't have "draw" command */
9253             cps->sendDrawOffers = 0;
9254             return;
9255         }
9256         if (cps->sendTime != 1 &&
9257             (StrStr(message, "time") || StrStr(message, "otim"))) {
9258           /* Program apparently doesn't have "time" or "otim" command */
9259           cps->sendTime = 0;
9260           return;
9261         }
9262         if (StrStr(message, "analyze")) {
9263             cps->analysisSupport = FALSE;
9264             cps->analyzing = FALSE;
9265 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
9266             EditGameEvent(); // [HGM] try to preserve loaded game
9267             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
9268             DisplayError(buf2, 0);
9269             return;
9270         }
9271         if (StrStr(message, "(no matching move)st")) {
9272           /* Special kludge for GNU Chess 4 only */
9273           cps->stKludge = TRUE;
9274           SendTimeControl(cps, movesPerSession, timeControl,
9275                           timeIncrement, appData.searchDepth,
9276                           searchTime);
9277           return;
9278         }
9279         if (StrStr(message, "(no matching move)sd")) {
9280           /* Special kludge for GNU Chess 4 only */
9281           cps->sdKludge = TRUE;
9282           SendTimeControl(cps, movesPerSession, timeControl,
9283                           timeIncrement, appData.searchDepth,
9284                           searchTime);
9285           return;
9286         }
9287         if (!StrStr(message, "llegal")) {
9288             return;
9289         }
9290         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9291             gameMode == IcsIdle) return;
9292         if (forwardMostMove <= backwardMostMove) return;
9293         if (pausing) PauseEvent();
9294       if(appData.forceIllegal) {
9295             // [HGM] illegal: machine refused move; force position after move into it
9296           SendToProgram("force\n", cps);
9297           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
9298                 // we have a real problem now, as SendBoard will use the a2a3 kludge
9299                 // when black is to move, while there might be nothing on a2 or black
9300                 // might already have the move. So send the board as if white has the move.
9301                 // But first we must change the stm of the engine, as it refused the last move
9302                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
9303                 if(WhiteOnMove(forwardMostMove)) {
9304                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
9305                     SendBoard(cps, forwardMostMove); // kludgeless board
9306                 } else {
9307                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
9308                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9309                     SendBoard(cps, forwardMostMove+1); // kludgeless board
9310                 }
9311           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
9312             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
9313                  gameMode == TwoMachinesPlay)
9314               SendToProgram("go\n", cps);
9315             return;
9316       } else
9317         if (gameMode == PlayFromGameFile) {
9318             /* Stop reading this game file */
9319             gameMode = EditGame;
9320             ModeHighlight();
9321         }
9322         /* [HGM] illegal-move claim should forfeit game when Xboard */
9323         /* only passes fully legal moves                            */
9324         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
9325             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
9326                                 "False illegal-move claim", GE_XBOARD );
9327             return; // do not take back move we tested as valid
9328         }
9329         currentMove = forwardMostMove-1;
9330         DisplayMove(currentMove-1); /* before DisplayMoveError */
9331         SwitchClocks(forwardMostMove-1); // [HGM] race
9332         DisplayBothClocks();
9333         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
9334                 parseList[currentMove], _(cps->which));
9335         DisplayMoveError(buf1);
9336         DrawPosition(FALSE, boards[currentMove]);
9337
9338         SetUserThinkingEnables();
9339         return;
9340     }
9341     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
9342         /* Program has a broken "time" command that
9343            outputs a string not ending in newline.
9344            Don't use it. */
9345         cps->sendTime = 0;
9346     }
9347     if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
9348         if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
9349             sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
9350     }
9351
9352     /*
9353      * If chess program startup fails, exit with an error message.
9354      * Attempts to recover here are futile. [HGM] Well, we try anyway
9355      */
9356     if ((StrStr(message, "unknown host") != NULL)
9357         || (StrStr(message, "No remote directory") != NULL)
9358         || (StrStr(message, "not found") != NULL)
9359         || (StrStr(message, "No such file") != NULL)
9360         || (StrStr(message, "can't alloc") != NULL)
9361         || (StrStr(message, "Permission denied") != NULL)) {
9362
9363         cps->maybeThinking = FALSE;
9364         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
9365                 _(cps->which), cps->program, cps->host, message);
9366         RemoveInputSource(cps->isr);
9367         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
9368             if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
9369             if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
9370         }
9371         return;
9372     }
9373
9374     /*
9375      * Look for hint output
9376      */
9377     if (sscanf(message, "Hint: %s", buf1) == 1) {
9378         if (cps == &first && hintRequested) {
9379             hintRequested = FALSE;
9380             if (ParseOneMove(buf1, forwardMostMove, &moveType,
9381                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
9382                 (void) CoordsToAlgebraic(boards[forwardMostMove],
9383                                     PosFlags(forwardMostMove),
9384                                     fromY, fromX, toY, toX, promoChar, buf1);
9385                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
9386                 DisplayInformation(buf2);
9387             } else {
9388                 /* Hint move could not be parsed!? */
9389               snprintf(buf2, sizeof(buf2),
9390                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
9391                         buf1, _(cps->which));
9392                 DisplayError(buf2, 0);
9393             }
9394         } else {
9395           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
9396         }
9397         return;
9398     }
9399
9400     /*
9401      * Ignore other messages if game is not in progress
9402      */
9403     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
9404         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
9405
9406     /*
9407      * look for win, lose, draw, or draw offer
9408      */
9409     if (strncmp(message, "1-0", 3) == 0) {
9410         char *p, *q, *r = "";
9411         p = strchr(message, '{');
9412         if (p) {
9413             q = strchr(p, '}');
9414             if (q) {
9415                 *q = NULLCHAR;
9416                 r = p + 1;
9417             }
9418         }
9419         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
9420         return;
9421     } else if (strncmp(message, "0-1", 3) == 0) {
9422         char *p, *q, *r = "";
9423         p = strchr(message, '{');
9424         if (p) {
9425             q = strchr(p, '}');
9426             if (q) {
9427                 *q = NULLCHAR;
9428                 r = p + 1;
9429             }
9430         }
9431         /* Kludge for Arasan 4.1 bug */
9432         if (strcmp(r, "Black resigns") == 0) {
9433             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
9434             return;
9435         }
9436         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
9437         return;
9438     } else if (strncmp(message, "1/2", 3) == 0) {
9439         char *p, *q, *r = "";
9440         p = strchr(message, '{');
9441         if (p) {
9442             q = strchr(p, '}');
9443             if (q) {
9444                 *q = NULLCHAR;
9445                 r = p + 1;
9446             }
9447         }
9448
9449         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
9450         return;
9451
9452     } else if (strncmp(message, "White resign", 12) == 0) {
9453         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9454         return;
9455     } else if (strncmp(message, "Black resign", 12) == 0) {
9456         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9457         return;
9458     } else if (strncmp(message, "White matches", 13) == 0 ||
9459                strncmp(message, "Black matches", 13) == 0   ) {
9460         /* [HGM] ignore GNUShogi noises */
9461         return;
9462     } else if (strncmp(message, "White", 5) == 0 &&
9463                message[5] != '(' &&
9464                StrStr(message, "Black") == NULL) {
9465         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9466         return;
9467     } else if (strncmp(message, "Black", 5) == 0 &&
9468                message[5] != '(') {
9469         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9470         return;
9471     } else if (strcmp(message, "resign") == 0 ||
9472                strcmp(message, "computer resigns") == 0) {
9473         switch (gameMode) {
9474           case MachinePlaysBlack:
9475           case IcsPlayingBlack:
9476             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
9477             break;
9478           case MachinePlaysWhite:
9479           case IcsPlayingWhite:
9480             GameEnds(BlackWins, "White resigns", GE_ENGINE);
9481             break;
9482           case TwoMachinesPlay:
9483             if (cps->twoMachinesColor[0] == 'w')
9484               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
9485             else
9486               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
9487             break;
9488           default:
9489             /* can't happen */
9490             break;
9491         }
9492         return;
9493     } else if (strncmp(message, "opponent mates", 14) == 0) {
9494         switch (gameMode) {
9495           case MachinePlaysBlack:
9496           case IcsPlayingBlack:
9497             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9498             break;
9499           case MachinePlaysWhite:
9500           case IcsPlayingWhite:
9501             GameEnds(BlackWins, "Black mates", GE_ENGINE);
9502             break;
9503           case TwoMachinesPlay:
9504             if (cps->twoMachinesColor[0] == 'w')
9505               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9506             else
9507               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9508             break;
9509           default:
9510             /* can't happen */
9511             break;
9512         }
9513         return;
9514     } else if (strncmp(message, "computer mates", 14) == 0) {
9515         switch (gameMode) {
9516           case MachinePlaysBlack:
9517           case IcsPlayingBlack:
9518             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
9519             break;
9520           case MachinePlaysWhite:
9521           case IcsPlayingWhite:
9522             GameEnds(WhiteWins, "White mates", GE_ENGINE);
9523             break;
9524           case TwoMachinesPlay:
9525             if (cps->twoMachinesColor[0] == 'w')
9526               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9527             else
9528               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9529             break;
9530           default:
9531             /* can't happen */
9532             break;
9533         }
9534         return;
9535     } else if (strncmp(message, "checkmate", 9) == 0) {
9536         if (WhiteOnMove(forwardMostMove)) {
9537             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
9538         } else {
9539             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
9540         }
9541         return;
9542     } else if (strstr(message, "Draw") != NULL ||
9543                strstr(message, "game is a draw") != NULL) {
9544         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
9545         return;
9546     } else if (strstr(message, "offer") != NULL &&
9547                strstr(message, "draw") != NULL) {
9548 #if ZIPPY
9549         if (appData.zippyPlay && first.initDone) {
9550             /* Relay offer to ICS */
9551             SendToICS(ics_prefix);
9552             SendToICS("draw\n");
9553         }
9554 #endif
9555         cps->offeredDraw = 2; /* valid until this engine moves twice */
9556         if (gameMode == TwoMachinesPlay) {
9557             if (cps->other->offeredDraw) {
9558                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9559             /* [HGM] in two-machine mode we delay relaying draw offer      */
9560             /* until after we also have move, to see if it is really claim */
9561             }
9562         } else if (gameMode == MachinePlaysWhite ||
9563                    gameMode == MachinePlaysBlack) {
9564           if (userOfferedDraw) {
9565             DisplayInformation(_("Machine accepts your draw offer"));
9566             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
9567           } else {
9568             DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
9569           }
9570         }
9571     }
9572
9573
9574     /*
9575      * Look for thinking output
9576      */
9577     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
9578           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9579                                 ) {
9580         int plylev, mvleft, mvtot, curscore, time;
9581         char mvname[MOVE_LEN];
9582         u64 nodes; // [DM]
9583         char plyext;
9584         int ignore = FALSE;
9585         int prefixHint = FALSE;
9586         mvname[0] = NULLCHAR;
9587
9588         switch (gameMode) {
9589           case MachinePlaysBlack:
9590           case IcsPlayingBlack:
9591             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9592             break;
9593           case MachinePlaysWhite:
9594           case IcsPlayingWhite:
9595             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
9596             break;
9597           case AnalyzeMode:
9598           case AnalyzeFile:
9599             break;
9600           case IcsObserving: /* [DM] icsEngineAnalyze */
9601             if (!appData.icsEngineAnalyze) ignore = TRUE;
9602             break;
9603           case TwoMachinesPlay:
9604             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
9605                 ignore = TRUE;
9606             }
9607             break;
9608           default:
9609             ignore = TRUE;
9610             break;
9611         }
9612
9613         if (!ignore) {
9614             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
9615             buf1[0] = NULLCHAR;
9616             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9617                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
9618                 char score_buf[MSG_SIZ];
9619
9620                 if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
9621                     nodes += u64Const(0x100000000);
9622
9623                 if (plyext != ' ' && plyext != '\t') {
9624                     time *= 100;
9625                 }
9626
9627                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9628                 if( cps->scoreIsAbsolute &&
9629                     ( gameMode == MachinePlaysBlack ||
9630                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
9631                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
9632                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
9633                      !WhiteOnMove(currentMove)
9634                     ) )
9635                 {
9636                     curscore = -curscore;
9637                 }
9638
9639                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
9640
9641                 if(*bestMove) { // rememer time best EPD move was first found
9642                     int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
9643                     ChessMove mt;
9644                     int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
9645                     ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
9646                     solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
9647                 }
9648
9649                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
9650                         char buf[MSG_SIZ];
9651                         FILE *f;
9652                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
9653                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
9654                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
9655                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
9656                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
9657                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
9658                                 fclose(f);
9659                         }
9660                         else
9661                           /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
9662                           DisplayError(_("failed writing PV"), 0);
9663                 }
9664
9665                 tempStats.depth = plylev;
9666                 tempStats.nodes = nodes;
9667                 tempStats.time = time;
9668                 tempStats.score = curscore;
9669                 tempStats.got_only_move = 0;
9670
9671                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
9672                         int ticklen;
9673
9674                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
9675                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
9676                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
9677                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
9678                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
9679                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
9680                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
9681                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
9682                 }
9683
9684                 /* Buffer overflow protection */
9685                 if (pv[0] != NULLCHAR) {
9686                     if (strlen(pv) >= sizeof(tempStats.movelist)
9687                         && appData.debugMode) {
9688                         fprintf(debugFP,
9689                                 "PV is too long; using the first %u bytes.\n",
9690                                 (unsigned) sizeof(tempStats.movelist) - 1);
9691                     }
9692
9693                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
9694                 } else {
9695                     sprintf(tempStats.movelist, " no PV\n");
9696                 }
9697
9698                 if (tempStats.seen_stat) {
9699                     tempStats.ok_to_send = 1;
9700                 }
9701
9702                 if (strchr(tempStats.movelist, '(') != NULL) {
9703                     tempStats.line_is_book = 1;
9704                     tempStats.nr_moves = 0;
9705                     tempStats.moves_left = 0;
9706                 } else {
9707                     tempStats.line_is_book = 0;
9708                 }
9709
9710                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
9711                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
9712
9713                 SendProgramStatsToFrontend( cps, &tempStats );
9714
9715                 /*
9716                     [AS] Protect the thinkOutput buffer from overflow... this
9717                     is only useful if buf1 hasn't overflowed first!
9718                 */
9719                 if(curscore >= MATE_SCORE) 
9720                     snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
9721                 else if(curscore <= -MATE_SCORE) 
9722                     snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
9723                 else
9724                     snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
9725                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
9726                          plylev,
9727                          (gameMode == TwoMachinesPlay ?
9728                           ToUpper(cps->twoMachinesColor[0]) : ' '),
9729                          score_buf,
9730                          prefixHint ? lastHint : "",
9731                          prefixHint ? " " : "" );
9732
9733                 if( buf1[0] != NULLCHAR ) {
9734                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
9735
9736                     if( strlen(pv) > max_len ) {
9737                         if( appData.debugMode) {
9738                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
9739                         }
9740                         pv[max_len+1] = '\0';
9741                     }
9742
9743                     strcat( thinkOutput, pv);
9744                 }
9745
9746                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
9747                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9748                     DisplayMove(currentMove - 1);
9749                 }
9750                 return;
9751
9752             } else if ((p=StrStr(message, "(only move)")) != NULL) {
9753                 /* crafty (9.25+) says "(only move) <move>"
9754                  * if there is only 1 legal move
9755                  */
9756                 sscanf(p, "(only move) %s", buf1);
9757                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
9758                 sprintf(programStats.movelist, "%s (only move)", buf1);
9759                 programStats.depth = 1;
9760                 programStats.nr_moves = 1;
9761                 programStats.moves_left = 1;
9762                 programStats.nodes = 1;
9763                 programStats.time = 1;
9764                 programStats.got_only_move = 1;
9765
9766                 /* Not really, but we also use this member to
9767                    mean "line isn't going to change" (Crafty
9768                    isn't searching, so stats won't change) */
9769                 programStats.line_is_book = 1;
9770
9771                 SendProgramStatsToFrontend( cps, &programStats );
9772
9773                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9774                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9775                     DisplayMove(currentMove - 1);
9776                 }
9777                 return;
9778             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
9779                               &time, &nodes, &plylev, &mvleft,
9780                               &mvtot, mvname) >= 5) {
9781                 /* The stat01: line is from Crafty (9.29+) in response
9782                    to the "." command */
9783                 programStats.seen_stat = 1;
9784                 cps->maybeThinking = TRUE;
9785
9786                 if (programStats.got_only_move || !appData.periodicUpdates)
9787                   return;
9788
9789                 programStats.depth = plylev;
9790                 programStats.time = time;
9791                 programStats.nodes = nodes;
9792                 programStats.moves_left = mvleft;
9793                 programStats.nr_moves = mvtot;
9794                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
9795                 programStats.ok_to_send = 1;
9796                 programStats.movelist[0] = '\0';
9797
9798                 SendProgramStatsToFrontend( cps, &programStats );
9799
9800                 return;
9801
9802             } else if (strncmp(message,"++",2) == 0) {
9803                 /* Crafty 9.29+ outputs this */
9804                 programStats.got_fail = 2;
9805                 return;
9806
9807             } else if (strncmp(message,"--",2) == 0) {
9808                 /* Crafty 9.29+ outputs this */
9809                 programStats.got_fail = 1;
9810                 return;
9811
9812             } else if (thinkOutput[0] != NULLCHAR &&
9813                        strncmp(message, "    ", 4) == 0) {
9814                 unsigned message_len;
9815
9816                 p = message;
9817                 while (*p && *p == ' ') p++;
9818
9819                 message_len = strlen( p );
9820
9821                 /* [AS] Avoid buffer overflow */
9822                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
9823                     strcat(thinkOutput, " ");
9824                     strcat(thinkOutput, p);
9825                 }
9826
9827                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
9828                     strcat(programStats.movelist, " ");
9829                     strcat(programStats.movelist, p);
9830                 }
9831
9832                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
9833                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
9834                     DisplayMove(currentMove - 1);
9835                 }
9836                 return;
9837             }
9838         }
9839         else {
9840             buf1[0] = NULLCHAR;
9841
9842             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
9843                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
9844             {
9845                 ChessProgramStats cpstats;
9846
9847                 if (plyext != ' ' && plyext != '\t') {
9848                     time *= 100;
9849                 }
9850
9851                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
9852                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
9853                     curscore = -curscore;
9854                 }
9855
9856                 cpstats.depth = plylev;
9857                 cpstats.nodes = nodes;
9858                 cpstats.time = time;
9859                 cpstats.score = curscore;
9860                 cpstats.got_only_move = 0;
9861                 cpstats.movelist[0] = '\0';
9862
9863                 if (buf1[0] != NULLCHAR) {
9864                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
9865                 }
9866
9867                 cpstats.ok_to_send = 0;
9868                 cpstats.line_is_book = 0;
9869                 cpstats.nr_moves = 0;
9870                 cpstats.moves_left = 0;
9871
9872                 SendProgramStatsToFrontend( cps, &cpstats );
9873             }
9874         }
9875     }
9876 }
9877
9878
9879 /* Parse a game score from the character string "game", and
9880    record it as the history of the current game.  The game
9881    score is NOT assumed to start from the standard position.
9882    The display is not updated in any way.
9883    */
9884 void
9885 ParseGameHistory (char *game)
9886 {
9887     ChessMove moveType;
9888     int fromX, fromY, toX, toY, boardIndex;
9889     char promoChar;
9890     char *p, *q;
9891     char buf[MSG_SIZ];
9892
9893     if (appData.debugMode)
9894       fprintf(debugFP, "Parsing game history: %s\n", game);
9895
9896     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
9897     gameInfo.site = StrSave(appData.icsHost);
9898     gameInfo.date = PGNDate();
9899     gameInfo.round = StrSave("-");
9900
9901     /* Parse out names of players */
9902     while (*game == ' ') game++;
9903     p = buf;
9904     while (*game != ' ') *p++ = *game++;
9905     *p = NULLCHAR;
9906     gameInfo.white = StrSave(buf);
9907     while (*game == ' ') game++;
9908     p = buf;
9909     while (*game != ' ' && *game != '\n') *p++ = *game++;
9910     *p = NULLCHAR;
9911     gameInfo.black = StrSave(buf);
9912
9913     /* Parse moves */
9914     boardIndex = blackPlaysFirst ? 1 : 0;
9915     yynewstr(game);
9916     for (;;) {
9917         yyboardindex = boardIndex;
9918         moveType = (ChessMove) Myylex();
9919         switch (moveType) {
9920           case IllegalMove:             /* maybe suicide chess, etc. */
9921   if (appData.debugMode) {
9922     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
9923     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9924     setbuf(debugFP, NULL);
9925   }
9926           case WhitePromotion:
9927           case BlackPromotion:
9928           case WhiteNonPromotion:
9929           case BlackNonPromotion:
9930           case NormalMove:
9931           case FirstLeg:
9932           case WhiteCapturesEnPassant:
9933           case BlackCapturesEnPassant:
9934           case WhiteKingSideCastle:
9935           case WhiteQueenSideCastle:
9936           case BlackKingSideCastle:
9937           case BlackQueenSideCastle:
9938           case WhiteKingSideCastleWild:
9939           case WhiteQueenSideCastleWild:
9940           case BlackKingSideCastleWild:
9941           case BlackQueenSideCastleWild:
9942           /* PUSH Fabien */
9943           case WhiteHSideCastleFR:
9944           case WhiteASideCastleFR:
9945           case BlackHSideCastleFR:
9946           case BlackASideCastleFR:
9947           /* POP Fabien */
9948             fromX = currentMoveString[0] - AAA;
9949             fromY = currentMoveString[1] - ONE;
9950             toX = currentMoveString[2] - AAA;
9951             toY = currentMoveString[3] - ONE;
9952             promoChar = currentMoveString[4];
9953             break;
9954           case WhiteDrop:
9955           case BlackDrop:
9956             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
9957             fromX = moveType == WhiteDrop ?
9958               (int) CharToPiece(ToUpper(currentMoveString[0])) :
9959             (int) CharToPiece(ToLower(currentMoveString[0]));
9960             fromY = DROP_RANK;
9961             toX = currentMoveString[2] - AAA;
9962             toY = currentMoveString[3] - ONE;
9963             promoChar = NULLCHAR;
9964             break;
9965           case AmbiguousMove:
9966             /* bug? */
9967             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
9968   if (appData.debugMode) {
9969     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
9970     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9971     setbuf(debugFP, NULL);
9972   }
9973             DisplayError(buf, 0);
9974             return;
9975           case ImpossibleMove:
9976             /* bug? */
9977             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
9978   if (appData.debugMode) {
9979     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
9980     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
9981     setbuf(debugFP, NULL);
9982   }
9983             DisplayError(buf, 0);
9984             return;
9985           case EndOfFile:
9986             if (boardIndex < backwardMostMove) {
9987                 /* Oops, gap.  How did that happen? */
9988                 DisplayError(_("Gap in move list"), 0);
9989                 return;
9990             }
9991             backwardMostMove =  blackPlaysFirst ? 1 : 0;
9992             if (boardIndex > forwardMostMove) {
9993                 forwardMostMove = boardIndex;
9994             }
9995             return;
9996           case ElapsedTime:
9997             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9998                 strcat(parseList[boardIndex-1], " ");
9999                 strcat(parseList[boardIndex-1], yy_text);
10000             }
10001             continue;
10002           case Comment:
10003           case PGNTag:
10004           case NAG:
10005           default:
10006             /* ignore */
10007             continue;
10008           case WhiteWins:
10009           case BlackWins:
10010           case GameIsDrawn:
10011           case GameUnfinished:
10012             if (gameMode == IcsExamining) {
10013                 if (boardIndex < backwardMostMove) {
10014                     /* Oops, gap.  How did that happen? */
10015                     return;
10016                 }
10017                 backwardMostMove = blackPlaysFirst ? 1 : 0;
10018                 return;
10019             }
10020             gameInfo.result = moveType;
10021             p = strchr(yy_text, '{');
10022             if (p == NULL) p = strchr(yy_text, '(');
10023             if (p == NULL) {
10024                 p = yy_text;
10025                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10026             } else {
10027                 q = strchr(p, *p == '{' ? '}' : ')');
10028                 if (q != NULL) *q = NULLCHAR;
10029                 p++;
10030             }
10031             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10032             gameInfo.resultDetails = StrSave(p);
10033             continue;
10034         }
10035         if (boardIndex >= forwardMostMove &&
10036             !(gameMode == IcsObserving && ics_gamenum == -1)) {
10037             backwardMostMove = blackPlaysFirst ? 1 : 0;
10038             return;
10039         }
10040         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
10041                                  fromY, fromX, toY, toX, promoChar,
10042                                  parseList[boardIndex]);
10043         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
10044         /* currentMoveString is set as a side-effect of yylex */
10045         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
10046         strcat(moveList[boardIndex], "\n");
10047         boardIndex++;
10048         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
10049         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
10050           case MT_NONE:
10051           case MT_STALEMATE:
10052           default:
10053             break;
10054           case MT_CHECK:
10055             if(!IS_SHOGI(gameInfo.variant))
10056                 strcat(parseList[boardIndex - 1], "+");
10057             break;
10058           case MT_CHECKMATE:
10059           case MT_STAINMATE:
10060             strcat(parseList[boardIndex - 1], "#");
10061             break;
10062         }
10063     }
10064 }
10065
10066
10067 /* Apply a move to the given board  */
10068 void
10069 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
10070 {
10071   ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
10072   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
10073
10074     /* [HGM] compute & store e.p. status and castling rights for new position */
10075     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
10076
10077       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
10078       oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
10079       board[EP_STATUS] = EP_NONE;
10080       board[EP_FILE] = board[EP_RANK] = 100;
10081
10082   if (fromY == DROP_RANK) {
10083         /* must be first */
10084         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
10085             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
10086             return;
10087         }
10088         piece = board[toY][toX] = (ChessSquare) fromX;
10089   } else {
10090 //      ChessSquare victim;
10091       int i;
10092
10093       if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
10094 //           victim = board[killY][killX],
10095            killed = board[killY][killX],
10096            board[killY][killX] = EmptySquare,
10097            board[EP_STATUS] = EP_CAPTURE;
10098            if( kill2X >= 0 && kill2Y >= 0)
10099              killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
10100       }
10101
10102       if( board[toY][toX] != EmptySquare ) {
10103            board[EP_STATUS] = EP_CAPTURE;
10104            if( (fromX != toX || fromY != toY) && // not igui!
10105                (captured == WhiteLion && board[fromY][fromX] != BlackLion ||
10106                 captured == BlackLion && board[fromY][fromX] != WhiteLion   ) ) { // [HGM] lion: Chu Lion-capture rules
10107                board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed
10108            }
10109       }
10110
10111       pawn = board[fromY][fromX];
10112       if( pawn == WhiteLance || pawn == BlackLance ) {
10113            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
10114                if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
10115                else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
10116            }
10117       }
10118       if( pawn == WhitePawn ) {
10119            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10120                board[EP_STATUS] = EP_PAWN_MOVE;
10121            if( toY-fromY>=2) {
10122                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
10123                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
10124                         gameInfo.variant != VariantBerolina || toX < fromX)
10125                       board[EP_STATUS] = toX | berolina;
10126                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
10127                         gameInfo.variant != VariantBerolina || toX > fromX)
10128                       board[EP_STATUS] = toX;
10129            }
10130       } else
10131       if( pawn == BlackPawn ) {
10132            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
10133                board[EP_STATUS] = EP_PAWN_MOVE;
10134            if( toY-fromY<= -2) {
10135                board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
10136                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
10137                         gameInfo.variant != VariantBerolina || toX < fromX)
10138                       board[EP_STATUS] = toX | berolina;
10139                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
10140                         gameInfo.variant != VariantBerolina || toX > fromX)
10141                       board[EP_STATUS] = toX;
10142            }
10143        }
10144
10145        if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
10146        if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
10147        if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
10148        if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
10149
10150        for(i=0; i<nrCastlingRights; i++) {
10151            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
10152               board[CASTLING][i] == toX   && castlingRank[i] == toY
10153              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
10154        }
10155
10156        if(gameInfo.variant == VariantSChess) { // update virginity
10157            if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
10158            if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
10159            if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
10160            if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
10161        }
10162
10163      if (fromX == toX && fromY == toY) return;
10164
10165      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
10166      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
10167      if(gameInfo.variant == VariantKnightmate)
10168          king += (int) WhiteUnicorn - (int) WhiteKing;
10169
10170     if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
10171        && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples own
10172         board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
10173         board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
10174         board[EP_STATUS] = EP_NONE; // capture was fake!
10175     } else
10176     if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
10177         board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
10178         board[toY][toX] = piece;
10179         board[EP_STATUS] = EP_NONE; // capture was fake!
10180     } else
10181     /* Code added by Tord: */
10182     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
10183     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
10184         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
10185       board[EP_STATUS] = EP_NONE; // capture was fake!
10186       board[fromY][fromX] = EmptySquare;
10187       board[toY][toX] = EmptySquare;
10188       if((toX > fromX) != (piece == WhiteRook)) {
10189         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
10190       } else {
10191         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
10192       }
10193     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
10194                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
10195       board[EP_STATUS] = EP_NONE;
10196       board[fromY][fromX] = EmptySquare;
10197       board[toY][toX] = EmptySquare;
10198       if((toX > fromX) != (piece == BlackRook)) {
10199         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
10200       } else {
10201         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
10202       }
10203     /* End of code added by Tord */
10204
10205     } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
10206         board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
10207         board[toY][toX] = piece;
10208     } else if (board[fromY][fromX] == king
10209         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10210         && toY == fromY && toX > fromX+1) {
10211         for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
10212         board[fromY][toX-1] = board[fromY][rookX];
10213         board[fromY][rookX] = EmptySquare;
10214         board[fromY][fromX] = EmptySquare;
10215         board[toY][toX] = king;
10216     } else if (board[fromY][fromX] == king
10217         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10218                && toY == fromY && toX < fromX-1) {
10219         for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
10220         board[fromY][toX+1] = board[fromY][rookX];
10221         board[fromY][rookX] = EmptySquare;
10222         board[fromY][fromX] = EmptySquare;
10223         board[toY][toX] = king;
10224     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
10225                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10226                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
10227                ) {
10228         /* white pawn promotion */
10229         board[toY][toX] = CharToPiece(ToUpper(promoChar));
10230         if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10231             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10232         board[fromY][fromX] = EmptySquare;
10233     } else if ((fromY >= BOARD_HEIGHT>>1)
10234                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10235                && (toX != fromX)
10236                && gameInfo.variant != VariantXiangqi
10237                && gameInfo.variant != VariantBerolina
10238                && (pawn == WhitePawn)
10239                && (board[toY][toX] == EmptySquare)) {
10240         board[fromY][fromX] = EmptySquare;
10241         board[toY][toX] = piece;
10242         if(toY == epRank - 128 + 1)
10243             captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
10244         else
10245             captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
10246     } else if ((fromY == BOARD_HEIGHT-4)
10247                && (toX == fromX)
10248                && gameInfo.variant == VariantBerolina
10249                && (board[fromY][fromX] == WhitePawn)
10250                && (board[toY][toX] == EmptySquare)) {
10251         board[fromY][fromX] = EmptySquare;
10252         board[toY][toX] = WhitePawn;
10253         if(oldEP & EP_BEROLIN_A) {
10254                 captured = board[fromY][fromX-1];
10255                 board[fromY][fromX-1] = EmptySquare;
10256         }else{  captured = board[fromY][fromX+1];
10257                 board[fromY][fromX+1] = EmptySquare;
10258         }
10259     } else if (board[fromY][fromX] == king
10260         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10261                && toY == fromY && toX > fromX+1) {
10262         for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
10263         board[fromY][toX-1] = board[fromY][rookX];
10264         board[fromY][rookX] = EmptySquare;
10265         board[fromY][fromX] = EmptySquare;
10266         board[toY][toX] = king;
10267     } else if (board[fromY][fromX] == king
10268         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
10269                && toY == fromY && toX < fromX-1) {
10270         for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
10271         board[fromY][toX+1] = board[fromY][rookX];
10272         board[fromY][rookX] = EmptySquare;
10273         board[fromY][fromX] = EmptySquare;
10274         board[toY][toX] = king;
10275     } else if (fromY == 7 && fromX == 3
10276                && board[fromY][fromX] == BlackKing
10277                && toY == 7 && toX == 5) {
10278         board[fromY][fromX] = EmptySquare;
10279         board[toY][toX] = BlackKing;
10280         board[fromY][7] = EmptySquare;
10281         board[toY][4] = BlackRook;
10282     } else if (fromY == 7 && fromX == 3
10283                && board[fromY][fromX] == BlackKing
10284                && toY == 7 && toX == 1) {
10285         board[fromY][fromX] = EmptySquare;
10286         board[toY][toX] = BlackKing;
10287         board[fromY][0] = EmptySquare;
10288         board[toY][2] = BlackRook;
10289     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
10290                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
10291                && toY < promoRank && promoChar
10292                ) {
10293         /* black pawn promotion */
10294         board[toY][toX] = CharToPiece(ToLower(promoChar));
10295         if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
10296             board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
10297         board[fromY][fromX] = EmptySquare;
10298     } else if ((fromY < BOARD_HEIGHT>>1)
10299                && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
10300                && (toX != fromX)
10301                && gameInfo.variant != VariantXiangqi
10302                && gameInfo.variant != VariantBerolina
10303                && (pawn == BlackPawn)
10304                && (board[toY][toX] == EmptySquare)) {
10305         board[fromY][fromX] = EmptySquare;
10306         board[toY][toX] = piece;
10307         if(toY == epRank - 128 - 1)
10308             captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
10309         else
10310             captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
10311     } else if ((fromY == 3)
10312                && (toX == fromX)
10313                && gameInfo.variant == VariantBerolina
10314                && (board[fromY][fromX] == BlackPawn)
10315                && (board[toY][toX] == EmptySquare)) {
10316         board[fromY][fromX] = EmptySquare;
10317         board[toY][toX] = BlackPawn;
10318         if(oldEP & EP_BEROLIN_A) {
10319                 captured = board[fromY][fromX-1];
10320                 board[fromY][fromX-1] = EmptySquare;
10321         }else{  captured = board[fromY][fromX+1];
10322                 board[fromY][fromX+1] = EmptySquare;
10323         }
10324     } else {
10325         ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to)
10326         board[fromY][fromX] = EmptySquare;
10327         board[toY][toX] = piece;
10328     }
10329   }
10330
10331     if (gameInfo.holdingsWidth != 0) {
10332
10333       /* !!A lot more code needs to be written to support holdings  */
10334       /* [HGM] OK, so I have written it. Holdings are stored in the */
10335       /* penultimate board files, so they are automaticlly stored   */
10336       /* in the game history.                                       */
10337       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
10338                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
10339         /* Delete from holdings, by decreasing count */
10340         /* and erasing image if necessary            */
10341         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
10342         if(p < (int) BlackPawn) { /* white drop */
10343              p -= (int)WhitePawn;
10344                  p = PieceToNumber((ChessSquare)p);
10345              if(p >= gameInfo.holdingsSize) p = 0;
10346              if(--board[p][BOARD_WIDTH-2] <= 0)
10347                   board[p][BOARD_WIDTH-1] = EmptySquare;
10348              if((int)board[p][BOARD_WIDTH-2] < 0)
10349                         board[p][BOARD_WIDTH-2] = 0;
10350         } else {                  /* black drop */
10351              p -= (int)BlackPawn;
10352                  p = PieceToNumber((ChessSquare)p);
10353              if(p >= gameInfo.holdingsSize) p = 0;
10354              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
10355                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
10356              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
10357                         board[BOARD_HEIGHT-1-p][1] = 0;
10358         }
10359       }
10360       if (captured != EmptySquare && gameInfo.holdingsSize > 0
10361           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
10362         /* [HGM] holdings: Add to holdings, if holdings exist */
10363         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
10364                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
10365                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
10366         }
10367         p = (int) captured;
10368         if (p >= (int) BlackPawn) {
10369           p -= (int)BlackPawn;
10370           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10371                   /* Restore shogi-promoted piece to its original  first */
10372                   captured = (ChessSquare) (DEMOTED(captured));
10373                   p = DEMOTED(p);
10374           }
10375           p = PieceToNumber((ChessSquare)p);
10376           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
10377           board[p][BOARD_WIDTH-2]++;
10378           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
10379         } else {
10380           p -= (int)WhitePawn;
10381           if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
10382                   captured = (ChessSquare) (DEMOTED(captured));
10383                   p = DEMOTED(p);
10384           }
10385           p = PieceToNumber((ChessSquare)p);
10386           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
10387           board[BOARD_HEIGHT-1-p][1]++;
10388           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
10389         }
10390       }
10391     } else if (gameInfo.variant == VariantAtomic) {
10392       if (captured != EmptySquare) {
10393         int y, x;
10394         for (y = toY-1; y <= toY+1; y++) {
10395           for (x = toX-1; x <= toX+1; x++) {
10396             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
10397                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
10398               board[y][x] = EmptySquare;
10399             }
10400           }
10401         }
10402         board[toY][toX] = EmptySquare;
10403       }
10404     }
10405
10406     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
10407         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
10408     } else
10409     if(promoChar == '+') {
10410         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
10411         board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
10412         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
10413           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
10414     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
10415         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
10416         if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
10417            && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
10418         board[toY][toX] = newPiece;
10419     }
10420     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10421                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
10422         // [HGM] superchess: take promotion piece out of holdings
10423         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
10424         if((int)piece < (int)BlackPawn) { // determine stm from piece color
10425             if(!--board[k][BOARD_WIDTH-2])
10426                 board[k][BOARD_WIDTH-1] = EmptySquare;
10427         } else {
10428             if(!--board[BOARD_HEIGHT-1-k][1])
10429                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
10430         }
10431     }
10432 }
10433
10434 /* Updates forwardMostMove */
10435 void
10436 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
10437 {
10438     int x = toX, y = toY;
10439     char *s = parseList[forwardMostMove];
10440     ChessSquare p = boards[forwardMostMove][toY][toX];
10441 //    forwardMostMove++; // [HGM] bare: moved downstream
10442
10443     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
10444     (void) CoordsToAlgebraic(boards[forwardMostMove],
10445                              PosFlags(forwardMostMove),
10446                              fromY, fromX, y, x, promoChar,
10447                              s);
10448     if(killX >= 0 && killY >= 0)
10449         sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
10450
10451     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
10452         int timeLeft; static int lastLoadFlag=0; int king, piece;
10453         piece = boards[forwardMostMove][fromY][fromX];
10454         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
10455         if(gameInfo.variant == VariantKnightmate)
10456             king += (int) WhiteUnicorn - (int) WhiteKing;
10457         if(forwardMostMove == 0) {
10458             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
10459                 fprintf(serverMoves, "%s;", UserName());
10460             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
10461                 fprintf(serverMoves, "%s;", second.tidy);
10462             fprintf(serverMoves, "%s;", first.tidy);
10463             if(gameMode == MachinePlaysWhite)
10464                 fprintf(serverMoves, "%s;", UserName());
10465             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
10466                 fprintf(serverMoves, "%s;", second.tidy);
10467         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
10468         lastLoadFlag = loadFlag;
10469         // print base move
10470         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
10471         // print castling suffix
10472         if( toY == fromY && piece == king ) {
10473             if(toX-fromX > 1)
10474                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
10475             if(fromX-toX >1)
10476                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
10477         }
10478         // e.p. suffix
10479         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
10480              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
10481              boards[forwardMostMove][toY][toX] == EmptySquare
10482              && fromX != toX && fromY != toY)
10483                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
10484         // promotion suffix
10485         if(promoChar != NULLCHAR) {
10486             if(fromY == 0 || fromY == BOARD_HEIGHT-1)
10487                  fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
10488                                                  ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
10489             else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
10490         }
10491         if(!loadFlag) {
10492                 char buf[MOVE_LEN*2], *p; int len;
10493             fprintf(serverMoves, "/%d/%d",
10494                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
10495             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
10496             else                      timeLeft = blackTimeRemaining/1000;
10497             fprintf(serverMoves, "/%d", timeLeft);
10498                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
10499                 if(p = strchr(buf, '/')) *p = NULLCHAR; else
10500                 if(p = strchr(buf, '=')) *p = NULLCHAR;
10501                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
10502             fprintf(serverMoves, "/%s", buf);
10503         }
10504         fflush(serverMoves);
10505     }
10506
10507     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
10508         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
10509       return;
10510     }
10511     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
10512     if (commentList[forwardMostMove+1] != NULL) {
10513         free(commentList[forwardMostMove+1]);
10514         commentList[forwardMostMove+1] = NULL;
10515     }
10516     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
10517     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
10518     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
10519     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
10520     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10521     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10522     adjustedClock = FALSE;
10523     gameInfo.result = GameUnfinished;
10524     if (gameInfo.resultDetails != NULL) {
10525         free(gameInfo.resultDetails);
10526         gameInfo.resultDetails = NULL;
10527     }
10528     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
10529                               moveList[forwardMostMove - 1]);
10530     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
10531       case MT_NONE:
10532       case MT_STALEMATE:
10533       default:
10534         break;
10535       case MT_CHECK:
10536         if(!IS_SHOGI(gameInfo.variant))
10537             strcat(parseList[forwardMostMove - 1], "+");
10538         break;
10539       case MT_CHECKMATE:
10540       case MT_STAINMATE:
10541         strcat(parseList[forwardMostMove - 1], "#");
10542         break;
10543     }
10544 }
10545
10546 /* Updates currentMove if not pausing */
10547 void
10548 ShowMove (int fromX, int fromY, int toX, int toY)
10549 {
10550     int instant = (gameMode == PlayFromGameFile) ?
10551         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
10552     if(appData.noGUI) return;
10553     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10554         if (!instant) {
10555             if (forwardMostMove == currentMove + 1) {
10556                 AnimateMove(boards[forwardMostMove - 1],
10557                             fromX, fromY, toX, toY);
10558             }
10559         }
10560         currentMove = forwardMostMove;
10561     }
10562
10563     killX = killY = -1; // [HGM] lion: used up
10564
10565     if (instant) return;
10566
10567     DisplayMove(currentMove - 1);
10568     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
10569             if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
10570                 SetHighlights(fromX, fromY, toX, toY);
10571             }
10572     }
10573     DrawPosition(FALSE, boards[currentMove]);
10574     DisplayBothClocks();
10575     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
10576 }
10577
10578 void
10579 SendEgtPath (ChessProgramState *cps)
10580 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
10581         char buf[MSG_SIZ], name[MSG_SIZ], *p;
10582
10583         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
10584
10585         while(*p) {
10586             char c, *q = name+1, *r, *s;
10587
10588             name[0] = ','; // extract next format name from feature and copy with prefixed ','
10589             while(*p && *p != ',') *q++ = *p++;
10590             *q++ = ':'; *q = 0;
10591             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
10592                 strcmp(name, ",nalimov:") == 0 ) {
10593                 // take nalimov path from the menu-changeable option first, if it is defined
10594               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
10595                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
10596             } else
10597             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
10598                 (s = StrStr(appData.egtFormats, name)) != NULL) {
10599                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
10600                 s = r = StrStr(s, ":") + 1; // beginning of path info
10601                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
10602                 c = *r; *r = 0;             // temporarily null-terminate path info
10603                     *--q = 0;               // strip of trailig ':' from name
10604                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
10605                 *r = c;
10606                 SendToProgram(buf,cps);     // send egtbpath command for this format
10607             }
10608             if(*p == ',') p++; // read away comma to position for next format name
10609         }
10610 }
10611
10612 static int
10613 NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
10614 {
10615       int width = 8, height = 8, holdings = 0;             // most common sizes
10616       if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
10617       // correct the deviations default for each variant
10618       if( v == VariantXiangqi ) width = 9,  height = 10;
10619       if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
10620       if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
10621       if( v == VariantCapablanca || v == VariantCapaRandom ||
10622           v == VariantGothic || v == VariantFalcon || v == VariantJanus )
10623                                 width = 10;
10624       if( v == VariantCourier ) width = 12;
10625       if( v == VariantSuper )                            holdings = 8;
10626       if( v == VariantGreat )   width = 10,              holdings = 8;
10627       if( v == VariantSChess )                           holdings = 7;
10628       if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
10629       if( v == VariantChuChess) width = 10, height = 10;
10630       if( v == VariantChu )     width = 12, height = 12;
10631       return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
10632              boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
10633              holdingsSize >= 0 && holdingsSize != holdings;
10634 }
10635
10636 char variantError[MSG_SIZ];
10637
10638 char *
10639 SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
10640 {     // returns error message (recognizable by upper-case) if engine does not support the variant
10641       char *p, *variant = VariantName(v);
10642       static char b[MSG_SIZ];
10643       if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
10644            snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
10645                                                holdingsSize, variant); // cook up sized variant name
10646            /* [HGM] varsize: try first if this deviant size variant is specifically known */
10647            if(StrStr(list, b) == NULL) {
10648                // specific sized variant not known, check if general sizing allowed
10649                if(proto != 1 && StrStr(list, "boardsize") == NULL) {
10650                    snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
10651                             boardWidth, boardHeight, holdingsSize, engine);
10652                    return NULL;
10653                }
10654                /* [HGM] here we really should compare with the maximum supported board size */
10655            }
10656       } else snprintf(b, MSG_SIZ,"%s", variant);
10657       if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
10658       p = StrStr(list, b);
10659       while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
10660       if(p == NULL) {
10661           // occurs not at all in list, or only as sub-string
10662           snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
10663           if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
10664               int l = strlen(variantError);
10665               char *q;
10666               while(p != list && p[-1] != ',') p--;
10667               q = strchr(p, ',');
10668               if(q) *q = NULLCHAR;
10669               snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
10670               if(q) *q= ',';
10671           }
10672           return NULL;
10673       }
10674       return b;
10675 }
10676
10677 void
10678 InitChessProgram (ChessProgramState *cps, int setup)
10679 /* setup needed to setup FRC opening position */
10680 {
10681     char buf[MSG_SIZ], *b;
10682     if (appData.noChessProgram) return;
10683     hintRequested = FALSE;
10684     bookRequested = FALSE;
10685
10686     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
10687     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
10688     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
10689     if(cps->memSize) { /* [HGM] memory */
10690       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
10691         SendToProgram(buf, cps);
10692     }
10693     SendEgtPath(cps); /* [HGM] EGT */
10694     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
10695       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
10696         SendToProgram(buf, cps);
10697     }
10698
10699     setboardSpoiledMachineBlack = FALSE;
10700     SendToProgram(cps->initString, cps);
10701     if (gameInfo.variant != VariantNormal &&
10702         gameInfo.variant != VariantLoadable
10703         /* [HGM] also send variant if board size non-standard */
10704         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
10705
10706       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
10707                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
10708       if (b == NULL) {
10709         VariantClass v;
10710         char c, *q = cps->variants, *p = strchr(q, ',');
10711         if(p) *p = NULLCHAR;
10712         v = StringToVariant(q);
10713         DisplayError(variantError, 0);
10714         if(v != VariantUnknown && cps == &first) {
10715             int w, h, s;
10716             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
10717                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
10718             ASSIGN(appData.variant, q);
10719             Reset(TRUE, FALSE);
10720         }
10721         if(p) *p = ',';
10722         return;
10723       }
10724
10725       snprintf(buf, MSG_SIZ, "variant %s\n", b);
10726       SendToProgram(buf, cps);
10727     }
10728     currentlyInitializedVariant = gameInfo.variant;
10729
10730     /* [HGM] send opening position in FRC to first engine */
10731     if(setup) {
10732           SendToProgram("force\n", cps);
10733           SendBoard(cps, 0);
10734           /* engine is now in force mode! Set flag to wake it up after first move. */
10735           setboardSpoiledMachineBlack = 1;
10736     }
10737
10738     if (cps->sendICS) {
10739       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
10740       SendToProgram(buf, cps);
10741     }
10742     cps->maybeThinking = FALSE;
10743     cps->offeredDraw = 0;
10744     if (!appData.icsActive) {
10745         SendTimeControl(cps, movesPerSession, timeControl,
10746                         timeIncrement, appData.searchDepth,
10747                         searchTime);
10748     }
10749     if (appData.showThinking
10750         // [HGM] thinking: four options require thinking output to be sent
10751         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
10752                                 ) {
10753         SendToProgram("post\n", cps);
10754     }
10755     SendToProgram("hard\n", cps);
10756     if (!appData.ponderNextMove) {
10757         /* Warning: "easy" is a toggle in GNU Chess, so don't send
10758            it without being sure what state we are in first.  "hard"
10759            is not a toggle, so that one is OK.
10760          */
10761         SendToProgram("easy\n", cps);
10762     }
10763     if (cps->usePing) {
10764       snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
10765       SendToProgram(buf, cps);
10766     }
10767     cps->initDone = TRUE;
10768     ClearEngineOutputPane(cps == &second);
10769 }
10770
10771
10772 void
10773 ResendOptions (ChessProgramState *cps)
10774 { // send the stored value of the options
10775   int i;
10776   char buf[MSG_SIZ];
10777   Option *opt = cps->option;
10778   for(i=0; i<cps->nrOptions; i++, opt++) {
10779       switch(opt->type) {
10780         case Spin:
10781         case Slider:
10782         case CheckBox:
10783             snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
10784           break;
10785         case ComboBox:
10786           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
10787           break;
10788         default:
10789             snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
10790           break;
10791         case Button:
10792         case SaveButton:
10793           continue;
10794       }
10795       SendToProgram(buf, cps);
10796   }
10797 }
10798
10799 void
10800 StartChessProgram (ChessProgramState *cps)
10801 {
10802     char buf[MSG_SIZ];
10803     int err;
10804
10805     if (appData.noChessProgram) return;
10806     cps->initDone = FALSE;
10807
10808     if (strcmp(cps->host, "localhost") == 0) {
10809         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
10810     } else if (*appData.remoteShell == NULLCHAR) {
10811         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
10812     } else {
10813         if (*appData.remoteUser == NULLCHAR) {
10814           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
10815                     cps->program);
10816         } else {
10817           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
10818                     cps->host, appData.remoteUser, cps->program);
10819         }
10820         err = StartChildProcess(buf, "", &cps->pr);
10821     }
10822
10823     if (err != 0) {
10824       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
10825         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
10826         if(cps != &first) return;
10827         appData.noChessProgram = TRUE;
10828         ThawUI();
10829         SetNCPMode();
10830 //      DisplayFatalError(buf, err, 1);
10831 //      cps->pr = NoProc;
10832 //      cps->isr = NULL;
10833         return;
10834     }
10835
10836     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
10837     if (cps->protocolVersion > 1) {
10838       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
10839       if(!cps->reload) { // do not clear options when reloading because of -xreuse
10840         cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
10841         cps->comboCnt = 0;  //                and values of combo boxes
10842       }
10843       SendToProgram(buf, cps);
10844       if(cps->reload) ResendOptions(cps);
10845     } else {
10846       SendToProgram("xboard\n", cps);
10847     }
10848 }
10849
10850 void
10851 TwoMachinesEventIfReady P((void))
10852 {
10853   static int curMess = 0;
10854   if (first.lastPing != first.lastPong) {
10855     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
10856     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10857     return;
10858   }
10859   if (second.lastPing != second.lastPong) {
10860     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
10861     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
10862     return;
10863   }
10864   DisplayMessage("", ""); curMess = 0;
10865   TwoMachinesEvent();
10866 }
10867
10868 char *
10869 MakeName (char *template)
10870 {
10871     time_t clock;
10872     struct tm *tm;
10873     static char buf[MSG_SIZ];
10874     char *p = buf;
10875     int i;
10876
10877     clock = time((time_t *)NULL);
10878     tm = localtime(&clock);
10879
10880     while(*p++ = *template++) if(p[-1] == '%') {
10881         switch(*template++) {
10882           case 0:   *p = 0; return buf;
10883           case 'Y': i = tm->tm_year+1900; break;
10884           case 'y': i = tm->tm_year-100; break;
10885           case 'M': i = tm->tm_mon+1; break;
10886           case 'd': i = tm->tm_mday; break;
10887           case 'h': i = tm->tm_hour; break;
10888           case 'm': i = tm->tm_min; break;
10889           case 's': i = tm->tm_sec; break;
10890           default:  i = 0;
10891         }
10892         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
10893     }
10894     return buf;
10895 }
10896
10897 int
10898 CountPlayers (char *p)
10899 {
10900     int n = 0;
10901     while(p = strchr(p, '\n')) p++, n++; // count participants
10902     return n;
10903 }
10904
10905 FILE *
10906 WriteTourneyFile (char *results, FILE *f)
10907 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
10908     if(f == NULL) f = fopen(appData.tourneyFile, "w");
10909     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
10910         // create a file with tournament description
10911         fprintf(f, "-participants {%s}\n", appData.participants);
10912         fprintf(f, "-seedBase %d\n", appData.seedBase);
10913         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
10914         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
10915         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
10916         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
10917         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
10918         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
10919         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
10920         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
10921         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
10922         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
10923         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
10924         fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false");
10925         fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook);
10926         fprintf(f, "-bookDepth %d\n", appData.bookDepth);
10927         fprintf(f, "-bookVariation %d\n", appData.bookStrength);
10928         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
10929         fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize);
10930         fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB);
10931         fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false");
10932         fprintf(f, "-smpCores %d\n", appData.smpCores);
10933         if(searchTime > 0)
10934                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
10935         else {
10936                 fprintf(f, "-mps %d\n", appData.movesPerSession);
10937                 fprintf(f, "-tc %s\n", appData.timeControl);
10938                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
10939         }
10940         fprintf(f, "-results \"%s\"\n", results);
10941     }
10942     return f;
10943 }
10944
10945 char *command[MAXENGINES], *mnemonic[MAXENGINES];
10946
10947 void
10948 Substitute (char *participants, int expunge)
10949 {
10950     int i, changed, changes=0, nPlayers=0;
10951     char *p, *q, *r, buf[MSG_SIZ];
10952     if(participants == NULL) return;
10953     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
10954     r = p = participants; q = appData.participants;
10955     while(*p && *p == *q) {
10956         if(*p == '\n') r = p+1, nPlayers++;
10957         p++; q++;
10958     }
10959     if(*p) { // difference
10960         while(*p && *p++ != '\n');
10961         while(*q && *q++ != '\n');
10962       changed = nPlayers;
10963         changes = 1 + (strcmp(p, q) != 0);
10964     }
10965     if(changes == 1) { // a single engine mnemonic was changed
10966         q = r; while(*q) nPlayers += (*q++ == '\n');
10967         p = buf; while(*r && (*p = *r++) != '\n') p++;
10968         *p = NULLCHAR;
10969         NamesToList(firstChessProgramNames, command, mnemonic, "all");
10970         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
10971         if(mnemonic[i]) { // The substitute is valid
10972             FILE *f;
10973             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
10974                 flock(fileno(f), LOCK_EX);
10975                 ParseArgsFromFile(f);
10976                 fseek(f, 0, SEEK_SET);
10977                 FREE(appData.participants); appData.participants = participants;
10978                 if(expunge) { // erase results of replaced engine
10979                     int len = strlen(appData.results), w, b, dummy;
10980                     for(i=0; i<len; i++) {
10981                         Pairing(i, nPlayers, &w, &b, &dummy);
10982                         if((w == changed || b == changed) && appData.results[i] == '*') {
10983                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
10984                             fclose(f);
10985                             return;
10986                         }
10987                     }
10988                     for(i=0; i<len; i++) {
10989                         Pairing(i, nPlayers, &w, &b, &dummy);
10990                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
10991                     }
10992                 }
10993                 WriteTourneyFile(appData.results, f);
10994                 fclose(f); // release lock
10995                 return;
10996             }
10997         } else DisplayError(_("No engine with the name you gave is installed"), 0);
10998     }
10999     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
11000     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
11001     free(participants);
11002     return;
11003 }
11004
11005 int
11006 CheckPlayers (char *participants)
11007 {
11008         int i;
11009         char buf[MSG_SIZ], *p;
11010         NamesToList(firstChessProgramNames, command, mnemonic, "all");
11011         while(p = strchr(participants, '\n')) {
11012             *p = NULLCHAR;
11013             for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
11014             if(!mnemonic[i]) {
11015                 snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
11016                 *p = '\n';
11017                 DisplayError(buf, 0);
11018                 return 1;
11019             }
11020             *p = '\n';
11021             participants = p + 1;
11022         }
11023         return 0;
11024 }
11025
11026 int
11027 CreateTourney (char *name)
11028 {
11029         FILE *f;
11030         if(matchMode && strcmp(name, appData.tourneyFile)) {
11031              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
11032         }
11033         if(name[0] == NULLCHAR) {
11034             if(appData.participants[0])
11035                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
11036             return 0;
11037         }
11038         f = fopen(name, "r");
11039         if(f) { // file exists
11040             ASSIGN(appData.tourneyFile, name);
11041             ParseArgsFromFile(f); // parse it
11042         } else {
11043             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
11044             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
11045                 DisplayError(_("Not enough participants"), 0);
11046                 return 0;
11047             }
11048             if(CheckPlayers(appData.participants)) return 0;
11049             ASSIGN(appData.tourneyFile, name);
11050             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
11051             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
11052         }
11053         fclose(f);
11054         appData.noChessProgram = FALSE;
11055         appData.clockMode = TRUE;
11056         SetGNUMode();
11057         return 1;
11058 }
11059
11060 int
11061 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
11062 {
11063     char buf[MSG_SIZ], *p, *q;
11064     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
11065     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
11066     skip = !all && group[0]; // if group requested, we start in skip mode
11067     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
11068         p = names; q = buf; header = 0;
11069         while(*p && *p != '\n') *q++ = *p++;
11070         *q = 0;
11071         if(*p == '\n') p++;
11072         if(buf[0] == '#') {
11073             if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label
11074             depth++; // we must be entering a new group
11075             if(all) continue; // suppress printing group headers when complete list requested
11076             header = 1;
11077             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
11078         }
11079         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
11080         if(engineList[i]) free(engineList[i]);
11081         engineList[i] = strdup(buf);
11082         if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
11083         if(engineMnemonic[i]) free(engineMnemonic[i]);
11084         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
11085             strcat(buf, " (");
11086             sscanf(q + 8, "%s", buf + strlen(buf));
11087             strcat(buf, ")");
11088         }
11089         engineMnemonic[i] = strdup(buf);
11090         i++;
11091     }
11092     engineList[i] = engineMnemonic[i] = NULL;
11093     return i;
11094 }
11095
11096 // following implemented as macro to avoid type limitations
11097 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
11098
11099 void
11100 SwapEngines (int n)
11101 {   // swap settings for first engine and other engine (so far only some selected options)
11102     int h;
11103     char *p;
11104     if(n == 0) return;
11105     SWAP(directory, p)
11106     SWAP(chessProgram, p)
11107     SWAP(isUCI, h)
11108     SWAP(hasOwnBookUCI, h)
11109     SWAP(protocolVersion, h)
11110     SWAP(reuse, h)
11111     SWAP(scoreIsAbsolute, h)
11112     SWAP(timeOdds, h)
11113     SWAP(logo, p)
11114     SWAP(pgnName, p)
11115     SWAP(pvSAN, h)
11116     SWAP(engOptions, p)
11117     SWAP(engInitString, p)
11118     SWAP(computerString, p)
11119     SWAP(features, p)
11120     SWAP(fenOverride, p)
11121     SWAP(NPS, h)
11122     SWAP(accumulateTC, h)
11123     SWAP(drawDepth, h)
11124     SWAP(host, p)
11125     SWAP(pseudo, h)
11126 }
11127
11128 int
11129 GetEngineLine (char *s, int n)
11130 {
11131     int i;
11132     char buf[MSG_SIZ];
11133     extern char *icsNames;
11134     if(!s || !*s) return 0;
11135     NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
11136     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
11137     if(!mnemonic[i]) return 0;
11138     if(n == 11) return 1; // just testing if there was a match
11139     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
11140     if(n == 1) SwapEngines(n);
11141     ParseArgsFromString(buf);
11142     if(n == 1) SwapEngines(n);
11143     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
11144         SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
11145         ParseArgsFromString(buf);
11146     }
11147     return 1;
11148 }
11149
11150 int
11151 SetPlayer (int player, char *p)
11152 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
11153     int i;
11154     char buf[MSG_SIZ], *engineName;
11155     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
11156     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
11157     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
11158     if(mnemonic[i]) {
11159         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
11160         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
11161         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
11162         ParseArgsFromString(buf);
11163     } else { // no engine with this nickname is installed!
11164         snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName);
11165         ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error
11166         matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11167         ModeHighlight();
11168         DisplayError(buf, 0);
11169         return 0;
11170     }
11171     free(engineName);
11172     return i;
11173 }
11174
11175 char *recentEngines;
11176
11177 void
11178 RecentEngineEvent (int nr)
11179 {
11180     int n;
11181 //    SwapEngines(1); // bump first to second
11182 //    ReplaceEngine(&second, 1); // and load it there
11183     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11184     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
11185     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
11186         ReplaceEngine(&first, 0);
11187         FloatToFront(&appData.recentEngineList, command[n]);
11188     }
11189 }
11190
11191 int
11192 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
11193 {   // determine players from game number
11194     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
11195
11196     if(appData.tourneyType == 0) {
11197         roundsPerCycle = (nPlayers - 1) | 1;
11198         pairingsPerRound = nPlayers / 2;
11199     } else if(appData.tourneyType > 0) {
11200         roundsPerCycle = nPlayers - appData.tourneyType;
11201         pairingsPerRound = appData.tourneyType;
11202     }
11203     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
11204     gamesPerCycle = gamesPerRound * roundsPerCycle;
11205     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
11206     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
11207     curRound = nr / gamesPerRound; nr %= gamesPerRound;
11208     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
11209     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
11210     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
11211
11212     if(appData.cycleSync) *syncInterval = gamesPerCycle;
11213     if(appData.roundSync) *syncInterval = gamesPerRound;
11214
11215     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
11216
11217     if(appData.tourneyType == 0) {
11218         if(curPairing == (nPlayers-1)/2 ) {
11219             *whitePlayer = curRound;
11220             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
11221         } else {
11222             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
11223             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
11224             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
11225             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
11226         }
11227     } else if(appData.tourneyType > 1) {
11228         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
11229         *whitePlayer = curRound + appData.tourneyType;
11230     } else if(appData.tourneyType > 0) {
11231         *whitePlayer = curPairing;
11232         *blackPlayer = curRound + appData.tourneyType;
11233     }
11234
11235     // take care of white/black alternation per round.
11236     // For cycles and games this is already taken care of by default, derived from matchGame!
11237     return curRound & 1;
11238 }
11239
11240 int
11241 NextTourneyGame (int nr, int *swapColors)
11242 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
11243     char *p, *q;
11244     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1;
11245     FILE *tf;
11246     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
11247     tf = fopen(appData.tourneyFile, "r");
11248     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
11249     ParseArgsFromFile(tf); fclose(tf);
11250     InitTimeControls(); // TC might be altered from tourney file
11251
11252     nPlayers = CountPlayers(appData.participants); // count participants
11253     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
11254     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
11255
11256     if(syncInterval) {
11257         p = q = appData.results;
11258         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
11259         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
11260             DisplayMessage(_("Waiting for other game(s)"),"");
11261             waitingForGame = TRUE;
11262             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
11263             return 0;
11264         }
11265         waitingForGame = FALSE;
11266     }
11267
11268     if(appData.tourneyType < 0) {
11269         if(nr>=0 && !pairingReceived) {
11270             char buf[1<<16];
11271             if(pairing.pr == NoProc) {
11272                 if(!appData.pairingEngine[0]) {
11273                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
11274                     return 0;
11275                 }
11276                 StartChessProgram(&pairing); // starts the pairing engine
11277             }
11278             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
11279             SendToProgram(buf, &pairing);
11280             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
11281             SendToProgram(buf, &pairing);
11282             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
11283         }
11284         pairingReceived = 0;                              // ... so we continue here
11285         *swapColors = 0;
11286         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
11287         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
11288         matchGame = 1; roundNr = nr / syncInterval + 1;
11289     }
11290
11291     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
11292
11293     // redefine engines, engine dir, etc.
11294     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
11295     if(first.pr == NoProc) {
11296       if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line
11297       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
11298     }
11299     if(second.pr == NoProc) {
11300       SwapEngines(1);
11301       if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line
11302       SwapEngines(1);         // and make that valid for second engine by swapping
11303       InitEngine(&second, 1);
11304     }
11305     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
11306     UpdateLogos(FALSE);     // leave display to ModeHiglight()
11307     return OK;
11308 }
11309
11310 void
11311 NextMatchGame ()
11312 {   // performs game initialization that does not invoke engines, and then tries to start the game
11313     int res, firstWhite, swapColors = 0;
11314     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
11315     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
11316         char buf[MSG_SIZ];
11317         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
11318         if(strcmp(buf, currentDebugFile)) { // name has changed
11319             FILE *f = fopen(buf, "w");
11320             if(f) { // if opening the new file failed, just keep using the old one
11321                 ASSIGN(currentDebugFile, buf);
11322                 fclose(debugFP);
11323                 debugFP = f;
11324             }
11325             if(appData.serverFileName) {
11326                 if(serverFP) fclose(serverFP);
11327                 serverFP = fopen(appData.serverFileName, "w");
11328                 if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy);
11329                 if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy);
11330             }
11331         }
11332     }
11333     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
11334     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
11335     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
11336     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
11337     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
11338     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
11339     Reset(FALSE, first.pr != NoProc);
11340     res = LoadGameOrPosition(matchGame); // setup game
11341     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
11342     if(!res) return; // abort when bad game/pos file
11343     TwoMachinesEvent();
11344 }
11345
11346 void
11347 UserAdjudicationEvent (int result)
11348 {
11349     ChessMove gameResult = GameIsDrawn;
11350
11351     if( result > 0 ) {
11352         gameResult = WhiteWins;
11353     }
11354     else if( result < 0 ) {
11355         gameResult = BlackWins;
11356     }
11357
11358     if( gameMode == TwoMachinesPlay ) {
11359         GameEnds( gameResult, "User adjudication", GE_XBOARD );
11360     }
11361 }
11362
11363
11364 // [HGM] save: calculate checksum of game to make games easily identifiable
11365 int
11366 StringCheckSum (char *s)
11367 {
11368         int i = 0;
11369         if(s==NULL) return 0;
11370         while(*s) i = i*259 + *s++;
11371         return i;
11372 }
11373
11374 int
11375 GameCheckSum ()
11376 {
11377         int i, sum=0;
11378         for(i=backwardMostMove; i<forwardMostMove; i++) {
11379                 sum += pvInfoList[i].depth;
11380                 sum += StringCheckSum(parseList[i]);
11381                 sum += StringCheckSum(commentList[i]);
11382                 sum *= 261;
11383         }
11384         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
11385         return sum + StringCheckSum(commentList[i]);
11386 } // end of save patch
11387
11388 void
11389 GameEnds (ChessMove result, char *resultDetails, int whosays)
11390 {
11391     GameMode nextGameMode;
11392     int isIcsGame;
11393     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
11394
11395     if(endingGame) return; /* [HGM] crash: forbid recursion */
11396     endingGame = 1;
11397     if(twoBoards) { // [HGM] dual: switch back to one board
11398         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
11399         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
11400     }
11401     if (appData.debugMode) {
11402       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
11403               result, resultDetails ? resultDetails : "(null)", whosays);
11404     }
11405
11406     fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
11407
11408     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
11409
11410     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
11411         /* If we are playing on ICS, the server decides when the
11412            game is over, but the engine can offer to draw, claim
11413            a draw, or resign.
11414          */
11415 #if ZIPPY
11416         if (appData.zippyPlay && first.initDone) {
11417             if (result == GameIsDrawn) {
11418                 /* In case draw still needs to be claimed */
11419                 SendToICS(ics_prefix);
11420                 SendToICS("draw\n");
11421             } else if (StrCaseStr(resultDetails, "resign")) {
11422                 SendToICS(ics_prefix);
11423                 SendToICS("resign\n");
11424             }
11425         }
11426 #endif
11427         endingGame = 0; /* [HGM] crash */
11428         return;
11429     }
11430
11431     /* If we're loading the game from a file, stop */
11432     if (whosays == GE_FILE) {
11433       (void) StopLoadGameTimer();
11434       gameFileFP = NULL;
11435     }
11436
11437     /* Cancel draw offers */
11438     first.offeredDraw = second.offeredDraw = 0;
11439
11440     /* If this is an ICS game, only ICS can really say it's done;
11441        if not, anyone can. */
11442     isIcsGame = (gameMode == IcsPlayingWhite ||
11443                  gameMode == IcsPlayingBlack ||
11444                  gameMode == IcsObserving    ||
11445                  gameMode == IcsExamining);
11446
11447     if (!isIcsGame || whosays == GE_ICS) {
11448         /* OK -- not an ICS game, or ICS said it was done */
11449         StopClocks();
11450         if (!isIcsGame && !appData.noChessProgram)
11451           SetUserThinkingEnables();
11452
11453         /* [HGM] if a machine claims the game end we verify this claim */
11454         if(gameMode == TwoMachinesPlay && appData.testClaims) {
11455             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
11456                 char claimer;
11457                 ChessMove trueResult = (ChessMove) -1;
11458
11459                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
11460                                             first.twoMachinesColor[0] :
11461                                             second.twoMachinesColor[0] ;
11462
11463                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
11464                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
11465                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11466                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
11467                 } else
11468                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
11469                     /* [HGM] verify: engine mate claims accepted if they were flagged */
11470                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
11471                 } else
11472                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
11473                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
11474                 }
11475
11476                 // now verify win claims, but not in drop games, as we don't understand those yet
11477                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11478                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
11479                     (result == WhiteWins && claimer == 'w' ||
11480                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
11481                       if (appData.debugMode) {
11482                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
11483                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
11484                       }
11485                       if(result != trueResult) {
11486                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
11487                               result = claimer == 'w' ? BlackWins : WhiteWins;
11488                               resultDetails = buf;
11489                       }
11490                 } else
11491                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
11492                     && (forwardMostMove <= backwardMostMove ||
11493                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
11494                         (claimer=='b')==(forwardMostMove&1))
11495                                                                                   ) {
11496                       /* [HGM] verify: draws that were not flagged are false claims */
11497                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
11498                       result = claimer == 'w' ? BlackWins : WhiteWins;
11499                       resultDetails = buf;
11500                 }
11501                 /* (Claiming a loss is accepted no questions asked!) */
11502             } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) {
11503                 forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving
11504                 result = GameUnfinished;
11505                 if(!*appData.tourneyFile) matchGame--; // replay even in plain match
11506             }
11507             /* [HGM] bare: don't allow bare King to win */
11508             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
11509                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
11510                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
11511                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
11512                && result != GameIsDrawn)
11513             {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
11514                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
11515                         int p = (signed char)boards[forwardMostMove][i][j] - color;
11516                         if(p >= 0 && p <= (int)WhiteKing) k++;
11517                         oppoKings += (p + color == WhiteKing + BlackPawn - color);
11518                 }
11519                 if (appData.debugMode) {
11520                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
11521                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
11522                 }
11523                 if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
11524                         result = GameIsDrawn;
11525                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
11526                         resultDetails = buf;
11527                 }
11528             }
11529         }
11530
11531
11532         if(serverMoves != NULL && !loadFlag) { char c = '=';
11533             if(result==WhiteWins) c = '+';
11534             if(result==BlackWins) c = '-';
11535             if(resultDetails != NULL)
11536                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
11537         }
11538         if (resultDetails != NULL) {
11539             gameInfo.result = result;
11540             gameInfo.resultDetails = StrSave(resultDetails);
11541
11542             /* display last move only if game was not loaded from file */
11543             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
11544                 DisplayMove(currentMove - 1);
11545
11546             if (forwardMostMove != 0) {
11547                 if (gameMode != PlayFromGameFile && gameMode != EditGame
11548                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
11549                                                                 ) {
11550                     if (*appData.saveGameFile != NULLCHAR) {
11551                         if(result == GameUnfinished && matchMode && *appData.tourneyFile)
11552                             AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead
11553                         else
11554                         SaveGameToFile(appData.saveGameFile, TRUE);
11555                     } else if (appData.autoSaveGames) {
11556                         if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame();
11557                     }
11558                     if (*appData.savePositionFile != NULLCHAR) {
11559                         SavePositionToFile(appData.savePositionFile);
11560                     }
11561                     AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
11562                 }
11563             }
11564
11565             /* Tell program how game ended in case it is learning */
11566             /* [HGM] Moved this to after saving the PGN, just in case */
11567             /* engine died and we got here through time loss. In that */
11568             /* case we will get a fatal error writing the pipe, which */
11569             /* would otherwise lose us the PGN.                       */
11570             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
11571             /* output during GameEnds should never be fatal anymore   */
11572             if (gameMode == MachinePlaysWhite ||
11573                 gameMode == MachinePlaysBlack ||
11574                 gameMode == TwoMachinesPlay ||
11575                 gameMode == IcsPlayingWhite ||
11576                 gameMode == IcsPlayingBlack ||
11577                 gameMode == BeginningOfGame) {
11578                 char buf[MSG_SIZ];
11579                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
11580                         resultDetails);
11581                 if (first.pr != NoProc) {
11582                     SendToProgram(buf, &first);
11583                 }
11584                 if (second.pr != NoProc &&
11585                     gameMode == TwoMachinesPlay) {
11586                     SendToProgram(buf, &second);
11587                 }
11588             }
11589         }
11590
11591         if (appData.icsActive) {
11592             if (appData.quietPlay &&
11593                 (gameMode == IcsPlayingWhite ||
11594                  gameMode == IcsPlayingBlack)) {
11595                 SendToICS(ics_prefix);
11596                 SendToICS("set shout 1\n");
11597             }
11598             nextGameMode = IcsIdle;
11599             ics_user_moved = FALSE;
11600             /* clean up premove.  It's ugly when the game has ended and the
11601              * premove highlights are still on the board.
11602              */
11603             if (gotPremove) {
11604               gotPremove = FALSE;
11605               ClearPremoveHighlights();
11606               DrawPosition(FALSE, boards[currentMove]);
11607             }
11608             if (whosays == GE_ICS) {
11609                 switch (result) {
11610                 case WhiteWins:
11611                     if (gameMode == IcsPlayingWhite)
11612                         PlayIcsWinSound();
11613                     else if(gameMode == IcsPlayingBlack)
11614                         PlayIcsLossSound();
11615                     break;
11616                 case BlackWins:
11617                     if (gameMode == IcsPlayingBlack)
11618                         PlayIcsWinSound();
11619                     else if(gameMode == IcsPlayingWhite)
11620                         PlayIcsLossSound();
11621                     break;
11622                 case GameIsDrawn:
11623                     PlayIcsDrawSound();
11624                     break;
11625                 default:
11626                     PlayIcsUnfinishedSound();
11627                 }
11628             }
11629             if(appData.quitNext) { ExitEvent(0); return; }
11630         } else if (gameMode == EditGame ||
11631                    gameMode == PlayFromGameFile ||
11632                    gameMode == AnalyzeMode ||
11633                    gameMode == AnalyzeFile) {
11634             nextGameMode = gameMode;
11635         } else {
11636             nextGameMode = EndOfGame;
11637         }
11638         pausing = FALSE;
11639         ModeHighlight();
11640     } else {
11641         nextGameMode = gameMode;
11642     }
11643
11644     if (appData.noChessProgram) {
11645         gameMode = nextGameMode;
11646         ModeHighlight();
11647         endingGame = 0; /* [HGM] crash */
11648         return;
11649     }
11650
11651     if (first.reuse) {
11652         /* Put first chess program into idle state */
11653         if (first.pr != NoProc &&
11654             (gameMode == MachinePlaysWhite ||
11655              gameMode == MachinePlaysBlack ||
11656              gameMode == TwoMachinesPlay ||
11657              gameMode == IcsPlayingWhite ||
11658              gameMode == IcsPlayingBlack ||
11659              gameMode == BeginningOfGame)) {
11660             SendToProgram("force\n", &first);
11661             if (first.usePing) {
11662               char buf[MSG_SIZ];
11663               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
11664               SendToProgram(buf, &first);
11665             }
11666         }
11667     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11668         /* Kill off first chess program */
11669         if (first.isr != NULL)
11670           RemoveInputSource(first.isr);
11671         first.isr = NULL;
11672
11673         if (first.pr != NoProc) {
11674             ExitAnalyzeMode();
11675             DoSleep( appData.delayBeforeQuit );
11676             SendToProgram("quit\n", &first);
11677             DestroyChildProcess(first.pr, 4 + first.useSigterm);
11678             first.reload = TRUE;
11679         }
11680         first.pr = NoProc;
11681     }
11682     if (second.reuse) {
11683         /* Put second chess program into idle state */
11684         if (second.pr != NoProc &&
11685             gameMode == TwoMachinesPlay) {
11686             SendToProgram("force\n", &second);
11687             if (second.usePing) {
11688               char buf[MSG_SIZ];
11689               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
11690               SendToProgram(buf, &second);
11691             }
11692         }
11693     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
11694         /* Kill off second chess program */
11695         if (second.isr != NULL)
11696           RemoveInputSource(second.isr);
11697         second.isr = NULL;
11698
11699         if (second.pr != NoProc) {
11700             DoSleep( appData.delayBeforeQuit );
11701             SendToProgram("quit\n", &second);
11702             DestroyChildProcess(second.pr, 4 + second.useSigterm);
11703             second.reload = TRUE;
11704         }
11705         second.pr = NoProc;
11706     }
11707
11708     if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) {
11709         char resChar = '=';
11710         switch (result) {
11711         case WhiteWins:
11712           resChar = '+';
11713           if (first.twoMachinesColor[0] == 'w') {
11714             first.matchWins++;
11715           } else {
11716             second.matchWins++;
11717           }
11718           break;
11719         case BlackWins:
11720           resChar = '-';
11721           if (first.twoMachinesColor[0] == 'b') {
11722             first.matchWins++;
11723           } else {
11724             second.matchWins++;
11725           }
11726           break;
11727         case GameUnfinished:
11728           resChar = ' ';
11729         default:
11730           break;
11731         }
11732
11733         if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
11734         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
11735             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
11736             ReserveGame(nextGame, resChar); // sets nextGame
11737             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
11738             else ranking = strdup("busy"); //suppress popup when aborted but not finished
11739         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
11740
11741         if (nextGame <= appData.matchGames && !abortMatch) {
11742             gameMode = nextGameMode;
11743             matchGame = nextGame; // this will be overruled in tourney mode!
11744             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
11745             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
11746             endingGame = 0; /* [HGM] crash */
11747             return;
11748         } else {
11749             gameMode = nextGameMode;
11750             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
11751                      first.tidy, second.tidy,
11752                      first.matchWins, second.matchWins,
11753                      appData.matchGames - (first.matchWins + second.matchWins));
11754             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
11755             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
11756             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
11757             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
11758                 first.twoMachinesColor = "black\n";
11759                 second.twoMachinesColor = "white\n";
11760             } else {
11761                 first.twoMachinesColor = "white\n";
11762                 second.twoMachinesColor = "black\n";
11763             }
11764         }
11765     }
11766     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
11767         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
11768       ExitAnalyzeMode();
11769     gameMode = nextGameMode;
11770     ModeHighlight();
11771     endingGame = 0;  /* [HGM] crash */
11772     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
11773         if(matchMode == TRUE) { // match through command line: exit with or without popup
11774             if(ranking) {
11775                 ToNrEvent(forwardMostMove);
11776                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
11777                 else ExitEvent(0);
11778             } else DisplayFatalError(buf, 0, 0);
11779         } else { // match through menu; just stop, with or without popup
11780             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
11781             ModeHighlight();
11782             if(ranking){
11783                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
11784             } else DisplayNote(buf);
11785       }
11786       if(ranking) free(ranking);
11787     }
11788 }
11789
11790 /* Assumes program was just initialized (initString sent).
11791    Leaves program in force mode. */
11792 void
11793 FeedMovesToProgram (ChessProgramState *cps, int upto)
11794 {
11795     int i;
11796
11797     if (appData.debugMode)
11798       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
11799               startedFromSetupPosition ? "position and " : "",
11800               backwardMostMove, upto, cps->which);
11801     if(currentlyInitializedVariant != gameInfo.variant) {
11802       char buf[MSG_SIZ];
11803         // [HGM] variantswitch: make engine aware of new variant
11804         if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
11805                              gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
11806                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
11807         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
11808         SendToProgram(buf, cps);
11809         currentlyInitializedVariant = gameInfo.variant;
11810     }
11811     SendToProgram("force\n", cps);
11812     if (startedFromSetupPosition) {
11813         SendBoard(cps, backwardMostMove);
11814     if (appData.debugMode) {
11815         fprintf(debugFP, "feedMoves\n");
11816     }
11817     }
11818     for (i = backwardMostMove; i < upto; i++) {
11819         SendMoveToProgram(i, cps);
11820     }
11821 }
11822
11823
11824 int
11825 ResurrectChessProgram ()
11826 {
11827      /* The chess program may have exited.
11828         If so, restart it and feed it all the moves made so far. */
11829     static int doInit = 0;
11830
11831     if (appData.noChessProgram) return 1;
11832
11833     if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
11834         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine
11835         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
11836         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
11837     } else {
11838         if (first.pr != NoProc) return 1;
11839         StartChessProgram(&first);
11840     }
11841     InitChessProgram(&first, FALSE);
11842     FeedMovesToProgram(&first, currentMove);
11843
11844     if (!first.sendTime) {
11845         /* can't tell gnuchess what its clock should read,
11846            so we bow to its notion. */
11847         ResetClocks();
11848         timeRemaining[0][currentMove] = whiteTimeRemaining;
11849         timeRemaining[1][currentMove] = blackTimeRemaining;
11850     }
11851
11852     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
11853                 appData.icsEngineAnalyze) && first.analysisSupport) {
11854       SendToProgram("analyze\n", &first);
11855       first.analyzing = TRUE;
11856     }
11857     return 1;
11858 }
11859
11860 /*
11861  * Button procedures
11862  */
11863 void
11864 Reset (int redraw, int init)
11865 {
11866     int i;
11867
11868     if (appData.debugMode) {
11869         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
11870                 redraw, init, gameMode);
11871     }
11872     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
11873     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
11874     CleanupTail(); // [HGM] vari: delete any stored variations
11875     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
11876     pausing = pauseExamInvalid = FALSE;
11877     startedFromSetupPosition = blackPlaysFirst = FALSE;
11878     firstMove = TRUE;
11879     whiteFlag = blackFlag = FALSE;
11880     userOfferedDraw = FALSE;
11881     hintRequested = bookRequested = FALSE;
11882     first.maybeThinking = FALSE;
11883     second.maybeThinking = FALSE;
11884     first.bookSuspend = FALSE; // [HGM] book
11885     second.bookSuspend = FALSE;
11886     thinkOutput[0] = NULLCHAR;
11887     lastHint[0] = NULLCHAR;
11888     ClearGameInfo(&gameInfo);
11889     gameInfo.variant = StringToVariant(appData.variant);
11890     if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
11891     ics_user_moved = ics_clock_paused = FALSE;
11892     ics_getting_history = H_FALSE;
11893     ics_gamenum = -1;
11894     white_holding[0] = black_holding[0] = NULLCHAR;
11895     ClearProgramStats();
11896     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
11897
11898     ResetFrontEnd();
11899     ClearHighlights();
11900     flipView = appData.flipView;
11901     ClearPremoveHighlights();
11902     gotPremove = FALSE;
11903     alarmSounded = FALSE;
11904     killX = killY = -1; // [HGM] lion
11905
11906     GameEnds(EndOfFile, NULL, GE_PLAYER);
11907     if(appData.serverMovesName != NULL) {
11908         /* [HGM] prepare to make moves file for broadcasting */
11909         clock_t t = clock();
11910         if(serverMoves != NULL) fclose(serverMoves);
11911         serverMoves = fopen(appData.serverMovesName, "r");
11912         if(serverMoves != NULL) {
11913             fclose(serverMoves);
11914             /* delay 15 sec before overwriting, so all clients can see end */
11915             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
11916         }
11917         serverMoves = fopen(appData.serverMovesName, "w");
11918     }
11919
11920     ExitAnalyzeMode();
11921     gameMode = BeginningOfGame;
11922     ModeHighlight();
11923     if(appData.icsActive) gameInfo.variant = VariantNormal;
11924     currentMove = forwardMostMove = backwardMostMove = 0;
11925     MarkTargetSquares(1);
11926     InitPosition(redraw);
11927     for (i = 0; i < MAX_MOVES; i++) {
11928         if (commentList[i] != NULL) {
11929             free(commentList[i]);
11930             commentList[i] = NULL;
11931         }
11932     }
11933     ResetClocks();
11934     timeRemaining[0][0] = whiteTimeRemaining;
11935     timeRemaining[1][0] = blackTimeRemaining;
11936
11937     if (first.pr == NoProc) {
11938         StartChessProgram(&first);
11939     }
11940     if (init) {
11941             InitChessProgram(&first, startedFromSetupPosition);
11942     }
11943     DisplayTitle("");
11944     DisplayMessage("", "");
11945     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11946     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
11947     ClearMap();        // [HGM] exclude: invalidate map
11948 }
11949
11950 void
11951 AutoPlayGameLoop ()
11952 {
11953     for (;;) {
11954         if (!AutoPlayOneMove())
11955           return;
11956         if (matchMode || appData.timeDelay == 0)
11957           continue;
11958         if (appData.timeDelay < 0)
11959           return;
11960         StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
11961         break;
11962     }
11963 }
11964
11965 void
11966 AnalyzeNextGame()
11967 {
11968     ReloadGame(1); // next game
11969 }
11970
11971 int
11972 AutoPlayOneMove ()
11973 {
11974     int fromX, fromY, toX, toY;
11975
11976     if (appData.debugMode) {
11977       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
11978     }
11979
11980     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
11981       return FALSE;
11982
11983     if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) {
11984       pvInfoList[currentMove].depth = programStats.depth;
11985       pvInfoList[currentMove].score = programStats.score;
11986       pvInfoList[currentMove].time  = 0;
11987       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
11988       else { // append analysis of final position as comment
11989         char buf[MSG_SIZ];
11990         snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth);
11991         AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth!
11992       }
11993       programStats.depth = 0;
11994     }
11995
11996     if (currentMove >= forwardMostMove) {
11997       if(gameMode == AnalyzeFile) {
11998           if(appData.loadGameIndex == -1) {
11999             GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE);
12000           ScheduleDelayedEvent(AnalyzeNextGame, 10);
12001           } else {
12002           ExitAnalyzeMode(); SendToProgram("force\n", &first);
12003         }
12004       }
12005 //      gameMode = EndOfGame;
12006 //      ModeHighlight();
12007
12008       /* [AS] Clear current move marker at the end of a game */
12009       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
12010
12011       return FALSE;
12012     }
12013
12014     toX = moveList[currentMove][2] - AAA;
12015     toY = moveList[currentMove][3] - ONE;
12016
12017     if (moveList[currentMove][1] == '@') {
12018         if (appData.highlightLastMove) {
12019             SetHighlights(-1, -1, toX, toY);
12020         }
12021     } else {
12022         int viaX = moveList[currentMove][5] - AAA;
12023         int viaY = moveList[currentMove][6] - ONE;
12024         fromX = moveList[currentMove][0] - AAA;
12025         fromY = moveList[currentMove][1] - ONE;
12026
12027         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
12028
12029         if(moveList[currentMove][4] == ';') { // multi-leg
12030             ChessSquare piece = boards[currentMove][viaY][viaX];
12031             AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
12032             boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
12033             AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
12034             boards[currentMove][viaY][viaX] = piece;
12035         } else
12036         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12037
12038         if (appData.highlightLastMove) {
12039             SetHighlights(fromX, fromY, toX, toY);
12040         }
12041     }
12042     DisplayMove(currentMove);
12043     SendMoveToProgram(currentMove++, &first);
12044     DisplayBothClocks();
12045     DrawPosition(FALSE, boards[currentMove]);
12046     // [HGM] PV info: always display, routine tests if empty
12047     DisplayComment(currentMove - 1, commentList[currentMove]);
12048     return TRUE;
12049 }
12050
12051
12052 int
12053 LoadGameOneMove (ChessMove readAhead)
12054 {
12055     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
12056     char promoChar = NULLCHAR;
12057     ChessMove moveType;
12058     char move[MSG_SIZ];
12059     char *p, *q;
12060
12061     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
12062         gameMode != AnalyzeMode && gameMode != Training) {
12063         gameFileFP = NULL;
12064         return FALSE;
12065     }
12066
12067     yyboardindex = forwardMostMove;
12068     if (readAhead != EndOfFile) {
12069       moveType = readAhead;
12070     } else {
12071       if (gameFileFP == NULL)
12072           return FALSE;
12073       moveType = (ChessMove) Myylex();
12074     }
12075
12076     done = FALSE;
12077     switch (moveType) {
12078       case Comment:
12079         if (appData.debugMode)
12080           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
12081         p = yy_text;
12082
12083         /* append the comment but don't display it */
12084         AppendComment(currentMove, p, FALSE);
12085         return TRUE;
12086
12087       case WhiteCapturesEnPassant:
12088       case BlackCapturesEnPassant:
12089       case WhitePromotion:
12090       case BlackPromotion:
12091       case WhiteNonPromotion:
12092       case BlackNonPromotion:
12093       case NormalMove:
12094       case FirstLeg:
12095       case WhiteKingSideCastle:
12096       case WhiteQueenSideCastle:
12097       case BlackKingSideCastle:
12098       case BlackQueenSideCastle:
12099       case WhiteKingSideCastleWild:
12100       case WhiteQueenSideCastleWild:
12101       case BlackKingSideCastleWild:
12102       case BlackQueenSideCastleWild:
12103       /* PUSH Fabien */
12104       case WhiteHSideCastleFR:
12105       case WhiteASideCastleFR:
12106       case BlackHSideCastleFR:
12107       case BlackASideCastleFR:
12108       /* POP Fabien */
12109         if (appData.debugMode)
12110           fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
12111         fromX = currentMoveString[0] - AAA;
12112         fromY = currentMoveString[1] - ONE;
12113         toX = currentMoveString[2] - AAA;
12114         toY = currentMoveString[3] - ONE;
12115         promoChar = currentMoveString[4];
12116         if(promoChar == ';') promoChar = NULLCHAR;
12117         break;
12118
12119       case WhiteDrop:
12120       case BlackDrop:
12121         if (appData.debugMode)
12122           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
12123         fromX = moveType == WhiteDrop ?
12124           (int) CharToPiece(ToUpper(currentMoveString[0])) :
12125         (int) CharToPiece(ToLower(currentMoveString[0]));
12126         fromY = DROP_RANK;
12127         toX = currentMoveString[2] - AAA;
12128         toY = currentMoveString[3] - ONE;
12129         break;
12130
12131       case WhiteWins:
12132       case BlackWins:
12133       case GameIsDrawn:
12134       case GameUnfinished:
12135         if (appData.debugMode)
12136           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
12137         p = strchr(yy_text, '{');
12138         if (p == NULL) p = strchr(yy_text, '(');
12139         if (p == NULL) {
12140             p = yy_text;
12141             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
12142         } else {
12143             q = strchr(p, *p == '{' ? '}' : ')');
12144             if (q != NULL) *q = NULLCHAR;
12145             p++;
12146         }
12147         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
12148         GameEnds(moveType, p, GE_FILE);
12149         done = TRUE;
12150         if (cmailMsgLoaded) {
12151             ClearHighlights();
12152             flipView = WhiteOnMove(currentMove);
12153             if (moveType == GameUnfinished) flipView = !flipView;
12154             if (appData.debugMode)
12155               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
12156         }
12157         break;
12158
12159       case EndOfFile:
12160         if (appData.debugMode)
12161           fprintf(debugFP, "Parser hit end of file\n");
12162         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12163           case MT_NONE:
12164           case MT_CHECK:
12165             break;
12166           case MT_CHECKMATE:
12167           case MT_STAINMATE:
12168             if (WhiteOnMove(currentMove)) {
12169                 GameEnds(BlackWins, "Black mates", GE_FILE);
12170             } else {
12171                 GameEnds(WhiteWins, "White mates", GE_FILE);
12172             }
12173             break;
12174           case MT_STALEMATE:
12175             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12176             break;
12177         }
12178         done = TRUE;
12179         break;
12180
12181       case MoveNumberOne:
12182         if (lastLoadGameStart == GNUChessGame) {
12183             /* GNUChessGames have numbers, but they aren't move numbers */
12184             if (appData.debugMode)
12185               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12186                       yy_text, (int) moveType);
12187             return LoadGameOneMove(EndOfFile); /* tail recursion */
12188         }
12189         /* else fall thru */
12190
12191       case XBoardGame:
12192       case GNUChessGame:
12193       case PGNTag:
12194         /* Reached start of next game in file */
12195         if (appData.debugMode)
12196           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
12197         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12198           case MT_NONE:
12199           case MT_CHECK:
12200             break;
12201           case MT_CHECKMATE:
12202           case MT_STAINMATE:
12203             if (WhiteOnMove(currentMove)) {
12204                 GameEnds(BlackWins, "Black mates", GE_FILE);
12205             } else {
12206                 GameEnds(WhiteWins, "White mates", GE_FILE);
12207             }
12208             break;
12209           case MT_STALEMATE:
12210             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
12211             break;
12212         }
12213         done = TRUE;
12214         break;
12215
12216       case PositionDiagram:     /* should not happen; ignore */
12217       case ElapsedTime:         /* ignore */
12218       case NAG:                 /* ignore */
12219         if (appData.debugMode)
12220           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
12221                   yy_text, (int) moveType);
12222         return LoadGameOneMove(EndOfFile); /* tail recursion */
12223
12224       case IllegalMove:
12225         if (appData.testLegality) {
12226             if (appData.debugMode)
12227               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
12228             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12229                     (forwardMostMove / 2) + 1,
12230                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12231             DisplayError(move, 0);
12232             done = TRUE;
12233         } else {
12234             if (appData.debugMode)
12235               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
12236                       yy_text, currentMoveString);
12237             if(currentMoveString[1] == '@') {
12238                 fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
12239                 fromY = DROP_RANK;
12240             } else {
12241                 fromX = currentMoveString[0] - AAA;
12242                 fromY = currentMoveString[1] - ONE;
12243             }
12244             toX = currentMoveString[2] - AAA;
12245             toY = currentMoveString[3] - ONE;
12246             promoChar = currentMoveString[4];
12247         }
12248         break;
12249
12250       case AmbiguousMove:
12251         if (appData.debugMode)
12252           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
12253         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
12254                 (forwardMostMove / 2) + 1,
12255                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12256         DisplayError(move, 0);
12257         done = TRUE;
12258         break;
12259
12260       default:
12261       case ImpossibleMove:
12262         if (appData.debugMode)
12263           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
12264         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
12265                 (forwardMostMove / 2) + 1,
12266                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
12267         DisplayError(move, 0);
12268         done = TRUE;
12269         break;
12270     }
12271
12272     if (done) {
12273         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
12274             DrawPosition(FALSE, boards[currentMove]);
12275             DisplayBothClocks();
12276             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
12277               DisplayComment(currentMove - 1, commentList[currentMove]);
12278         }
12279         (void) StopLoadGameTimer();
12280         gameFileFP = NULL;
12281         cmailOldMove = forwardMostMove;
12282         return FALSE;
12283     } else {
12284         /* currentMoveString is set as a side-effect of yylex */
12285
12286         thinkOutput[0] = NULLCHAR;
12287         MakeMove(fromX, fromY, toX, toY, promoChar);
12288         killX = killY = -1; // [HGM] lion: used up
12289         currentMove = forwardMostMove;
12290         return TRUE;
12291     }
12292 }
12293
12294 /* Load the nth game from the given file */
12295 int
12296 LoadGameFromFile (char *filename, int n, char *title, int useList)
12297 {
12298     FILE *f;
12299     char buf[MSG_SIZ];
12300
12301     if (strcmp(filename, "-") == 0) {
12302         f = stdin;
12303         title = "stdin";
12304     } else {
12305         f = fopen(filename, "rb");
12306         if (f == NULL) {
12307           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
12308             DisplayError(buf, errno);
12309             return FALSE;
12310         }
12311     }
12312     if (fseek(f, 0, 0) == -1) {
12313         /* f is not seekable; probably a pipe */
12314         useList = FALSE;
12315     }
12316     if (useList && n == 0) {
12317         int error = GameListBuild(f);
12318         if (error) {
12319             DisplayError(_("Cannot build game list"), error);
12320         } else if (!ListEmpty(&gameList) &&
12321                    ((ListGame *) gameList.tailPred)->number > 1) {
12322             GameListPopUp(f, title);
12323             return TRUE;
12324         }
12325         GameListDestroy();
12326         n = 1;
12327     }
12328     if (n == 0) n = 1;
12329     return LoadGame(f, n, title, FALSE);
12330 }
12331
12332
12333 void
12334 MakeRegisteredMove ()
12335 {
12336     int fromX, fromY, toX, toY;
12337     char promoChar;
12338     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12339         switch (cmailMoveType[lastLoadGameNumber - 1]) {
12340           case CMAIL_MOVE:
12341           case CMAIL_DRAW:
12342             if (appData.debugMode)
12343               fprintf(debugFP, "Restoring %s for game %d\n",
12344                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12345
12346             thinkOutput[0] = NULLCHAR;
12347             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
12348             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
12349             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
12350             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
12351             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
12352             promoChar = cmailMove[lastLoadGameNumber - 1][4];
12353             MakeMove(fromX, fromY, toX, toY, promoChar);
12354             ShowMove(fromX, fromY, toX, toY);
12355
12356             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
12357               case MT_NONE:
12358               case MT_CHECK:
12359                 break;
12360
12361               case MT_CHECKMATE:
12362               case MT_STAINMATE:
12363                 if (WhiteOnMove(currentMove)) {
12364                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
12365                 } else {
12366                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
12367                 }
12368                 break;
12369
12370               case MT_STALEMATE:
12371                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
12372                 break;
12373             }
12374
12375             break;
12376
12377           case CMAIL_RESIGN:
12378             if (WhiteOnMove(currentMove)) {
12379                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12380             } else {
12381                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12382             }
12383             break;
12384
12385           case CMAIL_ACCEPT:
12386             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12387             break;
12388
12389           default:
12390             break;
12391         }
12392     }
12393
12394     return;
12395 }
12396
12397 /* Wrapper around LoadGame for use when a Cmail message is loaded */
12398 int
12399 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
12400 {
12401     int retVal;
12402
12403     if (gameNumber > nCmailGames) {
12404         DisplayError(_("No more games in this message"), 0);
12405         return FALSE;
12406     }
12407     if (f == lastLoadGameFP) {
12408         int offset = gameNumber - lastLoadGameNumber;
12409         if (offset == 0) {
12410             cmailMsg[0] = NULLCHAR;
12411             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12412                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12413                 nCmailMovesRegistered--;
12414             }
12415             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12416             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
12417                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
12418             }
12419         } else {
12420             if (! RegisterMove()) return FALSE;
12421         }
12422     }
12423
12424     retVal = LoadGame(f, gameNumber, title, useList);
12425
12426     /* Make move registered during previous look at this game, if any */
12427     MakeRegisteredMove();
12428
12429     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
12430         commentList[currentMove]
12431           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
12432         DisplayComment(currentMove - 1, commentList[currentMove]);
12433     }
12434
12435     return retVal;
12436 }
12437
12438 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
12439 int
12440 ReloadGame (int offset)
12441 {
12442     int gameNumber = lastLoadGameNumber + offset;
12443     if (lastLoadGameFP == NULL) {
12444         DisplayError(_("No game has been loaded yet"), 0);
12445         return FALSE;
12446     }
12447     if (gameNumber <= 0) {
12448         DisplayError(_("Can't back up any further"), 0);
12449         return FALSE;
12450     }
12451     if (cmailMsgLoaded) {
12452         return CmailLoadGame(lastLoadGameFP, gameNumber,
12453                              lastLoadGameTitle, lastLoadGameUseList);
12454     } else {
12455         return LoadGame(lastLoadGameFP, gameNumber,
12456                         lastLoadGameTitle, lastLoadGameUseList);
12457     }
12458 }
12459
12460 int keys[EmptySquare+1];
12461
12462 int
12463 PositionMatches (Board b1, Board b2)
12464 {
12465     int r, f, sum=0;
12466     switch(appData.searchMode) {
12467         case 1: return CompareWithRights(b1, b2);
12468         case 2:
12469             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12470                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
12471             }
12472             return TRUE;
12473         case 3:
12474             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12475               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
12476                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12477             }
12478             return sum==0;
12479         case 4:
12480             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12481                 sum += keys[b1[r][f]] - keys[b2[r][f]];
12482             }
12483             return sum==0;
12484     }
12485     return TRUE;
12486 }
12487
12488 #define Q_PROMO  4
12489 #define Q_EP     3
12490 #define Q_BCASTL 2
12491 #define Q_WCASTL 1
12492
12493 int pieceList[256], quickBoard[256];
12494 ChessSquare pieceType[256] = { EmptySquare };
12495 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
12496 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
12497 int soughtTotal, turn;
12498 Boolean epOK, flipSearch;
12499
12500 typedef struct {
12501     unsigned char piece, to;
12502 } Move;
12503
12504 #define DSIZE (250000)
12505
12506 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
12507 Move *moveDatabase = initialSpace;
12508 unsigned int movePtr, dataSize = DSIZE;
12509
12510 int
12511 MakePieceList (Board board, int *counts)
12512 {
12513     int r, f, n=Q_PROMO, total=0;
12514     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
12515     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12516         int sq = f + (r<<4);
12517         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
12518             quickBoard[sq] = ++n;
12519             pieceList[n] = sq;
12520             pieceType[n] = board[r][f];
12521             counts[board[r][f]]++;
12522             if(board[r][f] == WhiteKing) pieceList[1] = n; else
12523             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
12524             total++;
12525         }
12526     }
12527     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
12528     return total;
12529 }
12530
12531 void
12532 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
12533 {
12534     int sq = fromX + (fromY<<4);
12535     int piece = quickBoard[sq], rook;
12536     quickBoard[sq] = 0;
12537     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
12538     if(piece == pieceList[1] && fromY == toY) {
12539       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12540         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
12541         moveDatabase[movePtr++].piece = Q_WCASTL;
12542         quickBoard[sq] = piece;
12543         piece = quickBoard[from]; quickBoard[from] = 0;
12544         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12545       } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
12546         quickBoard[sq] = 0; // remove Rook
12547         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
12548         moveDatabase[movePtr++].piece = Q_WCASTL;
12549         quickBoard[sq] = pieceList[1]; // put King
12550         piece = rook;
12551         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12552       }
12553     } else
12554     if(piece == pieceList[2] && fromY == toY) {
12555       if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
12556         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
12557         moveDatabase[movePtr++].piece = Q_BCASTL;
12558         quickBoard[sq] = piece;
12559         piece = quickBoard[from]; quickBoard[from] = 0;
12560         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
12561       } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
12562         quickBoard[sq] = 0; // remove Rook
12563         moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
12564         moveDatabase[movePtr++].piece = Q_BCASTL;
12565         quickBoard[sq] = pieceList[2]; // put King
12566         piece = rook;
12567         moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
12568       }
12569     } else
12570     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
12571         quickBoard[(fromY<<4)+toX] = 0;
12572         moveDatabase[movePtr].piece = Q_EP;
12573         moveDatabase[movePtr++].to = (fromY<<4)+toX;
12574         moveDatabase[movePtr].to = sq;
12575     } else
12576     if(promoPiece != pieceType[piece]) {
12577         moveDatabase[movePtr++].piece = Q_PROMO;
12578         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
12579     }
12580     moveDatabase[movePtr].piece = piece;
12581     quickBoard[sq] = piece;
12582     movePtr++;
12583 }
12584
12585 int
12586 PackGame (Board board)
12587 {
12588     Move *newSpace = NULL;
12589     moveDatabase[movePtr].piece = 0; // terminate previous game
12590     if(movePtr > dataSize) {
12591         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
12592         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
12593         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
12594         if(newSpace) {
12595             int i;
12596             Move *p = moveDatabase, *q = newSpace;
12597             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
12598             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
12599             moveDatabase = newSpace;
12600         } else { // calloc failed, we must be out of memory. Too bad...
12601             dataSize = 0; // prevent calloc events for all subsequent games
12602             return 0;     // and signal this one isn't cached
12603         }
12604     }
12605     movePtr++;
12606     MakePieceList(board, counts);
12607     return movePtr;
12608 }
12609
12610 int
12611 QuickCompare (Board board, int *minCounts, int *maxCounts)
12612 {   // compare according to search mode
12613     int r, f;
12614     switch(appData.searchMode)
12615     {
12616       case 1: // exact position match
12617         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
12618         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12619             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12620         }
12621         break;
12622       case 2: // can have extra material on empty squares
12623         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12624             if(board[r][f] == EmptySquare) continue;
12625             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12626         }
12627         break;
12628       case 3: // material with exact Pawn structure
12629         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12630             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
12631             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
12632         } // fall through to material comparison
12633       case 4: // exact material
12634         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
12635         break;
12636       case 6: // material range with given imbalance
12637         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
12638         // fall through to range comparison
12639       case 5: // material range
12640         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
12641     }
12642     return TRUE;
12643 }
12644
12645 int
12646 QuickScan (Board board, Move *move)
12647 {   // reconstruct game,and compare all positions in it
12648     int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
12649     do {
12650         int piece = move->piece;
12651         int to = move->to, from = pieceList[piece];
12652         if(found < 0) { // if already found just scan to game end for final piece count
12653           if(QuickCompare(soughtBoard, minSought, maxSought) ||
12654            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
12655            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
12656                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
12657             ) {
12658             static int lastCounts[EmptySquare+1];
12659             int i;
12660             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
12661             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
12662           } else stretch = 0;
12663           if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
12664           if(found >= 0 && !appData.minPieces) return found;
12665         }
12666         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
12667           if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
12668           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
12669             piece = (++move)->piece;
12670             from = pieceList[piece];
12671             counts[pieceType[piece]]--;
12672             pieceType[piece] = (ChessSquare) move->to;
12673             counts[move->to]++;
12674           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
12675             counts[pieceType[quickBoard[to]]]--;
12676             quickBoard[to] = 0; total--;
12677             move++;
12678             continue;
12679           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
12680             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
12681             from  = pieceList[piece]; // so this must be King
12682             quickBoard[from] = 0;
12683             pieceList[piece] = to;
12684             from = pieceList[(++move)->piece]; // for FRC this has to be done here
12685             quickBoard[from] = 0; // rook
12686             quickBoard[to] = piece;
12687             to = move->to; piece = move->piece;
12688             goto aftercastle;
12689           }
12690         }
12691         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
12692         if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
12693         quickBoard[from] = 0;
12694       aftercastle:
12695         quickBoard[to] = piece;
12696         pieceList[piece] = to;
12697         cnt++; turn ^= 3;
12698         move++;
12699     } while(1);
12700 }
12701
12702 void
12703 InitSearch ()
12704 {
12705     int r, f;
12706     flipSearch = FALSE;
12707     CopyBoard(soughtBoard, boards[currentMove]);
12708     soughtTotal = MakePieceList(soughtBoard, maxSought);
12709     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
12710     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
12711     CopyBoard(reverseBoard, boards[currentMove]);
12712     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12713         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
12714         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
12715         reverseBoard[r][f] = piece;
12716     }
12717     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3;
12718     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
12719     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
12720                  || (boards[currentMove][CASTLING][2] == NoRights ||
12721                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
12722                  && (boards[currentMove][CASTLING][5] == NoRights ||
12723                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
12724       ) {
12725         flipSearch = TRUE;
12726         CopyBoard(flipBoard, soughtBoard);
12727         CopyBoard(rotateBoard, reverseBoard);
12728         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
12729             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
12730             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
12731         }
12732     }
12733     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
12734     if(appData.searchMode >= 5) {
12735         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
12736         MakePieceList(soughtBoard, minSought);
12737         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
12738     }
12739     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
12740         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
12741 }
12742
12743 GameInfo dummyInfo;
12744 static int creatingBook;
12745
12746 int
12747 GameContainsPosition (FILE *f, ListGame *lg)
12748 {
12749     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
12750     int fromX, fromY, toX, toY;
12751     char promoChar;
12752     static int initDone=FALSE;
12753
12754     // weed out games based on numerical tag comparison
12755     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
12756     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
12757     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
12758     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
12759     if(!initDone) {
12760         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
12761         initDone = TRUE;
12762     }
12763     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE);
12764     else CopyBoard(boards[scratch], initialPosition); // default start position
12765     if(lg->moves) {
12766         turn = btm + 1;
12767         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
12768         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
12769     }
12770     if(btm) plyNr++;
12771     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12772     fseek(f, lg->offset, 0);
12773     yynewfile(f);
12774     while(1) {
12775         yyboardindex = scratch;
12776         quickFlag = plyNr+1;
12777         next = Myylex();
12778         quickFlag = 0;
12779         switch(next) {
12780             case PGNTag:
12781                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
12782             default:
12783                 continue;
12784
12785             case XBoardGame:
12786             case GNUChessGame:
12787                 if(plyNr) return -1; // after we have seen moves, this is for new game
12788               continue;
12789
12790             case AmbiguousMove: // we cannot reconstruct the game beyond these two
12791             case ImpossibleMove:
12792             case WhiteWins: // game ends here with these four
12793             case BlackWins:
12794             case GameIsDrawn:
12795             case GameUnfinished:
12796                 return -1;
12797
12798             case IllegalMove:
12799                 if(appData.testLegality) return -1;
12800             case WhiteCapturesEnPassant:
12801             case BlackCapturesEnPassant:
12802             case WhitePromotion:
12803             case BlackPromotion:
12804             case WhiteNonPromotion:
12805             case BlackNonPromotion:
12806             case NormalMove:
12807             case FirstLeg:
12808             case WhiteKingSideCastle:
12809             case WhiteQueenSideCastle:
12810             case BlackKingSideCastle:
12811             case BlackQueenSideCastle:
12812             case WhiteKingSideCastleWild:
12813             case WhiteQueenSideCastleWild:
12814             case BlackKingSideCastleWild:
12815             case BlackQueenSideCastleWild:
12816             case WhiteHSideCastleFR:
12817             case WhiteASideCastleFR:
12818             case BlackHSideCastleFR:
12819             case BlackASideCastleFR:
12820                 fromX = currentMoveString[0] - AAA;
12821                 fromY = currentMoveString[1] - ONE;
12822                 toX = currentMoveString[2] - AAA;
12823                 toY = currentMoveString[3] - ONE;
12824                 promoChar = currentMoveString[4];
12825                 break;
12826             case WhiteDrop:
12827             case BlackDrop:
12828                 fromX = next == WhiteDrop ?
12829                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
12830                   (int) CharToPiece(ToLower(currentMoveString[0]));
12831                 fromY = DROP_RANK;
12832                 toX = currentMoveString[2] - AAA;
12833                 toY = currentMoveString[3] - ONE;
12834                 promoChar = 0;
12835                 break;
12836         }
12837         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
12838         plyNr++;
12839         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
12840         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
12841         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
12842         if(appData.findMirror) {
12843             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
12844             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
12845         }
12846     }
12847 }
12848
12849 /* Load the nth game from open file f */
12850 int
12851 LoadGame (FILE *f, int gameNumber, char *title, int useList)
12852 {
12853     ChessMove cm;
12854     char buf[MSG_SIZ];
12855     int gn = gameNumber;
12856     ListGame *lg = NULL;
12857     int numPGNTags = 0;
12858     int err, pos = -1;
12859     GameMode oldGameMode;
12860     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
12861     char oldName[MSG_SIZ];
12862
12863     safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
12864
12865     if (appData.debugMode)
12866         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
12867
12868     if (gameMode == Training )
12869         SetTrainingModeOff();
12870
12871     oldGameMode = gameMode;
12872     if (gameMode != BeginningOfGame) {
12873       Reset(FALSE, TRUE);
12874     }
12875     killX = killY = -1; // [HGM] lion: in case we did not Reset
12876
12877     gameFileFP = f;
12878     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
12879         fclose(lastLoadGameFP);
12880     }
12881
12882     if (useList) {
12883         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
12884
12885         if (lg) {
12886             fseek(f, lg->offset, 0);
12887             GameListHighlight(gameNumber);
12888             pos = lg->position;
12889             gn = 1;
12890         }
12891         else {
12892             if(oldGameMode == AnalyzeFile && appData.loadGameIndex == -1)
12893               appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
12894             else
12895             DisplayError(_("Game number out of range"), 0);
12896             return FALSE;
12897         }
12898     } else {
12899         GameListDestroy();
12900         if (fseek(f, 0, 0) == -1) {
12901             if (f == lastLoadGameFP ?
12902                 gameNumber == lastLoadGameNumber + 1 :
12903                 gameNumber == 1) {
12904                 gn = 1;
12905             } else {
12906                 DisplayError(_("Can't seek on game file"), 0);
12907                 return FALSE;
12908             }
12909         }
12910     }
12911     lastLoadGameFP = f;
12912     lastLoadGameNumber = gameNumber;
12913     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
12914     lastLoadGameUseList = useList;
12915
12916     yynewfile(f);
12917
12918     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
12919       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
12920                 lg->gameInfo.black);
12921             DisplayTitle(buf);
12922     } else if (*title != NULLCHAR) {
12923         if (gameNumber > 1) {
12924           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
12925             DisplayTitle(buf);
12926         } else {
12927             DisplayTitle(title);
12928         }
12929     }
12930
12931     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
12932         gameMode = PlayFromGameFile;
12933         ModeHighlight();
12934     }
12935
12936     currentMove = forwardMostMove = backwardMostMove = 0;
12937     CopyBoard(boards[0], initialPosition);
12938     StopClocks();
12939
12940     /*
12941      * Skip the first gn-1 games in the file.
12942      * Also skip over anything that precedes an identifiable
12943      * start of game marker, to avoid being confused by
12944      * garbage at the start of the file.  Currently
12945      * recognized start of game markers are the move number "1",
12946      * the pattern "gnuchess .* game", the pattern
12947      * "^[#;%] [^ ]* game file", and a PGN tag block.
12948      * A game that starts with one of the latter two patterns
12949      * will also have a move number 1, possibly
12950      * following a position diagram.
12951      * 5-4-02: Let's try being more lenient and allowing a game to
12952      * start with an unnumbered move.  Does that break anything?
12953      */
12954     cm = lastLoadGameStart = EndOfFile;
12955     while (gn > 0) {
12956         yyboardindex = forwardMostMove;
12957         cm = (ChessMove) Myylex();
12958         switch (cm) {
12959           case EndOfFile:
12960             if (cmailMsgLoaded) {
12961                 nCmailGames = CMAIL_MAX_GAMES - gn;
12962             } else {
12963                 Reset(TRUE, TRUE);
12964                 DisplayError(_("Game not found in file"), 0);
12965             }
12966             return FALSE;
12967
12968           case GNUChessGame:
12969           case XBoardGame:
12970             gn--;
12971             lastLoadGameStart = cm;
12972             break;
12973
12974           case MoveNumberOne:
12975             switch (lastLoadGameStart) {
12976               case GNUChessGame:
12977               case XBoardGame:
12978               case PGNTag:
12979                 break;
12980               case MoveNumberOne:
12981               case EndOfFile:
12982                 gn--;           /* count this game */
12983                 lastLoadGameStart = cm;
12984                 break;
12985               default:
12986                 /* impossible */
12987                 break;
12988             }
12989             break;
12990
12991           case PGNTag:
12992             switch (lastLoadGameStart) {
12993               case GNUChessGame:
12994               case PGNTag:
12995               case MoveNumberOne:
12996               case EndOfFile:
12997                 gn--;           /* count this game */
12998                 lastLoadGameStart = cm;
12999                 break;
13000               case XBoardGame:
13001                 lastLoadGameStart = cm; /* game counted already */
13002                 break;
13003               default:
13004                 /* impossible */
13005                 break;
13006             }
13007             if (gn > 0) {
13008                 do {
13009                     yyboardindex = forwardMostMove;
13010                     cm = (ChessMove) Myylex();
13011                 } while (cm == PGNTag || cm == Comment);
13012             }
13013             break;
13014
13015           case WhiteWins:
13016           case BlackWins:
13017           case GameIsDrawn:
13018             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
13019                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
13020                     != CMAIL_OLD_RESULT) {
13021                     nCmailResults ++ ;
13022                     cmailResult[  CMAIL_MAX_GAMES
13023                                 - gn - 1] = CMAIL_OLD_RESULT;
13024                 }
13025             }
13026             break;
13027
13028           case NormalMove:
13029           case FirstLeg:
13030             /* Only a NormalMove can be at the start of a game
13031              * without a position diagram. */
13032             if (lastLoadGameStart == EndOfFile ) {
13033               gn--;
13034               lastLoadGameStart = MoveNumberOne;
13035             }
13036             break;
13037
13038           default:
13039             break;
13040         }
13041     }
13042
13043     if (appData.debugMode)
13044       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
13045
13046     if (cm == XBoardGame) {
13047         /* Skip any header junk before position diagram and/or move 1 */
13048         for (;;) {
13049             yyboardindex = forwardMostMove;
13050             cm = (ChessMove) Myylex();
13051
13052             if (cm == EndOfFile ||
13053                 cm == GNUChessGame || cm == XBoardGame) {
13054                 /* Empty game; pretend end-of-file and handle later */
13055                 cm = EndOfFile;
13056                 break;
13057             }
13058
13059             if (cm == MoveNumberOne || cm == PositionDiagram ||
13060                 cm == PGNTag || cm == Comment)
13061               break;
13062         }
13063     } else if (cm == GNUChessGame) {
13064         if (gameInfo.event != NULL) {
13065             free(gameInfo.event);
13066         }
13067         gameInfo.event = StrSave(yy_text);
13068     }
13069
13070     startedFromSetupPosition = startedFromPositionFile; // [HGM]
13071     while (cm == PGNTag) {
13072         if (appData.debugMode)
13073           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
13074         err = ParsePGNTag(yy_text, &gameInfo);
13075         if (!err) numPGNTags++;
13076
13077         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
13078         if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
13079             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
13080             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
13081             InitPosition(TRUE);
13082             oldVariant = gameInfo.variant;
13083             if (appData.debugMode)
13084               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
13085         }
13086
13087
13088         if (gameInfo.fen != NULL) {
13089           Board initial_position;
13090           startedFromSetupPosition = TRUE;
13091           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) {
13092             Reset(TRUE, TRUE);
13093             DisplayError(_("Bad FEN position in file"), 0);
13094             return FALSE;
13095           }
13096           CopyBoard(boards[0], initial_position);
13097           if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
13098             CopyBoard(initialPosition, initial_position);
13099           if (blackPlaysFirst) {
13100             currentMove = forwardMostMove = backwardMostMove = 1;
13101             CopyBoard(boards[1], initial_position);
13102             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13103             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13104             timeRemaining[0][1] = whiteTimeRemaining;
13105             timeRemaining[1][1] = blackTimeRemaining;
13106             if (commentList[0] != NULL) {
13107               commentList[1] = commentList[0];
13108               commentList[0] = NULL;
13109             }
13110           } else {
13111             currentMove = forwardMostMove = backwardMostMove = 0;
13112           }
13113           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
13114           {   int i;
13115               initialRulePlies = FENrulePlies;
13116               for( i=0; i< nrCastlingRights; i++ )
13117                   initialRights[i] = initial_position[CASTLING][i];
13118           }
13119           yyboardindex = forwardMostMove;
13120           free(gameInfo.fen);
13121           gameInfo.fen = NULL;
13122         }
13123
13124         yyboardindex = forwardMostMove;
13125         cm = (ChessMove) Myylex();
13126
13127         /* Handle comments interspersed among the tags */
13128         while (cm == Comment) {
13129             char *p;
13130             if (appData.debugMode)
13131               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13132             p = yy_text;
13133             AppendComment(currentMove, p, FALSE);
13134             yyboardindex = forwardMostMove;
13135             cm = (ChessMove) Myylex();
13136         }
13137     }
13138
13139     /* don't rely on existence of Event tag since if game was
13140      * pasted from clipboard the Event tag may not exist
13141      */
13142     if (numPGNTags > 0){
13143         char *tags;
13144         if (gameInfo.variant == VariantNormal) {
13145           VariantClass v = StringToVariant(gameInfo.event);
13146           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
13147           if(v < VariantShogi) gameInfo.variant = v;
13148         }
13149         if (!matchMode) {
13150           if( appData.autoDisplayTags ) {
13151             tags = PGNTags(&gameInfo);
13152             TagsPopUp(tags, CmailMsg());
13153             free(tags);
13154           }
13155         }
13156     } else {
13157         /* Make something up, but don't display it now */
13158         SetGameInfo();
13159         TagsPopDown();
13160     }
13161
13162     if (cm == PositionDiagram) {
13163         int i, j;
13164         char *p;
13165         Board initial_position;
13166
13167         if (appData.debugMode)
13168           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
13169
13170         if (!startedFromSetupPosition) {
13171             p = yy_text;
13172             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
13173               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
13174                 switch (*p) {
13175                   case '{':
13176                   case '[':
13177                   case '-':
13178                   case ' ':
13179                   case '\t':
13180                   case '\n':
13181                   case '\r':
13182                     break;
13183                   default:
13184                     initial_position[i][j++] = CharToPiece(*p);
13185                     break;
13186                 }
13187             while (*p == ' ' || *p == '\t' ||
13188                    *p == '\n' || *p == '\r') p++;
13189
13190             if (strncmp(p, "black", strlen("black"))==0)
13191               blackPlaysFirst = TRUE;
13192             else
13193               blackPlaysFirst = FALSE;
13194             startedFromSetupPosition = TRUE;
13195
13196             CopyBoard(boards[0], initial_position);
13197             if (blackPlaysFirst) {
13198                 currentMove = forwardMostMove = backwardMostMove = 1;
13199                 CopyBoard(boards[1], initial_position);
13200                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13201                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13202                 timeRemaining[0][1] = whiteTimeRemaining;
13203                 timeRemaining[1][1] = blackTimeRemaining;
13204                 if (commentList[0] != NULL) {
13205                     commentList[1] = commentList[0];
13206                     commentList[0] = NULL;
13207                 }
13208             } else {
13209                 currentMove = forwardMostMove = backwardMostMove = 0;
13210             }
13211         }
13212         yyboardindex = forwardMostMove;
13213         cm = (ChessMove) Myylex();
13214     }
13215
13216   if(!creatingBook) {
13217     if (first.pr == NoProc) {
13218         StartChessProgram(&first);
13219     }
13220     InitChessProgram(&first, FALSE);
13221     if(gameInfo.variant == VariantUnknown && *oldName) {
13222         safeStrCpy(engineVariant, oldName, MSG_SIZ);
13223         gameInfo.variant = v;
13224     }
13225     SendToProgram("force\n", &first);
13226     if (startedFromSetupPosition) {
13227         SendBoard(&first, forwardMostMove);
13228     if (appData.debugMode) {
13229         fprintf(debugFP, "Load Game\n");
13230     }
13231         DisplayBothClocks();
13232     }
13233   }
13234
13235     /* [HGM] server: flag to write setup moves in broadcast file as one */
13236     loadFlag = appData.suppressLoadMoves;
13237
13238     while (cm == Comment) {
13239         char *p;
13240         if (appData.debugMode)
13241           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
13242         p = yy_text;
13243         AppendComment(currentMove, p, FALSE);
13244         yyboardindex = forwardMostMove;
13245         cm = (ChessMove) Myylex();
13246     }
13247
13248     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
13249         cm == WhiteWins || cm == BlackWins ||
13250         cm == GameIsDrawn || cm == GameUnfinished) {
13251         DisplayMessage("", _("No moves in game"));
13252         if (cmailMsgLoaded) {
13253             if (appData.debugMode)
13254               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
13255             ClearHighlights();
13256             flipView = FALSE;
13257         }
13258         DrawPosition(FALSE, boards[currentMove]);
13259         DisplayBothClocks();
13260         gameMode = EditGame;
13261         ModeHighlight();
13262         gameFileFP = NULL;
13263         cmailOldMove = 0;
13264         return TRUE;
13265     }
13266
13267     // [HGM] PV info: routine tests if comment empty
13268     if (!matchMode && (pausing || appData.timeDelay != 0)) {
13269         DisplayComment(currentMove - 1, commentList[currentMove]);
13270     }
13271     if (!matchMode && appData.timeDelay != 0)
13272       DrawPosition(FALSE, boards[currentMove]);
13273
13274     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
13275       programStats.ok_to_send = 1;
13276     }
13277
13278     /* if the first token after the PGN tags is a move
13279      * and not move number 1, retrieve it from the parser
13280      */
13281     if (cm != MoveNumberOne)
13282         LoadGameOneMove(cm);
13283
13284     /* load the remaining moves from the file */
13285     while (LoadGameOneMove(EndOfFile)) {
13286       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13287       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13288     }
13289
13290     /* rewind to the start of the game */
13291     currentMove = backwardMostMove;
13292
13293     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13294
13295     if (oldGameMode == AnalyzeFile) {
13296       appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
13297       AnalyzeFileEvent();
13298     } else
13299     if (oldGameMode == AnalyzeMode) {
13300       AnalyzeFileEvent();
13301     }
13302
13303     if(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) {
13304         long int w, b; // [HGM] adjourn: restore saved clock times
13305         char *p = strstr(gameInfo.resultDetails, "(Clocks:");
13306         if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) {
13307             timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500;
13308             timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500;
13309         }
13310     }
13311
13312     if(creatingBook) return TRUE;
13313     if (!matchMode && pos > 0) {
13314         ToNrEvent(pos); // [HGM] no autoplay if selected on position
13315     } else
13316     if (matchMode || appData.timeDelay == 0) {
13317       ToEndEvent();
13318     } else if (appData.timeDelay > 0) {
13319       AutoPlayGameLoop();
13320     }
13321
13322     if (appData.debugMode)
13323         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
13324
13325     loadFlag = 0; /* [HGM] true game starts */
13326     return TRUE;
13327 }
13328
13329 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
13330 int
13331 ReloadPosition (int offset)
13332 {
13333     int positionNumber = lastLoadPositionNumber + offset;
13334     if (lastLoadPositionFP == NULL) {
13335         DisplayError(_("No position has been loaded yet"), 0);
13336         return FALSE;
13337     }
13338     if (positionNumber <= 0) {
13339         DisplayError(_("Can't back up any further"), 0);
13340         return FALSE;
13341     }
13342     return LoadPosition(lastLoadPositionFP, positionNumber,
13343                         lastLoadPositionTitle);
13344 }
13345
13346 /* Load the nth position from the given file */
13347 int
13348 LoadPositionFromFile (char *filename, int n, char *title)
13349 {
13350     FILE *f;
13351     char buf[MSG_SIZ];
13352
13353     if (strcmp(filename, "-") == 0) {
13354         return LoadPosition(stdin, n, "stdin");
13355     } else {
13356         f = fopen(filename, "rb");
13357         if (f == NULL) {
13358             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13359             DisplayError(buf, errno);
13360             return FALSE;
13361         } else {
13362             return LoadPosition(f, n, title);
13363         }
13364     }
13365 }
13366
13367 /* Load the nth position from the given open file, and close it */
13368 int
13369 LoadPosition (FILE *f, int positionNumber, char *title)
13370 {
13371     char *p, line[MSG_SIZ];
13372     Board initial_position;
13373     int i, j, fenMode, pn;
13374
13375     if (gameMode == Training )
13376         SetTrainingModeOff();
13377
13378     if (gameMode != BeginningOfGame) {
13379         Reset(FALSE, TRUE);
13380     }
13381     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
13382         fclose(lastLoadPositionFP);
13383     }
13384     if (positionNumber == 0) positionNumber = 1;
13385     lastLoadPositionFP = f;
13386     lastLoadPositionNumber = positionNumber;
13387     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
13388     if (first.pr == NoProc && !appData.noChessProgram) {
13389       StartChessProgram(&first);
13390       InitChessProgram(&first, FALSE);
13391     }
13392     pn = positionNumber;
13393     if (positionNumber < 0) {
13394         /* Negative position number means to seek to that byte offset */
13395         if (fseek(f, -positionNumber, 0) == -1) {
13396             DisplayError(_("Can't seek on position file"), 0);
13397             return FALSE;
13398         };
13399         pn = 1;
13400     } else {
13401         if (fseek(f, 0, 0) == -1) {
13402             if (f == lastLoadPositionFP ?
13403                 positionNumber == lastLoadPositionNumber + 1 :
13404                 positionNumber == 1) {
13405                 pn = 1;
13406             } else {
13407                 DisplayError(_("Can't seek on position file"), 0);
13408                 return FALSE;
13409             }
13410         }
13411     }
13412     /* See if this file is FEN or old-style xboard */
13413     if (fgets(line, MSG_SIZ, f) == NULL) {
13414         DisplayError(_("Position not found in file"), 0);
13415         return FALSE;
13416     }
13417     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
13418     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
13419
13420     if (pn >= 2) {
13421         if (fenMode || line[0] == '#') pn--;
13422         while (pn > 0) {
13423             /* skip positions before number pn */
13424             if (fgets(line, MSG_SIZ, f) == NULL) {
13425                 Reset(TRUE, TRUE);
13426                 DisplayError(_("Position not found in file"), 0);
13427                 return FALSE;
13428             }
13429             if (fenMode || line[0] == '#') pn--;
13430         }
13431     }
13432
13433     if (fenMode) {
13434         char *p;
13435         if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
13436             DisplayError(_("Bad FEN position in file"), 0);
13437             return FALSE;
13438         }
13439         if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
13440             sscanf(p+3, "%s", bestMove);
13441         } else *bestMove = NULLCHAR;
13442     } else {
13443         (void) fgets(line, MSG_SIZ, f);
13444         (void) fgets(line, MSG_SIZ, f);
13445
13446         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13447             (void) fgets(line, MSG_SIZ, f);
13448             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
13449                 if (*p == ' ')
13450                   continue;
13451                 initial_position[i][j++] = CharToPiece(*p);
13452             }
13453         }
13454
13455         blackPlaysFirst = FALSE;
13456         if (!feof(f)) {
13457             (void) fgets(line, MSG_SIZ, f);
13458             if (strncmp(line, "black", strlen("black"))==0)
13459               blackPlaysFirst = TRUE;
13460         }
13461     }
13462     startedFromSetupPosition = TRUE;
13463
13464     CopyBoard(boards[0], initial_position);
13465     if (blackPlaysFirst) {
13466         currentMove = forwardMostMove = backwardMostMove = 1;
13467         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13468         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13469         CopyBoard(boards[1], initial_position);
13470         DisplayMessage("", _("Black to play"));
13471     } else {
13472         currentMove = forwardMostMove = backwardMostMove = 0;
13473         DisplayMessage("", _("White to play"));
13474     }
13475     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13476     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
13477         SendToProgram("force\n", &first);
13478         SendBoard(&first, forwardMostMove);
13479     }
13480     if (appData.debugMode) {
13481 int i, j;
13482   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
13483   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
13484         fprintf(debugFP, "Load Position\n");
13485     }
13486
13487     if (positionNumber > 1) {
13488       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
13489         DisplayTitle(line);
13490     } else {
13491         DisplayTitle(title);
13492     }
13493     gameMode = EditGame;
13494     ModeHighlight();
13495     ResetClocks();
13496     timeRemaining[0][1] = whiteTimeRemaining;
13497     timeRemaining[1][1] = blackTimeRemaining;
13498     DrawPosition(FALSE, boards[currentMove]);
13499
13500     return TRUE;
13501 }
13502
13503
13504 void
13505 CopyPlayerNameIntoFileName (char **dest, char *src)
13506 {
13507     while (*src != NULLCHAR && *src != ',') {
13508         if (*src == ' ') {
13509             *(*dest)++ = '_';
13510             src++;
13511         } else {
13512             *(*dest)++ = *src++;
13513         }
13514     }
13515 }
13516
13517 char *
13518 DefaultFileName (char *ext)
13519 {
13520     static char def[MSG_SIZ];
13521     char *p;
13522
13523     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
13524         p = def;
13525         CopyPlayerNameIntoFileName(&p, gameInfo.white);
13526         *p++ = '-';
13527         CopyPlayerNameIntoFileName(&p, gameInfo.black);
13528         *p++ = '.';
13529         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
13530     } else {
13531         def[0] = NULLCHAR;
13532     }
13533     return def;
13534 }
13535
13536 /* Save the current game to the given file */
13537 int
13538 SaveGameToFile (char *filename, int append)
13539 {
13540     FILE *f;
13541     char buf[MSG_SIZ];
13542     int result, i, t,tot=0;
13543
13544     if (strcmp(filename, "-") == 0) {
13545         return SaveGame(stdout, 0, NULL);
13546     } else {
13547         for(i=0; i<10; i++) { // upto 10 tries
13548              f = fopen(filename, append ? "a" : "w");
13549              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
13550              if(f || errno != 13) break;
13551              DoSleep(t = 5 + random()%11); // wait 5-15 msec
13552              tot += t;
13553         }
13554         if (f == NULL) {
13555             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13556             DisplayError(buf, errno);
13557             return FALSE;
13558         } else {
13559             safeStrCpy(buf, lastMsg, MSG_SIZ);
13560             DisplayMessage(_("Waiting for access to save file"), "");
13561             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
13562             DisplayMessage(_("Saving game"), "");
13563             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
13564             result = SaveGame(f, 0, NULL);
13565             DisplayMessage(buf, "");
13566             return result;
13567         }
13568     }
13569 }
13570
13571 char *
13572 SavePart (char *str)
13573 {
13574     static char buf[MSG_SIZ];
13575     char *p;
13576
13577     p = strchr(str, ' ');
13578     if (p == NULL) return str;
13579     strncpy(buf, str, p - str);
13580     buf[p - str] = NULLCHAR;
13581     return buf;
13582 }
13583
13584 #define PGN_MAX_LINE 75
13585
13586 #define PGN_SIDE_WHITE  0
13587 #define PGN_SIDE_BLACK  1
13588
13589 static int
13590 FindFirstMoveOutOfBook (int side)
13591 {
13592     int result = -1;
13593
13594     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
13595         int index = backwardMostMove;
13596         int has_book_hit = 0;
13597
13598         if( (index % 2) != side ) {
13599             index++;
13600         }
13601
13602         while( index < forwardMostMove ) {
13603             /* Check to see if engine is in book */
13604             int depth = pvInfoList[index].depth;
13605             int score = pvInfoList[index].score;
13606             int in_book = 0;
13607
13608             if( depth <= 2 ) {
13609                 in_book = 1;
13610             }
13611             else if( score == 0 && depth == 63 ) {
13612                 in_book = 1; /* Zappa */
13613             }
13614             else if( score == 2 && depth == 99 ) {
13615                 in_book = 1; /* Abrok */
13616             }
13617
13618             has_book_hit += in_book;
13619
13620             if( ! in_book ) {
13621                 result = index;
13622
13623                 break;
13624             }
13625
13626             index += 2;
13627         }
13628     }
13629
13630     return result;
13631 }
13632
13633 void
13634 GetOutOfBookInfo (char * buf)
13635 {
13636     int oob[2];
13637     int i;
13638     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13639
13640     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
13641     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
13642
13643     *buf = '\0';
13644
13645     if( oob[0] >= 0 || oob[1] >= 0 ) {
13646         for( i=0; i<2; i++ ) {
13647             int idx = oob[i];
13648
13649             if( idx >= 0 ) {
13650                 if( i > 0 && oob[0] >= 0 ) {
13651                     strcat( buf, "   " );
13652                 }
13653
13654                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
13655                 sprintf( buf+strlen(buf), "%s%.2f",
13656                     pvInfoList[idx].score >= 0 ? "+" : "",
13657                     pvInfoList[idx].score / 100.0 );
13658             }
13659         }
13660     }
13661 }
13662
13663 /* Save game in PGN style */
13664 static void
13665 SaveGamePGN2 (FILE *f)
13666 {
13667     int i, offset, linelen, newblock;
13668 //    char *movetext;
13669     char numtext[32];
13670     int movelen, numlen, blank;
13671     char move_buffer[100]; /* [AS] Buffer for move+PV info */
13672
13673     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13674
13675     PrintPGNTags(f, &gameInfo);
13676
13677     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
13678
13679     if (backwardMostMove > 0 || startedFromSetupPosition) {
13680         char *fen = PositionToFEN(backwardMostMove, NULL, 1);
13681         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
13682         fprintf(f, "\n{--------------\n");
13683         PrintPosition(f, backwardMostMove);
13684         fprintf(f, "--------------}\n");
13685         free(fen);
13686     }
13687     else {
13688         /* [AS] Out of book annotation */
13689         if( appData.saveOutOfBookInfo ) {
13690             char buf[64];
13691
13692             GetOutOfBookInfo( buf );
13693
13694             if( buf[0] != '\0' ) {
13695                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
13696             }
13697         }
13698
13699         fprintf(f, "\n");
13700     }
13701
13702     i = backwardMostMove;
13703     linelen = 0;
13704     newblock = TRUE;
13705
13706     while (i < forwardMostMove) {
13707         /* Print comments preceding this move */
13708         if (commentList[i] != NULL) {
13709             if (linelen > 0) fprintf(f, "\n");
13710             fprintf(f, "%s", commentList[i]);
13711             linelen = 0;
13712             newblock = TRUE;
13713         }
13714
13715         /* Format move number */
13716         if ((i % 2) == 0)
13717           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
13718         else
13719           if (newblock)
13720             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
13721           else
13722             numtext[0] = NULLCHAR;
13723
13724         numlen = strlen(numtext);
13725         newblock = FALSE;
13726
13727         /* Print move number */
13728         blank = linelen > 0 && numlen > 0;
13729         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
13730             fprintf(f, "\n");
13731             linelen = 0;
13732             blank = 0;
13733         }
13734         if (blank) {
13735             fprintf(f, " ");
13736             linelen++;
13737         }
13738         fprintf(f, "%s", numtext);
13739         linelen += numlen;
13740
13741         /* Get move */
13742         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
13743         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
13744
13745         /* Print move */
13746         blank = linelen > 0 && movelen > 0;
13747         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13748             fprintf(f, "\n");
13749             linelen = 0;
13750             blank = 0;
13751         }
13752         if (blank) {
13753             fprintf(f, " ");
13754             linelen++;
13755         }
13756         fprintf(f, "%s", move_buffer);
13757         linelen += movelen;
13758
13759         /* [AS] Add PV info if present */
13760         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
13761             /* [HGM] add time */
13762             char buf[MSG_SIZ]; int seconds;
13763
13764             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
13765
13766             if( seconds <= 0)
13767               buf[0] = 0;
13768             else
13769               if( seconds < 30 )
13770                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
13771               else
13772                 {
13773                   seconds = (seconds + 4)/10; // round to full seconds
13774                   if( seconds < 60 )
13775                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
13776                   else
13777                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
13778                 }
13779
13780             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
13781                       pvInfoList[i].score >= 0 ? "+" : "",
13782                       pvInfoList[i].score / 100.0,
13783                       pvInfoList[i].depth,
13784                       buf );
13785
13786             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
13787
13788             /* Print score/depth */
13789             blank = linelen > 0 && movelen > 0;
13790             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
13791                 fprintf(f, "\n");
13792                 linelen = 0;
13793                 blank = 0;
13794             }
13795             if (blank) {
13796                 fprintf(f, " ");
13797                 linelen++;
13798             }
13799             fprintf(f, "%s", move_buffer);
13800             linelen += movelen;
13801         }
13802
13803         i++;
13804     }
13805
13806     /* Start a new line */
13807     if (linelen > 0) fprintf(f, "\n");
13808
13809     /* Print comments after last move */
13810     if (commentList[i] != NULL) {
13811         fprintf(f, "%s\n", commentList[i]);
13812     }
13813
13814     /* Print result */
13815     if (gameInfo.resultDetails != NULL &&
13816         gameInfo.resultDetails[0] != NULLCHAR) {
13817         char buf[MSG_SIZ], *p = gameInfo.resultDetails;
13818         if(gameInfo.result == GameUnfinished && appData.clockMode &&
13819            (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings
13820             snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf;
13821         fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result));
13822     } else {
13823         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13824     }
13825 }
13826
13827 /* Save game in PGN style and close the file */
13828 int
13829 SaveGamePGN (FILE *f)
13830 {
13831     SaveGamePGN2(f);
13832     fclose(f);
13833     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13834     return TRUE;
13835 }
13836
13837 /* Save game in old style and close the file */
13838 int
13839 SaveGameOldStyle (FILE *f)
13840 {
13841     int i, offset;
13842     time_t tm;
13843
13844     tm = time((time_t *) NULL);
13845
13846     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
13847     PrintOpponents(f);
13848
13849     if (backwardMostMove > 0 || startedFromSetupPosition) {
13850         fprintf(f, "\n[--------------\n");
13851         PrintPosition(f, backwardMostMove);
13852         fprintf(f, "--------------]\n");
13853     } else {
13854         fprintf(f, "\n");
13855     }
13856
13857     i = backwardMostMove;
13858     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
13859
13860     while (i < forwardMostMove) {
13861         if (commentList[i] != NULL) {
13862             fprintf(f, "[%s]\n", commentList[i]);
13863         }
13864
13865         if ((i % 2) == 1) {
13866             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
13867             i++;
13868         } else {
13869             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
13870             i++;
13871             if (commentList[i] != NULL) {
13872                 fprintf(f, "\n");
13873                 continue;
13874             }
13875             if (i >= forwardMostMove) {
13876                 fprintf(f, "\n");
13877                 break;
13878             }
13879             fprintf(f, "%s\n", parseList[i]);
13880             i++;
13881         }
13882     }
13883
13884     if (commentList[i] != NULL) {
13885         fprintf(f, "[%s]\n", commentList[i]);
13886     }
13887
13888     /* This isn't really the old style, but it's close enough */
13889     if (gameInfo.resultDetails != NULL &&
13890         gameInfo.resultDetails[0] != NULLCHAR) {
13891         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
13892                 gameInfo.resultDetails);
13893     } else {
13894         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
13895     }
13896
13897     fclose(f);
13898     return TRUE;
13899 }
13900
13901 /* Save the current game to open file f and close the file */
13902 int
13903 SaveGame (FILE *f, int dummy, char *dummy2)
13904 {
13905     if (gameMode == EditPosition) EditPositionDone(TRUE);
13906     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
13907     if (appData.oldSaveStyle)
13908       return SaveGameOldStyle(f);
13909     else
13910       return SaveGamePGN(f);
13911 }
13912
13913 /* Save the current position to the given file */
13914 int
13915 SavePositionToFile (char *filename)
13916 {
13917     FILE *f;
13918     char buf[MSG_SIZ];
13919
13920     if (strcmp(filename, "-") == 0) {
13921         return SavePosition(stdout, 0, NULL);
13922     } else {
13923         f = fopen(filename, "a");
13924         if (f == NULL) {
13925             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
13926             DisplayError(buf, errno);
13927             return FALSE;
13928         } else {
13929             safeStrCpy(buf, lastMsg, MSG_SIZ);
13930             DisplayMessage(_("Waiting for access to save file"), "");
13931             flock(fileno(f), LOCK_EX); // [HGM] lock
13932             DisplayMessage(_("Saving position"), "");
13933             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
13934             SavePosition(f, 0, NULL);
13935             DisplayMessage(buf, "");
13936             return TRUE;
13937         }
13938     }
13939 }
13940
13941 /* Save the current position to the given open file and close the file */
13942 int
13943 SavePosition (FILE *f, int dummy, char *dummy2)
13944 {
13945     time_t tm;
13946     char *fen;
13947
13948     if (gameMode == EditPosition) EditPositionDone(TRUE);
13949     if (appData.oldSaveStyle) {
13950         tm = time((time_t *) NULL);
13951
13952         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
13953         PrintOpponents(f);
13954         fprintf(f, "[--------------\n");
13955         PrintPosition(f, currentMove);
13956         fprintf(f, "--------------]\n");
13957     } else {
13958         fen = PositionToFEN(currentMove, NULL, 1);
13959         fprintf(f, "%s\n", fen);
13960         free(fen);
13961     }
13962     fclose(f);
13963     return TRUE;
13964 }
13965
13966 void
13967 ReloadCmailMsgEvent (int unregister)
13968 {
13969 #if !WIN32
13970     static char *inFilename = NULL;
13971     static char *outFilename;
13972     int i;
13973     struct stat inbuf, outbuf;
13974     int status;
13975
13976     /* Any registered moves are unregistered if unregister is set, */
13977     /* i.e. invoked by the signal handler */
13978     if (unregister) {
13979         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13980             cmailMoveRegistered[i] = FALSE;
13981             if (cmailCommentList[i] != NULL) {
13982                 free(cmailCommentList[i]);
13983                 cmailCommentList[i] = NULL;
13984             }
13985         }
13986         nCmailMovesRegistered = 0;
13987     }
13988
13989     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
13990         cmailResult[i] = CMAIL_NOT_RESULT;
13991     }
13992     nCmailResults = 0;
13993
13994     if (inFilename == NULL) {
13995         /* Because the filenames are static they only get malloced once  */
13996         /* and they never get freed                                      */
13997         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
13998         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
13999
14000         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
14001         sprintf(outFilename, "%s.out", appData.cmailGameName);
14002     }
14003
14004     status = stat(outFilename, &outbuf);
14005     if (status < 0) {
14006         cmailMailedMove = FALSE;
14007     } else {
14008         status = stat(inFilename, &inbuf);
14009         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
14010     }
14011
14012     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
14013        counts the games, notes how each one terminated, etc.
14014
14015        It would be nice to remove this kludge and instead gather all
14016        the information while building the game list.  (And to keep it
14017        in the game list nodes instead of having a bunch of fixed-size
14018        parallel arrays.)  Note this will require getting each game's
14019        termination from the PGN tags, as the game list builder does
14020        not process the game moves.  --mann
14021        */
14022     cmailMsgLoaded = TRUE;
14023     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
14024
14025     /* Load first game in the file or popup game menu */
14026     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
14027
14028 #endif /* !WIN32 */
14029     return;
14030 }
14031
14032 int
14033 RegisterMove ()
14034 {
14035     FILE *f;
14036     char string[MSG_SIZ];
14037
14038     if (   cmailMailedMove
14039         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
14040         return TRUE;            /* Allow free viewing  */
14041     }
14042
14043     /* Unregister move to ensure that we don't leave RegisterMove        */
14044     /* with the move registered when the conditions for registering no   */
14045     /* longer hold                                                       */
14046     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
14047         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
14048         nCmailMovesRegistered --;
14049
14050         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
14051           {
14052               free(cmailCommentList[lastLoadGameNumber - 1]);
14053               cmailCommentList[lastLoadGameNumber - 1] = NULL;
14054           }
14055     }
14056
14057     if (cmailOldMove == -1) {
14058         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
14059         return FALSE;
14060     }
14061
14062     if (currentMove > cmailOldMove + 1) {
14063         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
14064         return FALSE;
14065     }
14066
14067     if (currentMove < cmailOldMove) {
14068         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
14069         return FALSE;
14070     }
14071
14072     if (forwardMostMove > currentMove) {
14073         /* Silently truncate extra moves */
14074         TruncateGame();
14075     }
14076
14077     if (   (currentMove == cmailOldMove + 1)
14078         || (   (currentMove == cmailOldMove)
14079             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
14080                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
14081         if (gameInfo.result != GameUnfinished) {
14082             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
14083         }
14084
14085         if (commentList[currentMove] != NULL) {
14086             cmailCommentList[lastLoadGameNumber - 1]
14087               = StrSave(commentList[currentMove]);
14088         }
14089         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
14090
14091         if (appData.debugMode)
14092           fprintf(debugFP, "Saving %s for game %d\n",
14093                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
14094
14095         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
14096
14097         f = fopen(string, "w");
14098         if (appData.oldSaveStyle) {
14099             SaveGameOldStyle(f); /* also closes the file */
14100
14101             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
14102             f = fopen(string, "w");
14103             SavePosition(f, 0, NULL); /* also closes the file */
14104         } else {
14105             fprintf(f, "{--------------\n");
14106             PrintPosition(f, currentMove);
14107             fprintf(f, "--------------}\n\n");
14108
14109             SaveGame(f, 0, NULL); /* also closes the file*/
14110         }
14111
14112         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
14113         nCmailMovesRegistered ++;
14114     } else if (nCmailGames == 1) {
14115         DisplayError(_("You have not made a move yet"), 0);
14116         return FALSE;
14117     }
14118
14119     return TRUE;
14120 }
14121
14122 void
14123 MailMoveEvent ()
14124 {
14125 #if !WIN32
14126     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
14127     FILE *commandOutput;
14128     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
14129     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
14130     int nBuffers;
14131     int i;
14132     int archived;
14133     char *arcDir;
14134
14135     if (! cmailMsgLoaded) {
14136         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
14137         return;
14138     }
14139
14140     if (nCmailGames == nCmailResults) {
14141         DisplayError(_("No unfinished games"), 0);
14142         return;
14143     }
14144
14145 #if CMAIL_PROHIBIT_REMAIL
14146     if (cmailMailedMove) {
14147       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);
14148         DisplayError(msg, 0);
14149         return;
14150     }
14151 #endif
14152
14153     if (! (cmailMailedMove || RegisterMove())) return;
14154
14155     if (   cmailMailedMove
14156         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
14157       snprintf(string, MSG_SIZ, partCommandString,
14158                appData.debugMode ? " -v" : "", appData.cmailGameName);
14159         commandOutput = popen(string, "r");
14160
14161         if (commandOutput == NULL) {
14162             DisplayError(_("Failed to invoke cmail"), 0);
14163         } else {
14164             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
14165                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
14166             }
14167             if (nBuffers > 1) {
14168                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
14169                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
14170                 nBytes = MSG_SIZ - 1;
14171             } else {
14172                 (void) memcpy(msg, buffer, nBytes);
14173             }
14174             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
14175
14176             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
14177                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
14178
14179                 archived = TRUE;
14180                 for (i = 0; i < nCmailGames; i ++) {
14181                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
14182                         archived = FALSE;
14183                     }
14184                 }
14185                 if (   archived
14186                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
14187                         != NULL)) {
14188                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
14189                            arcDir,
14190                            appData.cmailGameName,
14191                            gameInfo.date);
14192                     LoadGameFromFile(buffer, 1, buffer, FALSE);
14193                     cmailMsgLoaded = FALSE;
14194                 }
14195             }
14196
14197             DisplayInformation(msg);
14198             pclose(commandOutput);
14199         }
14200     } else {
14201         if ((*cmailMsg) != '\0') {
14202             DisplayInformation(cmailMsg);
14203         }
14204     }
14205
14206     return;
14207 #endif /* !WIN32 */
14208 }
14209
14210 char *
14211 CmailMsg ()
14212 {
14213 #if WIN32
14214     return NULL;
14215 #else
14216     int  prependComma = 0;
14217     char number[5];
14218     char string[MSG_SIZ];       /* Space for game-list */
14219     int  i;
14220
14221     if (!cmailMsgLoaded) return "";
14222
14223     if (cmailMailedMove) {
14224       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
14225     } else {
14226         /* Create a list of games left */
14227       snprintf(string, MSG_SIZ, "[");
14228         for (i = 0; i < nCmailGames; i ++) {
14229             if (! (   cmailMoveRegistered[i]
14230                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
14231                 if (prependComma) {
14232                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
14233                 } else {
14234                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
14235                     prependComma = 1;
14236                 }
14237
14238                 strcat(string, number);
14239             }
14240         }
14241         strcat(string, "]");
14242
14243         if (nCmailMovesRegistered + nCmailResults == 0) {
14244             switch (nCmailGames) {
14245               case 1:
14246                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
14247                 break;
14248
14249               case 2:
14250                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
14251                 break;
14252
14253               default:
14254                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
14255                          nCmailGames);
14256                 break;
14257             }
14258         } else {
14259             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
14260               case 1:
14261                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
14262                          string);
14263                 break;
14264
14265               case 0:
14266                 if (nCmailResults == nCmailGames) {
14267                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
14268                 } else {
14269                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
14270                 }
14271                 break;
14272
14273               default:
14274                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
14275                          string);
14276             }
14277         }
14278     }
14279     return cmailMsg;
14280 #endif /* WIN32 */
14281 }
14282
14283 void
14284 ResetGameEvent ()
14285 {
14286     if (gameMode == Training)
14287       SetTrainingModeOff();
14288
14289     Reset(TRUE, TRUE);
14290     cmailMsgLoaded = FALSE;
14291     if (appData.icsActive) {
14292       SendToICS(ics_prefix);
14293       SendToICS("refresh\n");
14294     }
14295 }
14296
14297 void
14298 ExitEvent (int status)
14299 {
14300     exiting++;
14301     if (exiting > 2) {
14302       /* Give up on clean exit */
14303       exit(status);
14304     }
14305     if (exiting > 1) {
14306       /* Keep trying for clean exit */
14307       return;
14308     }
14309
14310     if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
14311     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
14312
14313     if (telnetISR != NULL) {
14314       RemoveInputSource(telnetISR);
14315     }
14316     if (icsPR != NoProc) {
14317       DestroyChildProcess(icsPR, TRUE);
14318     }
14319
14320     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
14321     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
14322
14323     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
14324     /* make sure this other one finishes before killing it!                  */
14325     if(endingGame) { int count = 0;
14326         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
14327         while(endingGame && count++ < 10) DoSleep(1);
14328         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
14329     }
14330
14331     /* Kill off chess programs */
14332     if (first.pr != NoProc) {
14333         ExitAnalyzeMode();
14334
14335         DoSleep( appData.delayBeforeQuit );
14336         SendToProgram("quit\n", &first);
14337         DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
14338     }
14339     if (second.pr != NoProc) {
14340         DoSleep( appData.delayBeforeQuit );
14341         SendToProgram("quit\n", &second);
14342         DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
14343     }
14344     if (first.isr != NULL) {
14345         RemoveInputSource(first.isr);
14346     }
14347     if (second.isr != NULL) {
14348         RemoveInputSource(second.isr);
14349     }
14350
14351     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
14352     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
14353
14354     ShutDownFrontEnd();
14355     exit(status);
14356 }
14357
14358 void
14359 PauseEngine (ChessProgramState *cps)
14360 {
14361     SendToProgram("pause\n", cps);
14362     cps->pause = 2;
14363 }
14364
14365 void
14366 UnPauseEngine (ChessProgramState *cps)
14367 {
14368     SendToProgram("resume\n", cps);
14369     cps->pause = 1;
14370 }
14371
14372 void
14373 PauseEvent ()
14374 {
14375     if (appData.debugMode)
14376         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
14377     if (pausing) {
14378         pausing = FALSE;
14379         ModeHighlight();
14380         if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
14381             StartClocks();
14382             if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
14383                 if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
14384                 else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
14385             }
14386             if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
14387             HandleMachineMove(stashedInputMove, stalledEngine);
14388             stalledEngine = NULL;
14389             return;
14390         }
14391         if (gameMode == MachinePlaysWhite ||
14392             gameMode == TwoMachinesPlay   ||
14393             gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
14394             if(first.pause)  UnPauseEngine(&first);
14395             else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
14396             if(second.pause) UnPauseEngine(&second);
14397             else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
14398             StartClocks();
14399         } else {
14400             DisplayBothClocks();
14401         }
14402         if (gameMode == PlayFromGameFile) {
14403             if (appData.timeDelay >= 0)
14404                 AutoPlayGameLoop();
14405         } else if (gameMode == IcsExamining && pauseExamInvalid) {
14406             Reset(FALSE, TRUE);
14407             SendToICS(ics_prefix);
14408             SendToICS("refresh\n");
14409         } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
14410             ForwardInner(forwardMostMove);
14411         }
14412         pauseExamInvalid = FALSE;
14413     } else {
14414         switch (gameMode) {
14415           default:
14416             return;
14417           case IcsExamining:
14418             pauseExamForwardMostMove = forwardMostMove;
14419             pauseExamInvalid = FALSE;
14420             /* fall through */
14421           case IcsObserving:
14422           case IcsPlayingWhite:
14423           case IcsPlayingBlack:
14424             pausing = TRUE;
14425             ModeHighlight();
14426             return;
14427           case PlayFromGameFile:
14428             (void) StopLoadGameTimer();
14429             pausing = TRUE;
14430             ModeHighlight();
14431             break;
14432           case BeginningOfGame:
14433             if (appData.icsActive) return;
14434             /* else fall through */
14435           case MachinePlaysWhite:
14436           case MachinePlaysBlack:
14437           case TwoMachinesPlay:
14438             if (forwardMostMove == 0)
14439               return;           /* don't pause if no one has moved */
14440             if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
14441                 ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
14442                 if(onMove->pause) {           // thinking engine can be paused
14443                     PauseEngine(onMove);      // do it
14444                     if(onMove->other->pause)  // pondering opponent can always be paused immediately
14445                         PauseEngine(onMove->other);
14446                     else
14447                         SendToProgram("easy\n", onMove->other);
14448                     StopClocks();
14449                 } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
14450             } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
14451                 if(first.pause) {
14452                     PauseEngine(&first);
14453                     StopClocks();
14454                 } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
14455             } else { // human on move, pause pondering by either method
14456                 if(first.pause)
14457                     PauseEngine(&first);
14458                 else if(appData.ponderNextMove)
14459                     SendToProgram("easy\n", &first);
14460                 StopClocks();
14461             }
14462             // if no immediate pausing is possible, wait for engine to move, and stop clocks then
14463           case AnalyzeMode:
14464             pausing = TRUE;
14465             ModeHighlight();
14466             break;
14467         }
14468     }
14469 }
14470
14471 void
14472 EditCommentEvent ()
14473 {
14474     char title[MSG_SIZ];
14475
14476     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
14477       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
14478     } else {
14479       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
14480                WhiteOnMove(currentMove - 1) ? " " : ".. ",
14481                parseList[currentMove - 1]);
14482     }
14483
14484     EditCommentPopUp(currentMove, title, commentList[currentMove]);
14485 }
14486
14487
14488 void
14489 EditTagsEvent ()
14490 {
14491     char *tags = PGNTags(&gameInfo);
14492     bookUp = FALSE;
14493     EditTagsPopUp(tags, NULL);
14494     free(tags);
14495 }
14496
14497 void
14498 ToggleSecond ()
14499 {
14500   if(second.analyzing) {
14501     SendToProgram("exit\n", &second);
14502     second.analyzing = FALSE;
14503   } else {
14504     if (second.pr == NoProc) StartChessProgram(&second);
14505     InitChessProgram(&second, FALSE);
14506     FeedMovesToProgram(&second, currentMove);
14507
14508     SendToProgram("analyze\n", &second);
14509     second.analyzing = TRUE;
14510   }
14511 }
14512
14513 /* Toggle ShowThinking */
14514 void
14515 ToggleShowThinking()
14516 {
14517   appData.showThinking = !appData.showThinking;
14518   ShowThinkingEvent();
14519 }
14520
14521 int
14522 AnalyzeModeEvent ()
14523 {
14524     char buf[MSG_SIZ];
14525
14526     if (!first.analysisSupport) {
14527       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14528       DisplayError(buf, 0);
14529       return 0;
14530     }
14531     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
14532     if (appData.icsActive) {
14533         if (gameMode != IcsObserving) {
14534           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
14535             DisplayError(buf, 0);
14536             /* secure check */
14537             if (appData.icsEngineAnalyze) {
14538                 if (appData.debugMode)
14539                     fprintf(debugFP, "Found unexpected active ICS engine analyze \n");
14540                 ExitAnalyzeMode();
14541                 ModeHighlight();
14542             }
14543             return 0;
14544         }
14545         /* if enable, user wants to disable icsEngineAnalyze */
14546         if (appData.icsEngineAnalyze) {
14547                 ExitAnalyzeMode();
14548                 ModeHighlight();
14549                 return 0;
14550         }
14551         appData.icsEngineAnalyze = TRUE;
14552         if (appData.debugMode)
14553             fprintf(debugFP, "ICS engine analyze starting... \n");
14554     }
14555
14556     if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
14557     if (appData.noChessProgram || gameMode == AnalyzeMode)
14558       return 0;
14559
14560     if (gameMode != AnalyzeFile) {
14561         if (!appData.icsEngineAnalyze) {
14562                EditGameEvent();
14563                if (gameMode != EditGame) return 0;
14564         }
14565         if (!appData.showThinking) ToggleShowThinking();
14566         ResurrectChessProgram();
14567         SendToProgram("analyze\n", &first);
14568         first.analyzing = TRUE;
14569         /*first.maybeThinking = TRUE;*/
14570         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14571         EngineOutputPopUp();
14572     }
14573     if (!appData.icsEngineAnalyze) {
14574         gameMode = AnalyzeMode;
14575         ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
14576     }
14577     pausing = FALSE;
14578     ModeHighlight();
14579     SetGameInfo();
14580
14581     StartAnalysisClock();
14582     GetTimeMark(&lastNodeCountTime);
14583     lastNodeCount = 0;
14584     return 1;
14585 }
14586
14587 void
14588 AnalyzeFileEvent ()
14589 {
14590     if (appData.noChessProgram || gameMode == AnalyzeFile)
14591       return;
14592
14593     if (!first.analysisSupport) {
14594       char buf[MSG_SIZ];
14595       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
14596       DisplayError(buf, 0);
14597       return;
14598     }
14599
14600     if (gameMode != AnalyzeMode) {
14601         keepInfo = 1; // mere annotating should not alter PGN tags
14602         EditGameEvent();
14603         keepInfo = 0;
14604         if (gameMode != EditGame) return;
14605         if (!appData.showThinking) ToggleShowThinking();
14606         ResurrectChessProgram();
14607         SendToProgram("analyze\n", &first);
14608         first.analyzing = TRUE;
14609         /*first.maybeThinking = TRUE;*/
14610         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14611         EngineOutputPopUp();
14612     }
14613     gameMode = AnalyzeFile;
14614     pausing = FALSE;
14615     ModeHighlight();
14616
14617     StartAnalysisClock();
14618     GetTimeMark(&lastNodeCountTime);
14619     lastNodeCount = 0;
14620     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
14621     AnalysisPeriodicEvent(1);
14622 }
14623
14624 void
14625 MachineWhiteEvent ()
14626 {
14627     char buf[MSG_SIZ];
14628     char *bookHit = NULL;
14629
14630     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
14631       return;
14632
14633
14634     if (gameMode == PlayFromGameFile ||
14635         gameMode == TwoMachinesPlay  ||
14636         gameMode == Training         ||
14637         gameMode == AnalyzeMode      ||
14638         gameMode == EndOfGame)
14639         EditGameEvent();
14640
14641     if (gameMode == EditPosition)
14642         EditPositionDone(TRUE);
14643
14644     if (!WhiteOnMove(currentMove)) {
14645         DisplayError(_("It is not White's turn"), 0);
14646         return;
14647     }
14648
14649     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14650       ExitAnalyzeMode();
14651
14652     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14653         gameMode == AnalyzeFile)
14654         TruncateGame();
14655
14656     ResurrectChessProgram();    /* in case it isn't running */
14657     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
14658         gameMode = MachinePlaysWhite;
14659         ResetClocks();
14660     } else
14661     gameMode = MachinePlaysWhite;
14662     pausing = FALSE;
14663     ModeHighlight();
14664     SetGameInfo();
14665     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14666     DisplayTitle(buf);
14667     if (first.sendName) {
14668       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
14669       SendToProgram(buf, &first);
14670     }
14671     if (first.sendTime) {
14672       if (first.useColors) {
14673         SendToProgram("black\n", &first); /*gnu kludge*/
14674       }
14675       SendTimeRemaining(&first, TRUE);
14676     }
14677     if (first.useColors) {
14678       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
14679     }
14680     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14681     SetMachineThinkingEnables();
14682     first.maybeThinking = TRUE;
14683     StartClocks();
14684     firstMove = FALSE;
14685
14686     if (appData.autoFlipView && !flipView) {
14687       flipView = !flipView;
14688       DrawPosition(FALSE, NULL);
14689       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14690     }
14691
14692     if(bookHit) { // [HGM] book: simulate book reply
14693         static char bookMove[MSG_SIZ]; // a bit generous?
14694
14695         programStats.nodes = programStats.depth = programStats.time =
14696         programStats.score = programStats.got_only_move = 0;
14697         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14698
14699         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14700         strcat(bookMove, bookHit);
14701         HandleMachineMove(bookMove, &first);
14702     }
14703 }
14704
14705 void
14706 MachineBlackEvent ()
14707 {
14708   char buf[MSG_SIZ];
14709   char *bookHit = NULL;
14710
14711     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
14712         return;
14713
14714
14715     if (gameMode == PlayFromGameFile ||
14716         gameMode == TwoMachinesPlay  ||
14717         gameMode == Training         ||
14718         gameMode == AnalyzeMode      ||
14719         gameMode == EndOfGame)
14720         EditGameEvent();
14721
14722     if (gameMode == EditPosition)
14723         EditPositionDone(TRUE);
14724
14725     if (WhiteOnMove(currentMove)) {
14726         DisplayError(_("It is not Black's turn"), 0);
14727         return;
14728     }
14729
14730     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
14731       ExitAnalyzeMode();
14732
14733     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14734         gameMode == AnalyzeFile)
14735         TruncateGame();
14736
14737     ResurrectChessProgram();    /* in case it isn't running */
14738     gameMode = MachinePlaysBlack;
14739     pausing = FALSE;
14740     ModeHighlight();
14741     SetGameInfo();
14742     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14743     DisplayTitle(buf);
14744     if (first.sendName) {
14745       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
14746       SendToProgram(buf, &first);
14747     }
14748     if (first.sendTime) {
14749       if (first.useColors) {
14750         SendToProgram("white\n", &first); /*gnu kludge*/
14751       }
14752       SendTimeRemaining(&first, FALSE);
14753     }
14754     if (first.useColors) {
14755       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
14756     }
14757     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
14758     SetMachineThinkingEnables();
14759     first.maybeThinking = TRUE;
14760     StartClocks();
14761
14762     if (appData.autoFlipView && flipView) {
14763       flipView = !flipView;
14764       DrawPosition(FALSE, NULL);
14765       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
14766     }
14767     if(bookHit) { // [HGM] book: simulate book reply
14768         static char bookMove[MSG_SIZ]; // a bit generous?
14769
14770         programStats.nodes = programStats.depth = programStats.time =
14771         programStats.score = programStats.got_only_move = 0;
14772         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14773
14774         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14775         strcat(bookMove, bookHit);
14776         HandleMachineMove(bookMove, &first);
14777     }
14778 }
14779
14780
14781 void
14782 DisplayTwoMachinesTitle ()
14783 {
14784     char buf[MSG_SIZ];
14785     if (appData.matchGames > 0) {
14786         if(appData.tourneyFile[0]) {
14787           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
14788                    gameInfo.white, _("vs."), gameInfo.black,
14789                    nextGame+1, appData.matchGames+1,
14790                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
14791         } else
14792         if (first.twoMachinesColor[0] == 'w') {
14793           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14794                    gameInfo.white, _("vs."),  gameInfo.black,
14795                    first.matchWins, second.matchWins,
14796                    matchGame - 1 - (first.matchWins + second.matchWins));
14797         } else {
14798           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
14799                    gameInfo.white, _("vs."), gameInfo.black,
14800                    second.matchWins, first.matchWins,
14801                    matchGame - 1 - (first.matchWins + second.matchWins));
14802         }
14803     } else {
14804       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
14805     }
14806     DisplayTitle(buf);
14807 }
14808
14809 void
14810 SettingsMenuIfReady ()
14811 {
14812   if (second.lastPing != second.lastPong) {
14813     DisplayMessage("", _("Waiting for second chess program"));
14814     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
14815     return;
14816   }
14817   ThawUI();
14818   DisplayMessage("", "");
14819   SettingsPopUp(&second);
14820 }
14821
14822 int
14823 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
14824 {
14825     char buf[MSG_SIZ];
14826     if (cps->pr == NoProc) {
14827         StartChessProgram(cps);
14828         if (cps->protocolVersion == 1) {
14829           retry();
14830           ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry'
14831         } else {
14832           /* kludge: allow timeout for initial "feature" command */
14833           if(retry != TwoMachinesEventIfReady) FreezeUI();
14834           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which));
14835           DisplayMessage("", buf);
14836           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
14837         }
14838         return 1;
14839     }
14840     return 0;
14841 }
14842
14843 void
14844 TwoMachinesEvent P((void))
14845 {
14846     int i;
14847     char buf[MSG_SIZ];
14848     ChessProgramState *onmove;
14849     char *bookHit = NULL;
14850     static int stalling = 0;
14851     TimeMark now;
14852     long wait;
14853
14854     if (appData.noChessProgram) return;
14855
14856     switch (gameMode) {
14857       case TwoMachinesPlay:
14858         return;
14859       case MachinePlaysWhite:
14860       case MachinePlaysBlack:
14861         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14862             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
14863             return;
14864         }
14865         /* fall through */
14866       case BeginningOfGame:
14867       case PlayFromGameFile:
14868       case EndOfGame:
14869         EditGameEvent();
14870         if (gameMode != EditGame) return;
14871         break;
14872       case EditPosition:
14873         EditPositionDone(TRUE);
14874         break;
14875       case AnalyzeMode:
14876       case AnalyzeFile:
14877         ExitAnalyzeMode();
14878         break;
14879       case EditGame:
14880       default:
14881         break;
14882     }
14883
14884 //    forwardMostMove = currentMove;
14885     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
14886     startingEngine = TRUE;
14887
14888     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
14889
14890     if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout
14891     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
14892       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14893       return;
14894     }
14895     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
14896
14897     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
14898                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
14899         startingEngine = matchMode = FALSE;
14900         DisplayError("second engine does not play this", 0);
14901         gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
14902         EditGameEvent(); // switch back to EditGame mode
14903         return;
14904     }
14905
14906     if(!stalling) {
14907       InitChessProgram(&second, FALSE); // unbalances ping of second engine
14908       SendToProgram("force\n", &second);
14909       stalling = 1;
14910       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
14911       return;
14912     }
14913     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
14914     if(appData.matchPause>10000 || appData.matchPause<10)
14915                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
14916     wait = SubtractTimeMarks(&now, &pauseStart);
14917     if(wait < appData.matchPause) {
14918         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
14919         return;
14920     }
14921     // we are now committed to starting the game
14922     stalling = 0;
14923     DisplayMessage("", "");
14924     if (startedFromSetupPosition) {
14925         SendBoard(&second, backwardMostMove);
14926     if (appData.debugMode) {
14927         fprintf(debugFP, "Two Machines\n");
14928     }
14929     }
14930     for (i = backwardMostMove; i < forwardMostMove; i++) {
14931         SendMoveToProgram(i, &second);
14932     }
14933
14934     gameMode = TwoMachinesPlay;
14935     pausing = startingEngine = FALSE;
14936     ModeHighlight(); // [HGM] logo: this triggers display update of logos
14937     SetGameInfo();
14938     DisplayTwoMachinesTitle();
14939     firstMove = TRUE;
14940     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
14941         onmove = &first;
14942     } else {
14943         onmove = &second;
14944     }
14945     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
14946     SendToProgram(first.computerString, &first);
14947     if (first.sendName) {
14948       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
14949       SendToProgram(buf, &first);
14950     }
14951     SendToProgram(second.computerString, &second);
14952     if (second.sendName) {
14953       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
14954       SendToProgram(buf, &second);
14955     }
14956
14957     ResetClocks();
14958     if (!first.sendTime || !second.sendTime) {
14959         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
14960         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
14961     }
14962     if (onmove->sendTime) {
14963       if (onmove->useColors) {
14964         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
14965       }
14966       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
14967     }
14968     if (onmove->useColors) {
14969       SendToProgram(onmove->twoMachinesColor, onmove);
14970     }
14971     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
14972 //    SendToProgram("go\n", onmove);
14973     onmove->maybeThinking = TRUE;
14974     SetMachineThinkingEnables();
14975
14976     StartClocks();
14977
14978     if(bookHit) { // [HGM] book: simulate book reply
14979         static char bookMove[MSG_SIZ]; // a bit generous?
14980
14981         programStats.nodes = programStats.depth = programStats.time =
14982         programStats.score = programStats.got_only_move = 0;
14983         sprintf(programStats.movelist, "%s (xbook)", bookHit);
14984
14985         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
14986         strcat(bookMove, bookHit);
14987         savedMessage = bookMove; // args for deferred call
14988         savedState = onmove;
14989         ScheduleDelayedEvent(DeferredBookMove, 1);
14990     }
14991 }
14992
14993 void
14994 TrainingEvent ()
14995 {
14996     if (gameMode == Training) {
14997       SetTrainingModeOff();
14998       gameMode = PlayFromGameFile;
14999       DisplayMessage("", _("Training mode off"));
15000     } else {
15001       gameMode = Training;
15002       animateTraining = appData.animate;
15003
15004       /* make sure we are not already at the end of the game */
15005       if (currentMove < forwardMostMove) {
15006         SetTrainingModeOn();
15007         DisplayMessage("", _("Training mode on"));
15008       } else {
15009         gameMode = PlayFromGameFile;
15010         DisplayError(_("Already at end of game"), 0);
15011       }
15012     }
15013     ModeHighlight();
15014 }
15015
15016 void
15017 IcsClientEvent ()
15018 {
15019     if (!appData.icsActive) return;
15020     switch (gameMode) {
15021       case IcsPlayingWhite:
15022       case IcsPlayingBlack:
15023       case IcsObserving:
15024       case IcsIdle:
15025       case BeginningOfGame:
15026       case IcsExamining:
15027         return;
15028
15029       case EditGame:
15030         break;
15031
15032       case EditPosition:
15033         EditPositionDone(TRUE);
15034         break;
15035
15036       case AnalyzeMode:
15037       case AnalyzeFile:
15038         ExitAnalyzeMode();
15039         break;
15040
15041       default:
15042         EditGameEvent();
15043         break;
15044     }
15045
15046     gameMode = IcsIdle;
15047     ModeHighlight();
15048     return;
15049 }
15050
15051 void
15052 EditGameEvent ()
15053 {
15054     int i;
15055
15056     switch (gameMode) {
15057       case Training:
15058         SetTrainingModeOff();
15059         break;
15060       case MachinePlaysWhite:
15061       case MachinePlaysBlack:
15062       case BeginningOfGame:
15063         SendToProgram("force\n", &first);
15064         if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
15065             if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
15066                 char buf[MSG_SIZ];
15067                 abortEngineThink = TRUE;
15068                 snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
15069                 SendToProgram(buf, &first);
15070                 DisplayMessage("Aborting engine think", "");
15071                 FreezeUI();
15072             }
15073         }
15074         SetUserThinkingEnables();
15075         break;
15076       case PlayFromGameFile:
15077         (void) StopLoadGameTimer();
15078         if (gameFileFP != NULL) {
15079             gameFileFP = NULL;
15080         }
15081         break;
15082       case EditPosition:
15083         EditPositionDone(TRUE);
15084         break;
15085       case AnalyzeMode:
15086       case AnalyzeFile:
15087         ExitAnalyzeMode();
15088         SendToProgram("force\n", &first);
15089         break;
15090       case TwoMachinesPlay:
15091         GameEnds(EndOfFile, NULL, GE_PLAYER);
15092         ResurrectChessProgram();
15093         SetUserThinkingEnables();
15094         break;
15095       case EndOfGame:
15096         ResurrectChessProgram();
15097         break;
15098       case IcsPlayingBlack:
15099       case IcsPlayingWhite:
15100         DisplayError(_("Warning: You are still playing a game"), 0);
15101         break;
15102       case IcsObserving:
15103         DisplayError(_("Warning: You are still observing a game"), 0);
15104         break;
15105       case IcsExamining:
15106         DisplayError(_("Warning: You are still examining a game"), 0);
15107         break;
15108       case IcsIdle:
15109         break;
15110       case EditGame:
15111       default:
15112         return;
15113     }
15114
15115     pausing = FALSE;
15116     StopClocks();
15117     first.offeredDraw = second.offeredDraw = 0;
15118
15119     if (gameMode == PlayFromGameFile) {
15120         whiteTimeRemaining = timeRemaining[0][currentMove];
15121         blackTimeRemaining = timeRemaining[1][currentMove];
15122         DisplayTitle("");
15123     }
15124
15125     if (gameMode == MachinePlaysWhite ||
15126         gameMode == MachinePlaysBlack ||
15127         gameMode == TwoMachinesPlay ||
15128         gameMode == EndOfGame) {
15129         i = forwardMostMove;
15130         while (i > currentMove) {
15131             SendToProgram("undo\n", &first);
15132             i--;
15133         }
15134         if(!adjustedClock) {
15135         whiteTimeRemaining = timeRemaining[0][currentMove];
15136         blackTimeRemaining = timeRemaining[1][currentMove];
15137         DisplayBothClocks();
15138         }
15139         if (whiteFlag || blackFlag) {
15140             whiteFlag = blackFlag = 0;
15141         }
15142         DisplayTitle("");
15143     }
15144
15145     gameMode = EditGame;
15146     ModeHighlight();
15147     SetGameInfo();
15148 }
15149
15150
15151 void
15152 EditPositionEvent ()
15153 {
15154     if (gameMode == EditPosition) {
15155         EditGameEvent();
15156         return;
15157     }
15158
15159     EditGameEvent();
15160     if (gameMode != EditGame) return;
15161
15162     gameMode = EditPosition;
15163     ModeHighlight();
15164     SetGameInfo();
15165     if (currentMove > 0)
15166       CopyBoard(boards[0], boards[currentMove]);
15167
15168     blackPlaysFirst = !WhiteOnMove(currentMove);
15169     ResetClocks();
15170     currentMove = forwardMostMove = backwardMostMove = 0;
15171     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15172     DisplayMove(-1);
15173     if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), "");
15174 }
15175
15176 void
15177 ExitAnalyzeMode ()
15178 {
15179     /* [DM] icsEngineAnalyze - possible call from other functions */
15180     if (appData.icsEngineAnalyze) {
15181         appData.icsEngineAnalyze = FALSE;
15182
15183         DisplayMessage("",_("Close ICS engine analyze..."));
15184     }
15185     if (first.analysisSupport && first.analyzing) {
15186       SendToBoth("exit\n");
15187       first.analyzing = second.analyzing = FALSE;
15188     }
15189     thinkOutput[0] = NULLCHAR;
15190 }
15191
15192 void
15193 EditPositionDone (Boolean fakeRights)
15194 {
15195     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
15196
15197     startedFromSetupPosition = TRUE;
15198     InitChessProgram(&first, FALSE);
15199     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
15200       boards[0][EP_STATUS] = EP_NONE;
15201       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
15202       if(boards[0][0][BOARD_WIDTH>>1] == king) {
15203         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
15204         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
15205       } else boards[0][CASTLING][2] = NoRights;
15206       if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
15207         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
15208         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
15209       } else boards[0][CASTLING][5] = NoRights;
15210       if(gameInfo.variant == VariantSChess) {
15211         int i;
15212         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
15213           boards[0][VIRGIN][i] = 0;
15214           if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
15215           if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
15216         }
15217       }
15218     }
15219     SendToProgram("force\n", &first);
15220     if (blackPlaysFirst) {
15221         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
15222         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
15223         currentMove = forwardMostMove = backwardMostMove = 1;
15224         CopyBoard(boards[1], boards[0]);
15225     } else {
15226         currentMove = forwardMostMove = backwardMostMove = 0;
15227     }
15228     SendBoard(&first, forwardMostMove);
15229     if (appData.debugMode) {
15230         fprintf(debugFP, "EditPosDone\n");
15231     }
15232     DisplayTitle("");
15233     DisplayMessage("", "");
15234     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
15235     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
15236     gameMode = EditGame;
15237     ModeHighlight();
15238     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
15239     ClearHighlights(); /* [AS] */
15240 }
15241
15242 /* Pause for `ms' milliseconds */
15243 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15244 void
15245 TimeDelay (long ms)
15246 {
15247     TimeMark m1, m2;
15248
15249     GetTimeMark(&m1);
15250     do {
15251         GetTimeMark(&m2);
15252     } while (SubtractTimeMarks(&m2, &m1) < ms);
15253 }
15254
15255 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
15256 void
15257 SendMultiLineToICS (char *buf)
15258 {
15259     char temp[MSG_SIZ+1], *p;
15260     int len;
15261
15262     len = strlen(buf);
15263     if (len > MSG_SIZ)
15264       len = MSG_SIZ;
15265
15266     strncpy(temp, buf, len);
15267     temp[len] = 0;
15268
15269     p = temp;
15270     while (*p) {
15271         if (*p == '\n' || *p == '\r')
15272           *p = ' ';
15273         ++p;
15274     }
15275
15276     strcat(temp, "\n");
15277     SendToICS(temp);
15278     SendToPlayer(temp, strlen(temp));
15279 }
15280
15281 void
15282 SetWhiteToPlayEvent ()
15283 {
15284     if (gameMode == EditPosition) {
15285         blackPlaysFirst = FALSE;
15286         DisplayBothClocks();    /* works because currentMove is 0 */
15287     } else if (gameMode == IcsExamining) {
15288         SendToICS(ics_prefix);
15289         SendToICS("tomove white\n");
15290     }
15291 }
15292
15293 void
15294 SetBlackToPlayEvent ()
15295 {
15296     if (gameMode == EditPosition) {
15297         blackPlaysFirst = TRUE;
15298         currentMove = 1;        /* kludge */
15299         DisplayBothClocks();
15300         currentMove = 0;
15301     } else if (gameMode == IcsExamining) {
15302         SendToICS(ics_prefix);
15303         SendToICS("tomove black\n");
15304     }
15305 }
15306
15307 void
15308 EditPositionMenuEvent (ChessSquare selection, int x, int y)
15309 {
15310     char buf[MSG_SIZ];
15311     ChessSquare piece = boards[0][y][x];
15312     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
15313     static int lastVariant;
15314
15315     if (gameMode != EditPosition && gameMode != IcsExamining) return;
15316
15317     switch (selection) {
15318       case ClearBoard:
15319         fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15320         MarkTargetSquares(1);
15321         CopyBoard(currentBoard, boards[0]);
15322         CopyBoard(menuBoard, initialPosition);
15323         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
15324             SendToICS(ics_prefix);
15325             SendToICS("bsetup clear\n");
15326         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
15327             SendToICS(ics_prefix);
15328             SendToICS("clearboard\n");
15329         } else {
15330             int nonEmpty = 0;
15331             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
15332                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
15333                 for (y = 0; y < BOARD_HEIGHT; y++) {
15334                     if (gameMode == IcsExamining) {
15335                         if (boards[currentMove][y][x] != EmptySquare) {
15336                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
15337                                     AAA + x, ONE + y);
15338                             SendToICS(buf);
15339                         }
15340                     } else if(boards[0][y][x] != DarkSquare) {
15341                         if(boards[0][y][x] != p) nonEmpty++;
15342                         boards[0][y][x] = p;
15343                     }
15344                 }
15345             }
15346             if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
15347                 int r;
15348                 for(r = 0; r < BOARD_HEIGHT; r++) {
15349                   for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
15350                     ChessSquare p = menuBoard[r][x];
15351                     for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
15352                   }
15353                 }
15354                 DisplayMessage("Clicking clock again restores position", "");
15355                 if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
15356                 if(!nonEmpty) { // asked to clear an empty board
15357                     CopyBoard(boards[0], menuBoard);
15358                 } else
15359                 if(CompareBoards(currentBoard, menuBoard)) { // asked to clear an empty board
15360                     CopyBoard(boards[0], initialPosition);
15361                 } else
15362                 if(CompareBoards(currentBoard, initialPosition) && !CompareBoards(currentBoard, erasedBoard)
15363                                                                  && !CompareBoards(nullBoard, erasedBoard)) {
15364                     CopyBoard(boards[0], erasedBoard);
15365                 } else
15366                     CopyBoard(erasedBoard, currentBoard);
15367
15368             }
15369         }
15370         if (gameMode == EditPosition) {
15371             DrawPosition(FALSE, boards[0]);
15372         }
15373         break;
15374
15375       case WhitePlay:
15376         SetWhiteToPlayEvent();
15377         break;
15378
15379       case BlackPlay:
15380         SetBlackToPlayEvent();
15381         break;
15382
15383       case EmptySquare:
15384         if (gameMode == IcsExamining) {
15385             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15386             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
15387             SendToICS(buf);
15388         } else {
15389             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15390                 if(x == BOARD_LEFT-2) {
15391                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
15392                     boards[0][y][1] = 0;
15393                 } else
15394                 if(x == BOARD_RGHT+1) {
15395                     if(y >= gameInfo.holdingsSize) break;
15396                     boards[0][y][BOARD_WIDTH-2] = 0;
15397                 } else break;
15398             }
15399             boards[0][y][x] = EmptySquare;
15400             DrawPosition(FALSE, boards[0]);
15401         }
15402         break;
15403
15404       case PromotePiece:
15405         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
15406            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
15407             selection = (ChessSquare) (PROMOTED(piece));
15408         } else if(piece == EmptySquare) selection = WhiteSilver;
15409         else selection = (ChessSquare)((int)piece - 1);
15410         goto defaultlabel;
15411
15412       case DemotePiece:
15413         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
15414            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
15415             selection = (ChessSquare) (DEMOTED(piece));
15416         } else if(piece == EmptySquare) selection = BlackSilver;
15417         else selection = (ChessSquare)((int)piece + 1);
15418         goto defaultlabel;
15419
15420       case WhiteQueen:
15421       case BlackQueen:
15422         if(gameInfo.variant == VariantShatranj ||
15423            gameInfo.variant == VariantXiangqi  ||
15424            gameInfo.variant == VariantCourier  ||
15425            gameInfo.variant == VariantASEAN    ||
15426            gameInfo.variant == VariantMakruk     )
15427             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
15428         goto defaultlabel;
15429
15430       case WhiteKing:
15431       case BlackKing:
15432         if(gameInfo.variant == VariantXiangqi)
15433             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
15434         if(gameInfo.variant == VariantKnightmate)
15435             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
15436       default:
15437         defaultlabel:
15438         if (gameMode == IcsExamining) {
15439             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
15440             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
15441                      PieceToChar(selection), AAA + x, ONE + y);
15442             SendToICS(buf);
15443         } else {
15444             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
15445                 int n;
15446                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
15447                     n = PieceToNumber(selection - BlackPawn);
15448                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
15449                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
15450                     boards[0][BOARD_HEIGHT-1-n][1]++;
15451                 } else
15452                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
15453                     n = PieceToNumber(selection);
15454                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
15455                     boards[0][n][BOARD_WIDTH-1] = selection;
15456                     boards[0][n][BOARD_WIDTH-2]++;
15457                 }
15458             } else
15459             boards[0][y][x] = selection;
15460             DrawPosition(TRUE, boards[0]);
15461             ClearHighlights();
15462             fromX = fromY = -1;
15463         }
15464         break;
15465     }
15466 }
15467
15468
15469 void
15470 DropMenuEvent (ChessSquare selection, int x, int y)
15471 {
15472     ChessMove moveType;
15473
15474     switch (gameMode) {
15475       case IcsPlayingWhite:
15476       case MachinePlaysBlack:
15477         if (!WhiteOnMove(currentMove)) {
15478             DisplayMoveError(_("It is Black's turn"));
15479             return;
15480         }
15481         moveType = WhiteDrop;
15482         break;
15483       case IcsPlayingBlack:
15484       case MachinePlaysWhite:
15485         if (WhiteOnMove(currentMove)) {
15486             DisplayMoveError(_("It is White's turn"));
15487             return;
15488         }
15489         moveType = BlackDrop;
15490         break;
15491       case EditGame:
15492         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
15493         break;
15494       default:
15495         return;
15496     }
15497
15498     if (moveType == BlackDrop && selection < BlackPawn) {
15499       selection = (ChessSquare) ((int) selection
15500                                  + (int) BlackPawn - (int) WhitePawn);
15501     }
15502     if (boards[currentMove][y][x] != EmptySquare) {
15503         DisplayMoveError(_("That square is occupied"));
15504         return;
15505     }
15506
15507     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
15508 }
15509
15510 void
15511 AcceptEvent ()
15512 {
15513     /* Accept a pending offer of any kind from opponent */
15514
15515     if (appData.icsActive) {
15516         SendToICS(ics_prefix);
15517         SendToICS("accept\n");
15518     } else if (cmailMsgLoaded) {
15519         if (currentMove == cmailOldMove &&
15520             commentList[cmailOldMove] != NULL &&
15521             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15522                    "Black offers a draw" : "White offers a draw")) {
15523             TruncateGame();
15524             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15525             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15526         } else {
15527             DisplayError(_("There is no pending offer on this move"), 0);
15528             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15529         }
15530     } else {
15531         /* Not used for offers from chess program */
15532     }
15533 }
15534
15535 void
15536 DeclineEvent ()
15537 {
15538     /* Decline a pending offer of any kind from opponent */
15539
15540     if (appData.icsActive) {
15541         SendToICS(ics_prefix);
15542         SendToICS("decline\n");
15543     } else if (cmailMsgLoaded) {
15544         if (currentMove == cmailOldMove &&
15545             commentList[cmailOldMove] != NULL &&
15546             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15547                    "Black offers a draw" : "White offers a draw")) {
15548 #ifdef NOTDEF
15549             AppendComment(cmailOldMove, "Draw declined", TRUE);
15550             DisplayComment(cmailOldMove - 1, "Draw declined");
15551 #endif /*NOTDEF*/
15552         } else {
15553             DisplayError(_("There is no pending offer on this move"), 0);
15554         }
15555     } else {
15556         /* Not used for offers from chess program */
15557     }
15558 }
15559
15560 void
15561 RematchEvent ()
15562 {
15563     /* Issue ICS rematch command */
15564     if (appData.icsActive) {
15565         SendToICS(ics_prefix);
15566         SendToICS("rematch\n");
15567     }
15568 }
15569
15570 void
15571 CallFlagEvent ()
15572 {
15573     /* Call your opponent's flag (claim a win on time) */
15574     if (appData.icsActive) {
15575         SendToICS(ics_prefix);
15576         SendToICS("flag\n");
15577     } else {
15578         switch (gameMode) {
15579           default:
15580             return;
15581           case MachinePlaysWhite:
15582             if (whiteFlag) {
15583                 if (blackFlag)
15584                   GameEnds(GameIsDrawn, "Both players ran out of time",
15585                            GE_PLAYER);
15586                 else
15587                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
15588             } else {
15589                 DisplayError(_("Your opponent is not out of time"), 0);
15590             }
15591             break;
15592           case MachinePlaysBlack:
15593             if (blackFlag) {
15594                 if (whiteFlag)
15595                   GameEnds(GameIsDrawn, "Both players ran out of time",
15596                            GE_PLAYER);
15597                 else
15598                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
15599             } else {
15600                 DisplayError(_("Your opponent is not out of time"), 0);
15601             }
15602             break;
15603         }
15604     }
15605 }
15606
15607 void
15608 ClockClick (int which)
15609 {       // [HGM] code moved to back-end from winboard.c
15610         if(which) { // black clock
15611           if (gameMode == EditPosition || gameMode == IcsExamining) {
15612             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15613             SetBlackToPlayEvent();
15614           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15615                       gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
15616           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
15617           } else if (shiftKey) {
15618             AdjustClock(which, -1);
15619           } else if (gameMode == IcsPlayingWhite ||
15620                      gameMode == MachinePlaysBlack) {
15621             CallFlagEvent();
15622           }
15623         } else { // white clock
15624           if (gameMode == EditPosition || gameMode == IcsExamining) {
15625             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
15626             SetWhiteToPlayEvent();
15627           } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
15628                       gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
15629           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
15630           } else if (shiftKey) {
15631             AdjustClock(which, -1);
15632           } else if (gameMode == IcsPlayingBlack ||
15633                    gameMode == MachinePlaysWhite) {
15634             CallFlagEvent();
15635           }
15636         }
15637 }
15638
15639 void
15640 DrawEvent ()
15641 {
15642     /* Offer draw or accept pending draw offer from opponent */
15643
15644     if (appData.icsActive) {
15645         /* Note: tournament rules require draw offers to be
15646            made after you make your move but before you punch
15647            your clock.  Currently ICS doesn't let you do that;
15648            instead, you immediately punch your clock after making
15649            a move, but you can offer a draw at any time. */
15650
15651         SendToICS(ics_prefix);
15652         SendToICS("draw\n");
15653         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
15654     } else if (cmailMsgLoaded) {
15655         if (currentMove == cmailOldMove &&
15656             commentList[cmailOldMove] != NULL &&
15657             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
15658                    "Black offers a draw" : "White offers a draw")) {
15659             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
15660             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
15661         } else if (currentMove == cmailOldMove + 1) {
15662             char *offer = WhiteOnMove(cmailOldMove) ?
15663               "White offers a draw" : "Black offers a draw";
15664             AppendComment(currentMove, offer, TRUE);
15665             DisplayComment(currentMove - 1, offer);
15666             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
15667         } else {
15668             DisplayError(_("You must make your move before offering a draw"), 0);
15669             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
15670         }
15671     } else if (first.offeredDraw) {
15672         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
15673     } else {
15674         if (first.sendDrawOffers) {
15675             SendToProgram("draw\n", &first);
15676             userOfferedDraw = TRUE;
15677         }
15678     }
15679 }
15680
15681 void
15682 AdjournEvent ()
15683 {
15684     /* Offer Adjourn or accept pending Adjourn offer from opponent */
15685
15686     if (appData.icsActive) {
15687         SendToICS(ics_prefix);
15688         SendToICS("adjourn\n");
15689     } else {
15690         /* Currently GNU Chess doesn't offer or accept Adjourns */
15691     }
15692 }
15693
15694
15695 void
15696 AbortEvent ()
15697 {
15698     /* Offer Abort or accept pending Abort offer from opponent */
15699
15700     if (appData.icsActive) {
15701         SendToICS(ics_prefix);
15702         SendToICS("abort\n");
15703     } else {
15704         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
15705     }
15706 }
15707
15708 void
15709 ResignEvent ()
15710 {
15711     /* Resign.  You can do this even if it's not your turn. */
15712
15713     if (appData.icsActive) {
15714         SendToICS(ics_prefix);
15715         SendToICS("resign\n");
15716     } else {
15717         switch (gameMode) {
15718           case MachinePlaysWhite:
15719             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15720             break;
15721           case MachinePlaysBlack:
15722             GameEnds(BlackWins, "White resigns", GE_PLAYER);
15723             break;
15724           case EditGame:
15725             if (cmailMsgLoaded) {
15726                 TruncateGame();
15727                 if (WhiteOnMove(cmailOldMove)) {
15728                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
15729                 } else {
15730                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
15731                 }
15732                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
15733             }
15734             break;
15735           default:
15736             break;
15737         }
15738     }
15739 }
15740
15741
15742 void
15743 StopObservingEvent ()
15744 {
15745     /* Stop observing current games */
15746     SendToICS(ics_prefix);
15747     SendToICS("unobserve\n");
15748 }
15749
15750 void
15751 StopExaminingEvent ()
15752 {
15753     /* Stop observing current game */
15754     SendToICS(ics_prefix);
15755     SendToICS("unexamine\n");
15756 }
15757
15758 void
15759 ForwardInner (int target)
15760 {
15761     int limit; int oldSeekGraphUp = seekGraphUp;
15762
15763     if (appData.debugMode)
15764         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
15765                 target, currentMove, forwardMostMove);
15766
15767     if (gameMode == EditPosition)
15768       return;
15769
15770     seekGraphUp = FALSE;
15771     MarkTargetSquares(1);
15772     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15773
15774     if (gameMode == PlayFromGameFile && !pausing)
15775       PauseEvent();
15776
15777     if (gameMode == IcsExamining && pausing)
15778       limit = pauseExamForwardMostMove;
15779     else
15780       limit = forwardMostMove;
15781
15782     if (target > limit) target = limit;
15783
15784     if (target > 0 && moveList[target - 1][0]) {
15785         int fromX, fromY, toX, toY;
15786         toX = moveList[target - 1][2] - AAA;
15787         toY = moveList[target - 1][3] - ONE;
15788         if (moveList[target - 1][1] == '@') {
15789             if (appData.highlightLastMove) {
15790                 SetHighlights(-1, -1, toX, toY);
15791             }
15792         } else {
15793             int viaX = moveList[target - 1][5] - AAA;
15794             int viaY = moveList[target - 1][6] - ONE;
15795             fromX = moveList[target - 1][0] - AAA;
15796             fromY = moveList[target - 1][1] - ONE;
15797             if (target == currentMove + 1) {
15798                 if(moveList[target - 1][4] == ';') { // multi-leg
15799                     ChessSquare piece = boards[currentMove][viaY][viaX];
15800                     AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
15801                     boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
15802                     AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
15803                     boards[currentMove][viaY][viaX] = piece;
15804                 } else
15805                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
15806             }
15807             if (appData.highlightLastMove) {
15808                 SetHighlights(fromX, fromY, toX, toY);
15809             }
15810         }
15811     }
15812     if (gameMode == EditGame || gameMode == AnalyzeMode ||
15813         gameMode == Training || gameMode == PlayFromGameFile ||
15814         gameMode == AnalyzeFile) {
15815         while (currentMove < target) {
15816             if(second.analyzing) SendMoveToProgram(currentMove, &second);
15817             SendMoveToProgram(currentMove++, &first);
15818         }
15819     } else {
15820         currentMove = target;
15821     }
15822
15823     if (gameMode == EditGame || gameMode == EndOfGame) {
15824         whiteTimeRemaining = timeRemaining[0][currentMove];
15825         blackTimeRemaining = timeRemaining[1][currentMove];
15826     }
15827     DisplayBothClocks();
15828     DisplayMove(currentMove - 1);
15829     DrawPosition(oldSeekGraphUp, boards[currentMove]);
15830     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15831     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
15832         DisplayComment(currentMove - 1, commentList[currentMove]);
15833     }
15834     ClearMap(); // [HGM] exclude: invalidate map
15835 }
15836
15837
15838 void
15839 ForwardEvent ()
15840 {
15841     if (gameMode == IcsExamining && !pausing) {
15842         SendToICS(ics_prefix);
15843         SendToICS("forward\n");
15844     } else {
15845         ForwardInner(currentMove + 1);
15846     }
15847 }
15848
15849 void
15850 ToEndEvent ()
15851 {
15852     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15853         /* to optimze, we temporarily turn off analysis mode while we feed
15854          * the remaining moves to the engine. Otherwise we get analysis output
15855          * after each move.
15856          */
15857         if (first.analysisSupport) {
15858           SendToProgram("exit\nforce\n", &first);
15859           first.analyzing = FALSE;
15860         }
15861     }
15862
15863     if (gameMode == IcsExamining && !pausing) {
15864         SendToICS(ics_prefix);
15865         SendToICS("forward 999999\n");
15866     } else {
15867         ForwardInner(forwardMostMove);
15868     }
15869
15870     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15871         /* we have fed all the moves, so reactivate analysis mode */
15872         SendToProgram("analyze\n", &first);
15873         first.analyzing = TRUE;
15874         /*first.maybeThinking = TRUE;*/
15875         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15876     }
15877 }
15878
15879 void
15880 BackwardInner (int target)
15881 {
15882     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
15883
15884     if (appData.debugMode)
15885         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
15886                 target, currentMove, forwardMostMove);
15887
15888     if (gameMode == EditPosition) return;
15889     seekGraphUp = FALSE;
15890     MarkTargetSquares(1);
15891     fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
15892     if (currentMove <= backwardMostMove) {
15893         ClearHighlights();
15894         DrawPosition(full_redraw, boards[currentMove]);
15895         return;
15896     }
15897     if (gameMode == PlayFromGameFile && !pausing)
15898       PauseEvent();
15899
15900     if (moveList[target][0]) {
15901         int fromX, fromY, toX, toY;
15902         toX = moveList[target][2] - AAA;
15903         toY = moveList[target][3] - ONE;
15904         if (moveList[target][1] == '@') {
15905             if (appData.highlightLastMove) {
15906                 SetHighlights(-1, -1, toX, toY);
15907             }
15908         } else {
15909             fromX = moveList[target][0] - AAA;
15910             fromY = moveList[target][1] - ONE;
15911             if (target == currentMove - 1) {
15912                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
15913             }
15914             if (appData.highlightLastMove) {
15915                 SetHighlights(fromX, fromY, toX, toY);
15916             }
15917         }
15918     }
15919     if (gameMode == EditGame || gameMode==AnalyzeMode ||
15920         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
15921         while (currentMove > target) {
15922             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
15923                 // null move cannot be undone. Reload program with move history before it.
15924                 int i;
15925                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
15926                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
15927                 }
15928                 SendBoard(&first, i);
15929               if(second.analyzing) SendBoard(&second, i);
15930                 for(currentMove=i; currentMove<target; currentMove++) {
15931                     SendMoveToProgram(currentMove, &first);
15932                     if(second.analyzing) SendMoveToProgram(currentMove, &second);
15933                 }
15934                 break;
15935             }
15936             SendToBoth("undo\n");
15937             currentMove--;
15938         }
15939     } else {
15940         currentMove = target;
15941     }
15942
15943     if (gameMode == EditGame || gameMode == EndOfGame) {
15944         whiteTimeRemaining = timeRemaining[0][currentMove];
15945         blackTimeRemaining = timeRemaining[1][currentMove];
15946     }
15947     DisplayBothClocks();
15948     DisplayMove(currentMove - 1);
15949     DrawPosition(full_redraw, boards[currentMove]);
15950     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
15951     // [HGM] PV info: routine tests if comment empty
15952     DisplayComment(currentMove - 1, commentList[currentMove]);
15953     ClearMap(); // [HGM] exclude: invalidate map
15954 }
15955
15956 void
15957 BackwardEvent ()
15958 {
15959     if (gameMode == IcsExamining && !pausing) {
15960         SendToICS(ics_prefix);
15961         SendToICS("backward\n");
15962     } else {
15963         BackwardInner(currentMove - 1);
15964     }
15965 }
15966
15967 void
15968 ToStartEvent ()
15969 {
15970     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15971         /* to optimize, we temporarily turn off analysis mode while we undo
15972          * all the moves. Otherwise we get analysis output after each undo.
15973          */
15974         if (first.analysisSupport) {
15975           SendToProgram("exit\nforce\n", &first);
15976           first.analyzing = FALSE;
15977         }
15978     }
15979
15980     if (gameMode == IcsExamining && !pausing) {
15981         SendToICS(ics_prefix);
15982         SendToICS("backward 999999\n");
15983     } else {
15984         BackwardInner(backwardMostMove);
15985     }
15986
15987     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15988         /* we have fed all the moves, so reactivate analysis mode */
15989         SendToProgram("analyze\n", &first);
15990         first.analyzing = TRUE;
15991         /*first.maybeThinking = TRUE;*/
15992         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
15993     }
15994 }
15995
15996 void
15997 ToNrEvent (int to)
15998 {
15999   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
16000   if (to >= forwardMostMove) to = forwardMostMove;
16001   if (to <= backwardMostMove) to = backwardMostMove;
16002   if (to < currentMove) {
16003     BackwardInner(to);
16004   } else {
16005     ForwardInner(to);
16006   }
16007 }
16008
16009 void
16010 RevertEvent (Boolean annotate)
16011 {
16012     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
16013         return;
16014     }
16015     if (gameMode != IcsExamining) {
16016         DisplayError(_("You are not examining a game"), 0);
16017         return;
16018     }
16019     if (pausing) {
16020         DisplayError(_("You can't revert while pausing"), 0);
16021         return;
16022     }
16023     SendToICS(ics_prefix);
16024     SendToICS("revert\n");
16025 }
16026
16027 void
16028 RetractMoveEvent ()
16029 {
16030     switch (gameMode) {
16031       case MachinePlaysWhite:
16032       case MachinePlaysBlack:
16033         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
16034             DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0);
16035             return;
16036         }
16037         if (forwardMostMove < 2) return;
16038         currentMove = forwardMostMove = forwardMostMove - 2;
16039         whiteTimeRemaining = timeRemaining[0][currentMove];
16040         blackTimeRemaining = timeRemaining[1][currentMove];
16041         DisplayBothClocks();
16042         DisplayMove(currentMove - 1);
16043         ClearHighlights();/*!! could figure this out*/
16044         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
16045         SendToProgram("remove\n", &first);
16046         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
16047         break;
16048
16049       case BeginningOfGame:
16050       default:
16051         break;
16052
16053       case IcsPlayingWhite:
16054       case IcsPlayingBlack:
16055         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
16056             SendToICS(ics_prefix);
16057             SendToICS("takeback 2\n");
16058         } else {
16059             SendToICS(ics_prefix);
16060             SendToICS("takeback 1\n");
16061         }
16062         break;
16063     }
16064 }
16065
16066 void
16067 MoveNowEvent ()
16068 {
16069     ChessProgramState *cps;
16070
16071     switch (gameMode) {
16072       case MachinePlaysWhite:
16073         if (!WhiteOnMove(forwardMostMove)) {
16074             DisplayError(_("It is your turn"), 0);
16075             return;
16076         }
16077         cps = &first;
16078         break;
16079       case MachinePlaysBlack:
16080         if (WhiteOnMove(forwardMostMove)) {
16081             DisplayError(_("It is your turn"), 0);
16082             return;
16083         }
16084         cps = &first;
16085         break;
16086       case TwoMachinesPlay:
16087         if (WhiteOnMove(forwardMostMove) ==
16088             (first.twoMachinesColor[0] == 'w')) {
16089             cps = &first;
16090         } else {
16091             cps = &second;
16092         }
16093         break;
16094       case BeginningOfGame:
16095       default:
16096         return;
16097     }
16098     SendToProgram("?\n", cps);
16099 }
16100
16101 void
16102 TruncateGameEvent ()
16103 {
16104     EditGameEvent();
16105     if (gameMode != EditGame) return;
16106     TruncateGame();
16107 }
16108
16109 void
16110 TruncateGame ()
16111 {
16112     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
16113     if (forwardMostMove > currentMove) {
16114         if (gameInfo.resultDetails != NULL) {
16115             free(gameInfo.resultDetails);
16116             gameInfo.resultDetails = NULL;
16117             gameInfo.result = GameUnfinished;
16118         }
16119         forwardMostMove = currentMove;
16120         HistorySet(parseList, backwardMostMove, forwardMostMove,
16121                    currentMove-1);
16122     }
16123 }
16124
16125 void
16126 HintEvent ()
16127 {
16128     if (appData.noChessProgram) return;
16129     switch (gameMode) {
16130       case MachinePlaysWhite:
16131         if (WhiteOnMove(forwardMostMove)) {
16132             DisplayError(_("Wait until your turn."), 0);
16133             return;
16134         }
16135         break;
16136       case BeginningOfGame:
16137       case MachinePlaysBlack:
16138         if (!WhiteOnMove(forwardMostMove)) {
16139             DisplayError(_("Wait until your turn."), 0);
16140             return;
16141         }
16142         break;
16143       default:
16144         DisplayError(_("No hint available"), 0);
16145         return;
16146     }
16147     SendToProgram("hint\n", &first);
16148     hintRequested = TRUE;
16149 }
16150
16151 int
16152 SaveSelected (FILE *g, int dummy, char *dummy2)
16153 {
16154     ListGame * lg = (ListGame *) gameList.head;
16155     int nItem, cnt=0;
16156     FILE *f;
16157
16158     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16159         DisplayError(_("Game list not loaded or empty"), 0);
16160         return 0;
16161     }
16162
16163     creatingBook = TRUE; // suppresses stuff during load game
16164
16165     /* Get list size */
16166     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16167         if(lg->position >= 0) { // selected?
16168             LoadGame(f, nItem, "", TRUE);
16169             SaveGamePGN2(g); // leaves g open
16170             cnt++; DoEvents();
16171         }
16172         lg = (ListGame *) lg->node.succ;
16173     }
16174
16175     fclose(g);
16176     creatingBook = FALSE;
16177
16178     return cnt;
16179 }
16180
16181 void
16182 CreateBookEvent ()
16183 {
16184     ListGame * lg = (ListGame *) gameList.head;
16185     FILE *f, *g;
16186     int nItem;
16187     static int secondTime = FALSE;
16188
16189     if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
16190         DisplayError(_("Game list not loaded or empty"), 0);
16191         return;
16192     }
16193
16194     if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) {
16195         fclose(g);
16196         secondTime++;
16197         DisplayNote(_("Book file exists! Try again for overwrite."));
16198         return;
16199     }
16200
16201     creatingBook = TRUE;
16202     secondTime = FALSE;
16203
16204     /* Get list size */
16205     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
16206         if(lg->position >= 0) {
16207             LoadGame(f, nItem, "", TRUE);
16208             AddGameToBook(TRUE);
16209             DoEvents();
16210         }
16211         lg = (ListGame *) lg->node.succ;
16212     }
16213
16214     creatingBook = FALSE;
16215     FlushBook();
16216 }
16217
16218 void
16219 BookEvent ()
16220 {
16221     if (appData.noChessProgram) return;
16222     switch (gameMode) {
16223       case MachinePlaysWhite:
16224         if (WhiteOnMove(forwardMostMove)) {
16225             DisplayError(_("Wait until your turn."), 0);
16226             return;
16227         }
16228         break;
16229       case BeginningOfGame:
16230       case MachinePlaysBlack:
16231         if (!WhiteOnMove(forwardMostMove)) {
16232             DisplayError(_("Wait until your turn."), 0);
16233             return;
16234         }
16235         break;
16236       case EditPosition:
16237         EditPositionDone(TRUE);
16238         break;
16239       case TwoMachinesPlay:
16240         return;
16241       default:
16242         break;
16243     }
16244     SendToProgram("bk\n", &first);
16245     bookOutput[0] = NULLCHAR;
16246     bookRequested = TRUE;
16247 }
16248
16249 void
16250 AboutGameEvent ()
16251 {
16252     char *tags = PGNTags(&gameInfo);
16253     TagsPopUp(tags, CmailMsg());
16254     free(tags);
16255 }
16256
16257 /* end button procedures */
16258
16259 void
16260 PrintPosition (FILE *fp, int move)
16261 {
16262     int i, j;
16263
16264     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16265         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16266             char c = PieceToChar(boards[move][i][j]);
16267             fputc(c == 'x' ? '.' : c, fp);
16268             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
16269         }
16270     }
16271     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
16272       fprintf(fp, "white to play\n");
16273     else
16274       fprintf(fp, "black to play\n");
16275 }
16276
16277 void
16278 PrintOpponents (FILE *fp)
16279 {
16280     if (gameInfo.white != NULL) {
16281         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
16282     } else {
16283         fprintf(fp, "\n");
16284     }
16285 }
16286
16287 /* Find last component of program's own name, using some heuristics */
16288 void
16289 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
16290 {
16291     char *p, *q, c;
16292     int local = (strcmp(host, "localhost") == 0);
16293     while (!local && (p = strchr(prog, ';')) != NULL) {
16294         p++;
16295         while (*p == ' ') p++;
16296         prog = p;
16297     }
16298     if (*prog == '"' || *prog == '\'') {
16299         q = strchr(prog + 1, *prog);
16300     } else {
16301         q = strchr(prog, ' ');
16302     }
16303     if (q == NULL) q = prog + strlen(prog);
16304     p = q;
16305     while (p >= prog && *p != '/' && *p != '\\') p--;
16306     p++;
16307     if(p == prog && *p == '"') p++;
16308     c = *q; *q = 0;
16309     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
16310     memcpy(buf, p, q - p);
16311     buf[q - p] = NULLCHAR;
16312     if (!local) {
16313         strcat(buf, "@");
16314         strcat(buf, host);
16315     }
16316 }
16317
16318 char *
16319 TimeControlTagValue ()
16320 {
16321     char buf[MSG_SIZ];
16322     if (!appData.clockMode) {
16323       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
16324     } else if (movesPerSession > 0) {
16325       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
16326     } else if (timeIncrement == 0) {
16327       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
16328     } else {
16329       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
16330     }
16331     return StrSave(buf);
16332 }
16333
16334 void
16335 SetGameInfo ()
16336 {
16337     /* This routine is used only for certain modes */
16338     VariantClass v = gameInfo.variant;
16339     ChessMove r = GameUnfinished;
16340     char *p = NULL;
16341
16342     if(keepInfo) return;
16343
16344     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
16345         r = gameInfo.result;
16346         p = gameInfo.resultDetails;
16347         gameInfo.resultDetails = NULL;
16348     }
16349     ClearGameInfo(&gameInfo);
16350     gameInfo.variant = v;
16351
16352     switch (gameMode) {
16353       case MachinePlaysWhite:
16354         gameInfo.event = StrSave( appData.pgnEventHeader );
16355         gameInfo.site = StrSave(HostName());
16356         gameInfo.date = PGNDate();
16357         gameInfo.round = StrSave("-");
16358         gameInfo.white = StrSave(first.tidy);
16359         gameInfo.black = StrSave(UserName());
16360         gameInfo.timeControl = TimeControlTagValue();
16361         break;
16362
16363       case MachinePlaysBlack:
16364         gameInfo.event = StrSave( appData.pgnEventHeader );
16365         gameInfo.site = StrSave(HostName());
16366         gameInfo.date = PGNDate();
16367         gameInfo.round = StrSave("-");
16368         gameInfo.white = StrSave(UserName());
16369         gameInfo.black = StrSave(first.tidy);
16370         gameInfo.timeControl = TimeControlTagValue();
16371         break;
16372
16373       case TwoMachinesPlay:
16374         gameInfo.event = StrSave( appData.pgnEventHeader );
16375         gameInfo.site = StrSave(HostName());
16376         gameInfo.date = PGNDate();
16377         if (roundNr > 0) {
16378             char buf[MSG_SIZ];
16379             snprintf(buf, MSG_SIZ, "%d", roundNr);
16380             gameInfo.round = StrSave(buf);
16381         } else {
16382             gameInfo.round = StrSave("-");
16383         }
16384         if (first.twoMachinesColor[0] == 'w') {
16385             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16386             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16387         } else {
16388             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
16389             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
16390         }
16391         gameInfo.timeControl = TimeControlTagValue();
16392         break;
16393
16394       case EditGame:
16395         gameInfo.event = StrSave("Edited game");
16396         gameInfo.site = StrSave(HostName());
16397         gameInfo.date = PGNDate();
16398         gameInfo.round = StrSave("-");
16399         gameInfo.white = StrSave("-");
16400         gameInfo.black = StrSave("-");
16401         gameInfo.result = r;
16402         gameInfo.resultDetails = p;
16403         break;
16404
16405       case EditPosition:
16406         gameInfo.event = StrSave("Edited position");
16407         gameInfo.site = StrSave(HostName());
16408         gameInfo.date = PGNDate();
16409         gameInfo.round = StrSave("-");
16410         gameInfo.white = StrSave("-");
16411         gameInfo.black = StrSave("-");
16412         break;
16413
16414       case IcsPlayingWhite:
16415       case IcsPlayingBlack:
16416       case IcsObserving:
16417       case IcsExamining:
16418         break;
16419
16420       case PlayFromGameFile:
16421         gameInfo.event = StrSave("Game from non-PGN file");
16422         gameInfo.site = StrSave(HostName());
16423         gameInfo.date = PGNDate();
16424         gameInfo.round = StrSave("-");
16425         gameInfo.white = StrSave("?");
16426         gameInfo.black = StrSave("?");
16427         break;
16428
16429       default:
16430         break;
16431     }
16432 }
16433
16434 void
16435 ReplaceComment (int index, char *text)
16436 {
16437     int len;
16438     char *p;
16439     float score;
16440
16441     if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
16442        pvInfoList[index-1].depth == len &&
16443        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
16444        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
16445     while (*text == '\n') text++;
16446     len = strlen(text);
16447     while (len > 0 && text[len - 1] == '\n') len--;
16448
16449     if (commentList[index] != NULL)
16450       free(commentList[index]);
16451
16452     if (len == 0) {
16453         commentList[index] = NULL;
16454         return;
16455     }
16456   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
16457       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
16458       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
16459     commentList[index] = (char *) malloc(len + 2);
16460     strncpy(commentList[index], text, len);
16461     commentList[index][len] = '\n';
16462     commentList[index][len + 1] = NULLCHAR;
16463   } else {
16464     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
16465     char *p;
16466     commentList[index] = (char *) malloc(len + 7);
16467     safeStrCpy(commentList[index], "{\n", 3);
16468     safeStrCpy(commentList[index]+2, text, len+1);
16469     commentList[index][len+2] = NULLCHAR;
16470     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
16471     strcat(commentList[index], "\n}\n");
16472   }
16473 }
16474
16475 void
16476 CrushCRs (char *text)
16477 {
16478   char *p = text;
16479   char *q = text;
16480   char ch;
16481
16482   do {
16483     ch = *p++;
16484     if (ch == '\r') continue;
16485     *q++ = ch;
16486   } while (ch != '\0');
16487 }
16488
16489 void
16490 AppendComment (int index, char *text, Boolean addBraces)
16491 /* addBraces  tells if we should add {} */
16492 {
16493     int oldlen, len;
16494     char *old;
16495
16496 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
16497     if(addBraces == 3) addBraces = 0; else // force appending literally
16498     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
16499
16500     CrushCRs(text);
16501     while (*text == '\n') text++;
16502     len = strlen(text);
16503     while (len > 0 && text[len - 1] == '\n') len--;
16504     text[len] = NULLCHAR;
16505
16506     if (len == 0) return;
16507
16508     if (commentList[index] != NULL) {
16509       Boolean addClosingBrace = addBraces;
16510         old = commentList[index];
16511         oldlen = strlen(old);
16512         while(commentList[index][oldlen-1] ==  '\n')
16513           commentList[index][--oldlen] = NULLCHAR;
16514         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
16515         safeStrCpy(commentList[index], old, oldlen + len + 6);
16516         free(old);
16517         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
16518         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
16519           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
16520           while (*text == '\n') { text++; len--; }
16521           commentList[index][--oldlen] = NULLCHAR;
16522       }
16523         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
16524         else          strcat(commentList[index], "\n");
16525         strcat(commentList[index], text);
16526         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
16527         else          strcat(commentList[index], "\n");
16528     } else {
16529         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
16530         if(addBraces)
16531           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
16532         else commentList[index][0] = NULLCHAR;
16533         strcat(commentList[index], text);
16534         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
16535         if(addBraces == TRUE) strcat(commentList[index], "}\n");
16536     }
16537 }
16538
16539 static char *
16540 FindStr (char * text, char * sub_text)
16541 {
16542     char * result = strstr( text, sub_text );
16543
16544     if( result != NULL ) {
16545         result += strlen( sub_text );
16546     }
16547
16548     return result;
16549 }
16550
16551 /* [AS] Try to extract PV info from PGN comment */
16552 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
16553 char *
16554 GetInfoFromComment (int index, char * text)
16555 {
16556     char * sep = text, *p;
16557
16558     if( text != NULL && index > 0 ) {
16559         int score = 0;
16560         int depth = 0;
16561         int time = -1, sec = 0, deci;
16562         char * s_eval = FindStr( text, "[%eval " );
16563         char * s_emt = FindStr( text, "[%emt " );
16564 #if 0
16565         if( s_eval != NULL || s_emt != NULL ) {
16566 #else
16567         if(0) { // [HGM] this code is not finished, and could actually be detrimental
16568 #endif
16569             /* New style */
16570             char delim;
16571
16572             if( s_eval != NULL ) {
16573                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
16574                     return text;
16575                 }
16576
16577                 if( delim != ']' ) {
16578                     return text;
16579                 }
16580             }
16581
16582             if( s_emt != NULL ) {
16583             }
16584                 return text;
16585         }
16586         else {
16587             /* We expect something like: [+|-]nnn.nn/dd */
16588             int score_lo = 0;
16589
16590             if(*text != '{') return text; // [HGM] braces: must be normal comment
16591
16592             sep = strchr( text, '/' );
16593             if( sep == NULL || sep < (text+4) ) {
16594                 return text;
16595             }
16596
16597             p = text;
16598             if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else
16599             if(p[1] == '(') { // comment starts with PV
16600                p = strchr(p, ')'); // locate end of PV
16601                if(p == NULL || sep < p+5) return text;
16602                // at this point we have something like "{(.*) +0.23/6 ..."
16603                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
16604                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
16605                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
16606             }
16607             time = -1; sec = -1; deci = -1;
16608             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
16609                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
16610                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
16611                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
16612                 return text;
16613             }
16614
16615             if( score_lo < 0 || score_lo >= 100 ) {
16616                 return text;
16617             }
16618
16619             if(sec >= 0) time = 600*time + 10*sec; else
16620             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
16621
16622             score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo;
16623
16624             /* [HGM] PV time: now locate end of PV info */
16625             while( *++sep >= '0' && *sep <= '9'); // strip depth
16626             if(time >= 0)
16627             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
16628             if(sec >= 0)
16629             while( *++sep >= '0' && *sep <= '9'); // strip seconds
16630             if(deci >= 0)
16631             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
16632             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
16633         }
16634
16635         if( depth <= 0 ) {
16636             return text;
16637         }
16638
16639         if( time < 0 ) {
16640             time = -1;
16641         }
16642
16643         pvInfoList[index-1].depth = depth;
16644         pvInfoList[index-1].score = score;
16645         pvInfoList[index-1].time  = 10*time; // centi-sec
16646         if(*sep == '}') *sep = 0; else *--sep = '{';
16647         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
16648     }
16649     return sep;
16650 }
16651
16652 void
16653 SendToProgram (char *message, ChessProgramState *cps)
16654 {
16655     int count, outCount, error;
16656     char buf[MSG_SIZ];
16657
16658     if (cps->pr == NoProc) return;
16659     Attention(cps);
16660
16661     if (appData.debugMode) {
16662         TimeMark now;
16663         GetTimeMark(&now);
16664         fprintf(debugFP, "%ld >%-6s: %s",
16665                 SubtractTimeMarks(&now, &programStartTime),
16666                 cps->which, message);
16667         if(serverFP)
16668             fprintf(serverFP, "%ld >%-6s: %s",
16669                 SubtractTimeMarks(&now, &programStartTime),
16670                 cps->which, message), fflush(serverFP);
16671     }
16672
16673     count = strlen(message);
16674     outCount = OutputToProcess(cps->pr, message, count, &error);
16675     if (outCount < count && !exiting
16676                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
16677       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
16678       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
16679         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16680             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16681                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16682                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16683                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16684             } else {
16685                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16686                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16687                 gameInfo.result = res;
16688             }
16689             gameInfo.resultDetails = StrSave(buf);
16690         }
16691         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16692         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16693     }
16694 }
16695
16696 void
16697 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
16698 {
16699     char *end_str;
16700     char buf[MSG_SIZ];
16701     ChessProgramState *cps = (ChessProgramState *)closure;
16702
16703     if (isr != cps->isr) return; /* Killed intentionally */
16704     if (count <= 0) {
16705         if (count == 0) {
16706             RemoveInputSource(cps->isr);
16707             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
16708                     _(cps->which), cps->program);
16709             if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
16710             if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
16711                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
16712                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
16713                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
16714                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
16715                 } else {
16716                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
16717                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
16718                     gameInfo.result = res;
16719                 }
16720                 gameInfo.resultDetails = StrSave(buf);
16721             }
16722             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
16723             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
16724         } else {
16725             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
16726                     _(cps->which), cps->program);
16727             RemoveInputSource(cps->isr);
16728
16729             /* [AS] Program is misbehaving badly... kill it */
16730             if( count == -2 ) {
16731                 DestroyChildProcess( cps->pr, 9 );
16732                 cps->pr = NoProc;
16733             }
16734
16735             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
16736         }
16737         return;
16738     }
16739
16740     if ((end_str = strchr(message, '\r')) != NULL)
16741       *end_str = NULLCHAR;
16742     if ((end_str = strchr(message, '\n')) != NULL)
16743       *end_str = NULLCHAR;
16744
16745     if (appData.debugMode) {
16746         TimeMark now; int print = 1;
16747         char *quote = ""; char c; int i;
16748
16749         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
16750                 char start = message[0];
16751                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
16752                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
16753                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
16754                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
16755                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
16756                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
16757                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
16758                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
16759                    sscanf(message, "hint: %c", &c)!=1 &&
16760                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
16761                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
16762                     print = (appData.engineComments >= 2);
16763                 }
16764                 message[0] = start; // restore original message
16765         }
16766         if(print) {
16767                 GetTimeMark(&now);
16768                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
16769                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16770                         quote,
16771                         message);
16772                 if(serverFP)
16773                     fprintf(serverFP, "%ld <%-6s: %s%s\n",
16774                         SubtractTimeMarks(&now, &programStartTime), cps->which,
16775                         quote,
16776                         message), fflush(serverFP);
16777         }
16778     }
16779
16780     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
16781     if (appData.icsEngineAnalyze) {
16782         if (strstr(message, "whisper") != NULL ||
16783              strstr(message, "kibitz") != NULL ||
16784             strstr(message, "tellics") != NULL) return;
16785     }
16786
16787     HandleMachineMove(message, cps);
16788 }
16789
16790
16791 void
16792 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
16793 {
16794     char buf[MSG_SIZ];
16795     int seconds;
16796
16797     if( timeControl_2 > 0 ) {
16798         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
16799             tc = timeControl_2;
16800         }
16801     }
16802     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
16803     inc /= cps->timeOdds;
16804     st  /= cps->timeOdds;
16805
16806     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
16807
16808     if (st > 0) {
16809       /* Set exact time per move, normally using st command */
16810       if (cps->stKludge) {
16811         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
16812         seconds = st % 60;
16813         if (seconds == 0) {
16814           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
16815         } else {
16816           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
16817         }
16818       } else {
16819         snprintf(buf, MSG_SIZ, "st %d\n", st);
16820       }
16821     } else {
16822       /* Set conventional or incremental time control, using level command */
16823       if (seconds == 0) {
16824         /* Note old gnuchess bug -- minutes:seconds used to not work.
16825            Fixed in later versions, but still avoid :seconds
16826            when seconds is 0. */
16827         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
16828       } else {
16829         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
16830                  seconds, inc/1000.);
16831       }
16832     }
16833     SendToProgram(buf, cps);
16834
16835     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
16836     /* Orthogonally, limit search to given depth */
16837     if (sd > 0) {
16838       if (cps->sdKludge) {
16839         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
16840       } else {
16841         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
16842       }
16843       SendToProgram(buf, cps);
16844     }
16845
16846     if(cps->nps >= 0) { /* [HGM] nps */
16847         if(cps->supportsNPS == FALSE)
16848           cps->nps = -1; // don't use if engine explicitly says not supported!
16849         else {
16850           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
16851           SendToProgram(buf, cps);
16852         }
16853     }
16854 }
16855
16856 ChessProgramState *
16857 WhitePlayer ()
16858 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
16859 {
16860     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
16861        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
16862         return &second;
16863     return &first;
16864 }
16865
16866 void
16867 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
16868 {
16869     char message[MSG_SIZ];
16870     long time, otime;
16871
16872     /* Note: this routine must be called when the clocks are stopped
16873        or when they have *just* been set or switched; otherwise
16874        it will be off by the time since the current tick started.
16875     */
16876     if (machineWhite) {
16877         time = whiteTimeRemaining / 10;
16878         otime = blackTimeRemaining / 10;
16879     } else {
16880         time = blackTimeRemaining / 10;
16881         otime = whiteTimeRemaining / 10;
16882     }
16883     /* [HGM] translate opponent's time by time-odds factor */
16884     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
16885
16886     if (time <= 0) time = 1;
16887     if (otime <= 0) otime = 1;
16888
16889     snprintf(message, MSG_SIZ, "time %ld\n", time);
16890     SendToProgram(message, cps);
16891
16892     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
16893     SendToProgram(message, cps);
16894 }
16895
16896 char *
16897 EngineDefinedVariant (ChessProgramState *cps, int n)
16898 {   // return name of n-th unknown variant that engine supports
16899     static char buf[MSG_SIZ];
16900     char *p, *s = cps->variants;
16901     if(!s) return NULL;
16902     do { // parse string from variants feature
16903       VariantClass v;
16904         p = strchr(s, ',');
16905         if(p) *p = NULLCHAR;
16906       v = StringToVariant(s);
16907       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
16908         if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
16909             if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
16910                !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
16911                !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
16912                !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
16913             if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
16914         }
16915         if(p) *p++ = ',';
16916         if(n < 0) return buf;
16917     } while(s = p);
16918     return NULL;
16919 }
16920
16921 int
16922 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16923 {
16924   char buf[MSG_SIZ];
16925   int len = strlen(name);
16926   int val;
16927
16928   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16929     (*p) += len + 1;
16930     sscanf(*p, "%d", &val);
16931     *loc = (val != 0);
16932     while (**p && **p != ' ')
16933       (*p)++;
16934     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16935     SendToProgram(buf, cps);
16936     return TRUE;
16937   }
16938   return FALSE;
16939 }
16940
16941 int
16942 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
16943 {
16944   char buf[MSG_SIZ];
16945   int len = strlen(name);
16946   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
16947     (*p) += len + 1;
16948     sscanf(*p, "%d", loc);
16949     while (**p && **p != ' ') (*p)++;
16950     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16951     SendToProgram(buf, cps);
16952     return TRUE;
16953   }
16954   return FALSE;
16955 }
16956
16957 int
16958 StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
16959 {
16960   char buf[MSG_SIZ];
16961   int len = strlen(name);
16962   if (strncmp((*p), name, len) == 0
16963       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
16964     (*p) += len + 2;
16965     ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
16966     sscanf(*p, "%[^\"]", *loc);
16967     while (**p && **p != '\"') (*p)++;
16968     if (**p == '\"') (*p)++;
16969     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
16970     SendToProgram(buf, cps);
16971     return TRUE;
16972   }
16973   return FALSE;
16974 }
16975
16976 int
16977 ParseOption (Option *opt, ChessProgramState *cps)
16978 // [HGM] options: process the string that defines an engine option, and determine
16979 // name, type, default value, and allowed value range
16980 {
16981         char *p, *q, buf[MSG_SIZ];
16982         int n, min = (-1)<<31, max = 1<<31, def;
16983
16984         opt->target = &opt->value;   // OK for spin/slider and checkbox
16985         if(p = strstr(opt->name, " -spin ")) {
16986             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16987             if(max < min) max = min; // enforce consistency
16988             if(def < min) def = min;
16989             if(def > max) def = max;
16990             opt->value = def;
16991             opt->min = min;
16992             opt->max = max;
16993             opt->type = Spin;
16994         } else if((p = strstr(opt->name, " -slider "))) {
16995             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
16996             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
16997             if(max < min) max = min; // enforce consistency
16998             if(def < min) def = min;
16999             if(def > max) def = max;
17000             opt->value = def;
17001             opt->min = min;
17002             opt->max = max;
17003             opt->type = Spin; // Slider;
17004         } else if((p = strstr(opt->name, " -string "))) {
17005             opt->textValue = p+9;
17006             opt->type = TextBox;
17007             opt->target = &opt->textValue;
17008         } else if((p = strstr(opt->name, " -file "))) {
17009             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17010             opt->target = opt->textValue = p+7;
17011             opt->type = FileName; // FileName;
17012             opt->target = &opt->textValue;
17013         } else if((p = strstr(opt->name, " -path "))) {
17014             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
17015             opt->target = opt->textValue = p+7;
17016             opt->type = PathName; // PathName;
17017             opt->target = &opt->textValue;
17018         } else if(p = strstr(opt->name, " -check ")) {
17019             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
17020             opt->value = (def != 0);
17021             opt->type = CheckBox;
17022         } else if(p = strstr(opt->name, " -combo ")) {
17023             opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type
17024             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
17025             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
17026             opt->value = n = 0;
17027             while(q = StrStr(q, " /// ")) {
17028                 n++; *q = 0;    // count choices, and null-terminate each of them
17029                 q += 5;
17030                 if(*q == '*') { // remember default, which is marked with * prefix
17031                     q++;
17032                     opt->value = n;
17033                 }
17034                 cps->comboList[cps->comboCnt++] = q;
17035             }
17036             cps->comboList[cps->comboCnt++] = NULL;
17037             opt->max = n + 1;
17038             opt->type = ComboBox;
17039         } else if(p = strstr(opt->name, " -button")) {
17040             opt->type = Button;
17041         } else if(p = strstr(opt->name, " -save")) {
17042             opt->type = SaveButton;
17043         } else return FALSE;
17044         *p = 0; // terminate option name
17045         // now look if the command-line options define a setting for this engine option.
17046         if(cps->optionSettings && cps->optionSettings[0])
17047             p = strstr(cps->optionSettings, opt->name); else p = NULL;
17048         if(p && (p == cps->optionSettings || p[-1] == ',')) {
17049           snprintf(buf, MSG_SIZ, "option %s", p);
17050                 if(p = strstr(buf, ",")) *p = 0;
17051                 if(q = strchr(buf, '=')) switch(opt->type) {
17052                     case ComboBox:
17053                         for(n=0; n<opt->max; n++)
17054                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
17055                         break;
17056                     case TextBox:
17057                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
17058                         break;
17059                     case Spin:
17060                     case CheckBox:
17061                         opt->value = atoi(q+1);
17062                     default:
17063                         break;
17064                 }
17065                 strcat(buf, "\n");
17066                 SendToProgram(buf, cps);
17067         }
17068         return TRUE;
17069 }
17070
17071 void
17072 FeatureDone (ChessProgramState *cps, int val)
17073 {
17074   DelayedEventCallback cb = GetDelayedEvent();
17075   if ((cb == InitBackEnd3 && cps == &first) ||
17076       (cb == SettingsMenuIfReady && cps == &second) ||
17077       (cb == LoadEngine) ||
17078       (cb == TwoMachinesEventIfReady)) {
17079     CancelDelayedEvent();
17080     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
17081   } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
17082   cps->initDone = val;
17083   if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
17084 }
17085
17086 /* Parse feature command from engine */
17087 void
17088 ParseFeatures (char *args, ChessProgramState *cps)
17089 {
17090   char *p = args;
17091   char *q = NULL;
17092   int val;
17093   char buf[MSG_SIZ];
17094
17095   for (;;) {
17096     while (*p == ' ') p++;
17097     if (*p == NULLCHAR) return;
17098
17099     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
17100     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
17101     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
17102     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
17103     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
17104     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
17105     if (BoolFeature(&p, "reuse", &val, cps)) {
17106       /* Engine can disable reuse, but can't enable it if user said no */
17107       if (!val) cps->reuse = FALSE;
17108       continue;
17109     }
17110     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
17111     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
17112       if (gameMode == TwoMachinesPlay) {
17113         DisplayTwoMachinesTitle();
17114       } else {
17115         DisplayTitle("");
17116       }
17117       continue;
17118     }
17119     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
17120     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
17121     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
17122     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
17123     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
17124     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
17125     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
17126     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
17127     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
17128     if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
17129     if (IntFeature(&p, "done", &val, cps)) {
17130       FeatureDone(cps, val);
17131       continue;
17132     }
17133     /* Added by Tord: */
17134     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
17135     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
17136     /* End of additions by Tord */
17137
17138     /* [HGM] added features: */
17139     if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue;
17140     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
17141     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
17142     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
17143     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
17144     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
17145     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
17146     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
17147         if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
17148         FREE(cps->option[cps->nrOptions].name);
17149         cps->option[cps->nrOptions].name = q; q = NULL;
17150         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
17151           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
17152             SendToProgram(buf, cps);
17153             continue;
17154         }
17155         if(cps->nrOptions >= MAX_OPTIONS) {
17156             cps->nrOptions--;
17157             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
17158             DisplayError(buf, 0);
17159         }
17160         continue;
17161     }
17162     /* End of additions by HGM */
17163
17164     /* unknown feature: complain and skip */
17165     q = p;
17166     while (*q && *q != '=') q++;
17167     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
17168     SendToProgram(buf, cps);
17169     p = q;
17170     if (*p == '=') {
17171       p++;
17172       if (*p == '\"') {
17173         p++;
17174         while (*p && *p != '\"') p++;
17175         if (*p == '\"') p++;
17176       } else {
17177         while (*p && *p != ' ') p++;
17178       }
17179     }
17180   }
17181
17182 }
17183
17184 void
17185 PeriodicUpdatesEvent (int newState)
17186 {
17187     if (newState == appData.periodicUpdates)
17188       return;
17189
17190     appData.periodicUpdates=newState;
17191
17192     /* Display type changes, so update it now */
17193 //    DisplayAnalysis();
17194
17195     /* Get the ball rolling again... */
17196     if (newState) {
17197         AnalysisPeriodicEvent(1);
17198         StartAnalysisClock();
17199     }
17200 }
17201
17202 void
17203 PonderNextMoveEvent (int newState)
17204 {
17205     if (newState == appData.ponderNextMove) return;
17206     if (gameMode == EditPosition) EditPositionDone(TRUE);
17207     if (newState) {
17208         SendToProgram("hard\n", &first);
17209         if (gameMode == TwoMachinesPlay) {
17210             SendToProgram("hard\n", &second);
17211         }
17212     } else {
17213         SendToProgram("easy\n", &first);
17214         thinkOutput[0] = NULLCHAR;
17215         if (gameMode == TwoMachinesPlay) {
17216             SendToProgram("easy\n", &second);
17217         }
17218     }
17219     appData.ponderNextMove = newState;
17220 }
17221
17222 void
17223 NewSettingEvent (int option, int *feature, char *command, int value)
17224 {
17225     char buf[MSG_SIZ];
17226
17227     if (gameMode == EditPosition) EditPositionDone(TRUE);
17228     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
17229     if(feature == NULL || *feature) SendToProgram(buf, &first);
17230     if (gameMode == TwoMachinesPlay) {
17231         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
17232     }
17233 }
17234
17235 void
17236 ShowThinkingEvent ()
17237 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
17238 {
17239     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
17240     int newState = appData.showThinking
17241         // [HGM] thinking: other features now need thinking output as well
17242         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
17243
17244     if (oldState == newState) return;
17245     oldState = newState;
17246     if (gameMode == EditPosition) EditPositionDone(TRUE);
17247     if (oldState) {
17248         SendToProgram("post\n", &first);
17249         if (gameMode == TwoMachinesPlay) {
17250             SendToProgram("post\n", &second);
17251         }
17252     } else {
17253         SendToProgram("nopost\n", &first);
17254         thinkOutput[0] = NULLCHAR;
17255         if (gameMode == TwoMachinesPlay) {
17256             SendToProgram("nopost\n", &second);
17257         }
17258     }
17259 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
17260 }
17261
17262 void
17263 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
17264 {
17265   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
17266   if (pr == NoProc) return;
17267   AskQuestion(title, question, replyPrefix, pr);
17268 }
17269
17270 void
17271 TypeInEvent (char firstChar)
17272 {
17273     if ((gameMode == BeginningOfGame && !appData.icsActive) ||
17274         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
17275         gameMode == AnalyzeMode || gameMode == EditGame ||
17276         gameMode == EditPosition || gameMode == IcsExamining ||
17277         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
17278         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
17279                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
17280                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
17281         gameMode == Training) PopUpMoveDialog(firstChar);
17282 }
17283
17284 void
17285 TypeInDoneEvent (char *move)
17286 {
17287         Board board;
17288         int n, fromX, fromY, toX, toY;
17289         char promoChar;
17290         ChessMove moveType;
17291
17292         // [HGM] FENedit
17293         if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) {
17294                 EditPositionPasteFEN(move);
17295                 return;
17296         }
17297         // [HGM] movenum: allow move number to be typed in any mode
17298         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
17299           ToNrEvent(2*n-1);
17300           return;
17301         }
17302         // undocumented kludge: allow command-line option to be typed in!
17303         // (potentially fatal, and does not implement the effect of the option.)
17304         // should only be used for options that are values on which future decisions will be made,
17305         // and definitely not on options that would be used during initialization.
17306         if(strstr(move, "!!! -") == move) {
17307             ParseArgsFromString(move+4);
17308             return;
17309         }
17310
17311       if (gameMode != EditGame && currentMove != forwardMostMove &&
17312         gameMode != Training) {
17313         DisplayMoveError(_("Displayed move is not current"));
17314       } else {
17315         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17316           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
17317         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
17318         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
17319           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
17320           UserMoveEvent(fromX, fromY, toX, toY, promoChar);
17321         } else {
17322           DisplayMoveError(_("Could not parse move"));
17323         }
17324       }
17325 }
17326
17327 void
17328 DisplayMove (int moveNumber)
17329 {
17330     char message[MSG_SIZ];
17331     char res[MSG_SIZ];
17332     char cpThinkOutput[MSG_SIZ];
17333
17334     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
17335
17336     if (moveNumber == forwardMostMove - 1 ||
17337         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
17338
17339         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
17340
17341         if (strchr(cpThinkOutput, '\n')) {
17342             *strchr(cpThinkOutput, '\n') = NULLCHAR;
17343         }
17344     } else {
17345         *cpThinkOutput = NULLCHAR;
17346     }
17347
17348     /* [AS] Hide thinking from human user */
17349     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
17350         *cpThinkOutput = NULLCHAR;
17351         if( thinkOutput[0] != NULLCHAR ) {
17352             int i;
17353
17354             for( i=0; i<=hiddenThinkOutputState; i++ ) {
17355                 cpThinkOutput[i] = '.';
17356             }
17357             cpThinkOutput[i] = NULLCHAR;
17358             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
17359         }
17360     }
17361
17362     if (moveNumber == forwardMostMove - 1 &&
17363         gameInfo.resultDetails != NULL) {
17364         if (gameInfo.resultDetails[0] == NULLCHAR) {
17365           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
17366         } else {
17367           snprintf(res, MSG_SIZ, " {%s} %s",
17368                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
17369         }
17370     } else {
17371         res[0] = NULLCHAR;
17372     }
17373
17374     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17375         DisplayMessage(res, cpThinkOutput);
17376     } else {
17377       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
17378                 WhiteOnMove(moveNumber) ? " " : ".. ",
17379                 parseList[moveNumber], res);
17380         DisplayMessage(message, cpThinkOutput);
17381     }
17382 }
17383
17384 void
17385 DisplayComment (int moveNumber, char *text)
17386 {
17387     char title[MSG_SIZ];
17388
17389     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
17390       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
17391     } else {
17392       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
17393               WhiteOnMove(moveNumber) ? " " : ".. ",
17394               parseList[moveNumber]);
17395     }
17396     if (text != NULL && (appData.autoDisplayComment || commentUp))
17397         CommentPopUp(title, text);
17398 }
17399
17400 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
17401  * might be busy thinking or pondering.  It can be omitted if your
17402  * gnuchess is configured to stop thinking immediately on any user
17403  * input.  However, that gnuchess feature depends on the FIONREAD
17404  * ioctl, which does not work properly on some flavors of Unix.
17405  */
17406 void
17407 Attention (ChessProgramState *cps)
17408 {
17409 #if ATTENTION
17410     if (!cps->useSigint) return;
17411     if (appData.noChessProgram || (cps->pr == NoProc)) return;
17412     switch (gameMode) {
17413       case MachinePlaysWhite:
17414       case MachinePlaysBlack:
17415       case TwoMachinesPlay:
17416       case IcsPlayingWhite:
17417       case IcsPlayingBlack:
17418       case AnalyzeMode:
17419       case AnalyzeFile:
17420         /* Skip if we know it isn't thinking */
17421         if (!cps->maybeThinking) return;
17422         if (appData.debugMode)
17423           fprintf(debugFP, "Interrupting %s\n", cps->which);
17424         InterruptChildProcess(cps->pr);
17425         cps->maybeThinking = FALSE;
17426         break;
17427       default:
17428         break;
17429     }
17430 #endif /*ATTENTION*/
17431 }
17432
17433 int
17434 CheckFlags ()
17435 {
17436     if (whiteTimeRemaining <= 0) {
17437         if (!whiteFlag) {
17438             whiteFlag = TRUE;
17439             if (appData.icsActive) {
17440                 if (appData.autoCallFlag &&
17441                     gameMode == IcsPlayingBlack && !blackFlag) {
17442                   SendToICS(ics_prefix);
17443                   SendToICS("flag\n");
17444                 }
17445             } else {
17446                 if (blackFlag) {
17447                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17448                 } else {
17449                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
17450                     if (appData.autoCallFlag) {
17451                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
17452                         return TRUE;
17453                     }
17454                 }
17455             }
17456         }
17457     }
17458     if (blackTimeRemaining <= 0) {
17459         if (!blackFlag) {
17460             blackFlag = TRUE;
17461             if (appData.icsActive) {
17462                 if (appData.autoCallFlag &&
17463                     gameMode == IcsPlayingWhite && !whiteFlag) {
17464                   SendToICS(ics_prefix);
17465                   SendToICS("flag\n");
17466                 }
17467             } else {
17468                 if (whiteFlag) {
17469                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
17470                 } else {
17471                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
17472                     if (appData.autoCallFlag) {
17473                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
17474                         return TRUE;
17475                     }
17476                 }
17477             }
17478         }
17479     }
17480     return FALSE;
17481 }
17482
17483 void
17484 CheckTimeControl ()
17485 {
17486     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
17487         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
17488
17489     /*
17490      * add time to clocks when time control is achieved ([HGM] now also used for increment)
17491      */
17492     if ( !WhiteOnMove(forwardMostMove) ) {
17493         /* White made time control */
17494         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
17495         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
17496         /* [HGM] time odds: correct new time quota for time odds! */
17497                                             / WhitePlayer()->timeOdds;
17498         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
17499     } else {
17500         lastBlack -= blackTimeRemaining;
17501         /* Black made time control */
17502         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
17503                                             / WhitePlayer()->other->timeOdds;
17504         lastWhite = whiteTimeRemaining;
17505     }
17506 }
17507
17508 void
17509 DisplayBothClocks ()
17510 {
17511     int wom = gameMode == EditPosition ?
17512       !blackPlaysFirst : WhiteOnMove(currentMove);
17513     DisplayWhiteClock(whiteTimeRemaining, wom);
17514     DisplayBlackClock(blackTimeRemaining, !wom);
17515 }
17516
17517
17518 /* Timekeeping seems to be a portability nightmare.  I think everyone
17519    has ftime(), but I'm really not sure, so I'm including some ifdefs
17520    to use other calls if you don't.  Clocks will be less accurate if
17521    you have neither ftime nor gettimeofday.
17522 */
17523
17524 /* VS 2008 requires the #include outside of the function */
17525 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
17526 #include <sys/timeb.h>
17527 #endif
17528
17529 /* Get the current time as a TimeMark */
17530 void
17531 GetTimeMark (TimeMark *tm)
17532 {
17533 #if HAVE_GETTIMEOFDAY
17534
17535     struct timeval timeVal;
17536     struct timezone timeZone;
17537
17538     gettimeofday(&timeVal, &timeZone);
17539     tm->sec = (long) timeVal.tv_sec;
17540     tm->ms = (int) (timeVal.tv_usec / 1000L);
17541
17542 #else /*!HAVE_GETTIMEOFDAY*/
17543 #if HAVE_FTIME
17544
17545 // include <sys/timeb.h> / moved to just above start of function
17546     struct timeb timeB;
17547
17548     ftime(&timeB);
17549     tm->sec = (long) timeB.time;
17550     tm->ms = (int) timeB.millitm;
17551
17552 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
17553     tm->sec = (long) time(NULL);
17554     tm->ms = 0;
17555 #endif
17556 #endif
17557 }
17558
17559 /* Return the difference in milliseconds between two
17560    time marks.  We assume the difference will fit in a long!
17561 */
17562 long
17563 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
17564 {
17565     return 1000L*(tm2->sec - tm1->sec) +
17566            (long) (tm2->ms - tm1->ms);
17567 }
17568
17569
17570 /*
17571  * Code to manage the game clocks.
17572  *
17573  * In tournament play, black starts the clock and then white makes a move.
17574  * We give the human user a slight advantage if he is playing white---the
17575  * clocks don't run until he makes his first move, so it takes zero time.
17576  * Also, we don't account for network lag, so we could get out of sync
17577  * with GNU Chess's clock -- but then, referees are always right.
17578  */
17579
17580 static TimeMark tickStartTM;
17581 static long intendedTickLength;
17582
17583 long
17584 NextTickLength (long timeRemaining)
17585 {
17586     long nominalTickLength, nextTickLength;
17587
17588     if (timeRemaining > 0L && timeRemaining <= 10000L)
17589       nominalTickLength = 100L;
17590     else
17591       nominalTickLength = 1000L;
17592     nextTickLength = timeRemaining % nominalTickLength;
17593     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
17594
17595     return nextTickLength;
17596 }
17597
17598 /* Adjust clock one minute up or down */
17599 void
17600 AdjustClock (Boolean which, int dir)
17601 {
17602     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
17603     if(which) blackTimeRemaining += 60000*dir;
17604     else      whiteTimeRemaining += 60000*dir;
17605     DisplayBothClocks();
17606     adjustedClock = TRUE;
17607 }
17608
17609 /* Stop clocks and reset to a fresh time control */
17610 void
17611 ResetClocks ()
17612 {
17613     (void) StopClockTimer();
17614     if (appData.icsActive) {
17615         whiteTimeRemaining = blackTimeRemaining = 0;
17616     } else if (searchTime) {
17617         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17618         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17619     } else { /* [HGM] correct new time quote for time odds */
17620         whiteTC = blackTC = fullTimeControlString;
17621         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
17622         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
17623     }
17624     if (whiteFlag || blackFlag) {
17625         DisplayTitle("");
17626         whiteFlag = blackFlag = FALSE;
17627     }
17628     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
17629     DisplayBothClocks();
17630     adjustedClock = FALSE;
17631 }
17632
17633 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
17634
17635 /* Decrement running clock by amount of time that has passed */
17636 void
17637 DecrementClocks ()
17638 {
17639     long timeRemaining;
17640     long lastTickLength, fudge;
17641     TimeMark now;
17642
17643     if (!appData.clockMode) return;
17644     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
17645
17646     GetTimeMark(&now);
17647
17648     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17649
17650     /* Fudge if we woke up a little too soon */
17651     fudge = intendedTickLength - lastTickLength;
17652     if (fudge < 0 || fudge > FUDGE) fudge = 0;
17653
17654     if (WhiteOnMove(forwardMostMove)) {
17655         if(whiteNPS >= 0) lastTickLength = 0;
17656         timeRemaining = whiteTimeRemaining -= lastTickLength;
17657         if(timeRemaining < 0 && !appData.icsActive) {
17658             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
17659             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
17660                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
17661                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
17662             }
17663         }
17664         DisplayWhiteClock(whiteTimeRemaining - fudge,
17665                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17666     } else {
17667         if(blackNPS >= 0) lastTickLength = 0;
17668         timeRemaining = blackTimeRemaining -= lastTickLength;
17669         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
17670             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
17671             if(suddenDeath) {
17672                 blackStartMove = forwardMostMove;
17673                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
17674             }
17675         }
17676         DisplayBlackClock(blackTimeRemaining - fudge,
17677                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
17678     }
17679     if (CheckFlags()) return;
17680
17681     if(twoBoards) { // count down secondary board's clocks as well
17682         activePartnerTime -= lastTickLength;
17683         partnerUp = 1;
17684         if(activePartner == 'W')
17685             DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
17686         else
17687             DisplayBlackClock(activePartnerTime, TRUE);
17688         partnerUp = 0;
17689     }
17690
17691     tickStartTM = now;
17692     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
17693     StartClockTimer(intendedTickLength);
17694
17695     /* if the time remaining has fallen below the alarm threshold, sound the
17696      * alarm. if the alarm has sounded and (due to a takeback or time control
17697      * with increment) the time remaining has increased to a level above the
17698      * threshold, reset the alarm so it can sound again.
17699      */
17700
17701     if (appData.icsActive && appData.icsAlarm) {
17702
17703         /* make sure we are dealing with the user's clock */
17704         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
17705                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
17706            )) return;
17707
17708         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
17709             alarmSounded = FALSE;
17710         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
17711             PlayAlarmSound();
17712             alarmSounded = TRUE;
17713         }
17714     }
17715 }
17716
17717
17718 /* A player has just moved, so stop the previously running
17719    clock and (if in clock mode) start the other one.
17720    We redisplay both clocks in case we're in ICS mode, because
17721    ICS gives us an update to both clocks after every move.
17722    Note that this routine is called *after* forwardMostMove
17723    is updated, so the last fractional tick must be subtracted
17724    from the color that is *not* on move now.
17725 */
17726 void
17727 SwitchClocks (int newMoveNr)
17728 {
17729     long lastTickLength;
17730     TimeMark now;
17731     int flagged = FALSE;
17732
17733     GetTimeMark(&now);
17734
17735     if (StopClockTimer() && appData.clockMode) {
17736         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17737         if (!WhiteOnMove(forwardMostMove)) {
17738             if(blackNPS >= 0) lastTickLength = 0;
17739             blackTimeRemaining -= lastTickLength;
17740            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17741 //         if(pvInfoList[forwardMostMove].time == -1)
17742                  pvInfoList[forwardMostMove].time =               // use GUI time
17743                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
17744         } else {
17745            if(whiteNPS >= 0) lastTickLength = 0;
17746            whiteTimeRemaining -= lastTickLength;
17747            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
17748 //         if(pvInfoList[forwardMostMove].time == -1)
17749                  pvInfoList[forwardMostMove].time =
17750                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
17751         }
17752         flagged = CheckFlags();
17753     }
17754     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
17755     CheckTimeControl();
17756
17757     if (flagged || !appData.clockMode) return;
17758
17759     switch (gameMode) {
17760       case MachinePlaysBlack:
17761       case MachinePlaysWhite:
17762       case BeginningOfGame:
17763         if (pausing) return;
17764         break;
17765
17766       case EditGame:
17767       case PlayFromGameFile:
17768       case IcsExamining:
17769         return;
17770
17771       default:
17772         break;
17773     }
17774
17775     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
17776         if(WhiteOnMove(forwardMostMove))
17777              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
17778         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
17779     }
17780
17781     tickStartTM = now;
17782     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17783       whiteTimeRemaining : blackTimeRemaining);
17784     StartClockTimer(intendedTickLength);
17785 }
17786
17787
17788 /* Stop both clocks */
17789 void
17790 StopClocks ()
17791 {
17792     long lastTickLength;
17793     TimeMark now;
17794
17795     if (!StopClockTimer()) return;
17796     if (!appData.clockMode) return;
17797
17798     GetTimeMark(&now);
17799
17800     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
17801     if (WhiteOnMove(forwardMostMove)) {
17802         if(whiteNPS >= 0) lastTickLength = 0;
17803         whiteTimeRemaining -= lastTickLength;
17804         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
17805     } else {
17806         if(blackNPS >= 0) lastTickLength = 0;
17807         blackTimeRemaining -= lastTickLength;
17808         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
17809     }
17810     CheckFlags();
17811 }
17812
17813 /* Start clock of player on move.  Time may have been reset, so
17814    if clock is already running, stop and restart it. */
17815 void
17816 StartClocks ()
17817 {
17818     (void) StopClockTimer(); /* in case it was running already */
17819     DisplayBothClocks();
17820     if (CheckFlags()) return;
17821
17822     if (!appData.clockMode) return;
17823     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
17824
17825     GetTimeMark(&tickStartTM);
17826     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
17827       whiteTimeRemaining : blackTimeRemaining);
17828
17829    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
17830     whiteNPS = blackNPS = -1;
17831     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
17832        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
17833         whiteNPS = first.nps;
17834     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
17835        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
17836         blackNPS = first.nps;
17837     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
17838         whiteNPS = second.nps;
17839     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
17840         blackNPS = second.nps;
17841     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
17842
17843     StartClockTimer(intendedTickLength);
17844 }
17845
17846 char *
17847 TimeString (long ms)
17848 {
17849     long second, minute, hour, day;
17850     char *sign = "";
17851     static char buf[32];
17852
17853     if (ms > 0 && ms <= 9900) {
17854       /* convert milliseconds to tenths, rounding up */
17855       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
17856
17857       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
17858       return buf;
17859     }
17860
17861     /* convert milliseconds to seconds, rounding up */
17862     /* use floating point to avoid strangeness of integer division
17863        with negative dividends on many machines */
17864     second = (long) floor(((double) (ms + 999L)) / 1000.0);
17865
17866     if (second < 0) {
17867         sign = "-";
17868         second = -second;
17869     }
17870
17871     day = second / (60 * 60 * 24);
17872     second = second % (60 * 60 * 24);
17873     hour = second / (60 * 60);
17874     second = second % (60 * 60);
17875     minute = second / 60;
17876     second = second % 60;
17877
17878     if (day > 0)
17879       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
17880               sign, day, hour, minute, second);
17881     else if (hour > 0)
17882       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
17883     else
17884       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
17885
17886     return buf;
17887 }
17888
17889
17890 /*
17891  * This is necessary because some C libraries aren't ANSI C compliant yet.
17892  */
17893 char *
17894 StrStr (char *string, char *match)
17895 {
17896     int i, length;
17897
17898     length = strlen(match);
17899
17900     for (i = strlen(string) - length; i >= 0; i--, string++)
17901       if (!strncmp(match, string, length))
17902         return string;
17903
17904     return NULL;
17905 }
17906
17907 char *
17908 StrCaseStr (char *string, char *match)
17909 {
17910     int i, j, length;
17911
17912     length = strlen(match);
17913
17914     for (i = strlen(string) - length; i >= 0; i--, string++) {
17915         for (j = 0; j < length; j++) {
17916             if (ToLower(match[j]) != ToLower(string[j]))
17917               break;
17918         }
17919         if (j == length) return string;
17920     }
17921
17922     return NULL;
17923 }
17924
17925 #ifndef _amigados
17926 int
17927 StrCaseCmp (char *s1, char *s2)
17928 {
17929     char c1, c2;
17930
17931     for (;;) {
17932         c1 = ToLower(*s1++);
17933         c2 = ToLower(*s2++);
17934         if (c1 > c2) return 1;
17935         if (c1 < c2) return -1;
17936         if (c1 == NULLCHAR) return 0;
17937     }
17938 }
17939
17940
17941 int
17942 ToLower (int c)
17943 {
17944     return isupper(c) ? tolower(c) : c;
17945 }
17946
17947
17948 int
17949 ToUpper (int c)
17950 {
17951     return islower(c) ? toupper(c) : c;
17952 }
17953 #endif /* !_amigados    */
17954
17955 char *
17956 StrSave (char *s)
17957 {
17958   char *ret;
17959
17960   if ((ret = (char *) malloc(strlen(s) + 1)))
17961     {
17962       safeStrCpy(ret, s, strlen(s)+1);
17963     }
17964   return ret;
17965 }
17966
17967 char *
17968 StrSavePtr (char *s, char **savePtr)
17969 {
17970     if (*savePtr) {
17971         free(*savePtr);
17972     }
17973     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
17974       safeStrCpy(*savePtr, s, strlen(s)+1);
17975     }
17976     return(*savePtr);
17977 }
17978
17979 char *
17980 PGNDate ()
17981 {
17982     time_t clock;
17983     struct tm *tm;
17984     char buf[MSG_SIZ];
17985
17986     clock = time((time_t *)NULL);
17987     tm = localtime(&clock);
17988     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
17989             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
17990     return StrSave(buf);
17991 }
17992
17993
17994 char *
17995 PositionToFEN (int move, char *overrideCastling, int moveCounts)
17996 {
17997     int i, j, fromX, fromY, toX, toY;
17998     int whiteToPlay, haveRights = nrCastlingRights;
17999     char buf[MSG_SIZ];
18000     char *p, *q;
18001     int emptycount;
18002     ChessSquare piece;
18003
18004     whiteToPlay = (gameMode == EditPosition) ?
18005       !blackPlaysFirst : (move % 2 == 0);
18006     p = buf;
18007
18008     /* Piece placement data */
18009     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18010         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
18011         emptycount = 0;
18012         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
18013             if (boards[move][i][j] == EmptySquare) {
18014                 emptycount++;
18015             } else { ChessSquare piece = boards[move][i][j];
18016                 if (emptycount > 0) {
18017                     if(emptycount<10) /* [HGM] can be >= 10 */
18018                         *p++ = '0' + emptycount;
18019                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18020                     emptycount = 0;
18021                 }
18022                 if(PieceToChar(piece) == '+') {
18023                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
18024                     *p++ = '+';
18025                     piece = (ChessSquare)(CHUDEMOTED(piece));
18026                 }
18027                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
18028                 if(*p = PieceSuffix(piece)) p++;
18029                 if(p[-1] == '~') {
18030                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
18031                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
18032                     *p++ = '~';
18033                 }
18034             }
18035         }
18036         if (emptycount > 0) {
18037             if(emptycount<10) /* [HGM] can be >= 10 */
18038                 *p++ = '0' + emptycount;
18039             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
18040             emptycount = 0;
18041         }
18042         *p++ = '/';
18043     }
18044     *(p - 1) = ' ';
18045
18046     /* [HGM] print Crazyhouse or Shogi holdings */
18047     if( gameInfo.holdingsWidth ) {
18048         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
18049         q = p;
18050         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
18051             piece = boards[move][i][BOARD_WIDTH-1];
18052             if( piece != EmptySquare )
18053               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
18054                   *p++ = PieceToChar(piece);
18055         }
18056         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
18057             piece = boards[move][BOARD_HEIGHT-i-1][0];
18058             if( piece != EmptySquare )
18059               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
18060                   *p++ = PieceToChar(piece);
18061         }
18062
18063         if( q == p ) *p++ = '-';
18064         *p++ = ']';
18065         *p++ = ' ';
18066     }
18067
18068     /* Active color */
18069     *p++ = whiteToPlay ? 'w' : 'b';
18070     *p++ = ' ';
18071
18072   if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18073     haveRights = 0; q = p;
18074     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18075       piece = boards[move][0][i];
18076       if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18077         if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
18078       }
18079     }
18080     for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
18081       piece = boards[move][BOARD_HEIGHT-1][i];
18082       if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
18083         if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
18084       }
18085     }
18086     if(p == q) *p++ = '-';
18087     *p++ = ' ';
18088   }
18089
18090   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
18091     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
18092   } else {
18093   if(haveRights) {
18094      int handW=0, handB=0;
18095      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
18096         for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
18097         for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
18098      }
18099      q = p;
18100      if(appData.fischerCastling) {
18101         if(handW) { // in shuffle S-Chess simply dump all virgin pieces
18102            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18103                if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18104         } else {
18105        /* [HGM] write directly from rights */
18106            if(boards[move][CASTLING][2] != NoRights &&
18107               boards[move][CASTLING][0] != NoRights   )
18108                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
18109            if(boards[move][CASTLING][2] != NoRights &&
18110               boards[move][CASTLING][1] != NoRights   )
18111                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
18112         }
18113         if(handB) {
18114            for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
18115                if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18116         } else {
18117            if(boards[move][CASTLING][5] != NoRights &&
18118               boards[move][CASTLING][3] != NoRights   )
18119                 *p++ = boards[move][CASTLING][3] + AAA;
18120            if(boards[move][CASTLING][5] != NoRights &&
18121               boards[move][CASTLING][4] != NoRights   )
18122                 *p++ = boards[move][CASTLING][4] + AAA;
18123         }
18124      } else {
18125
18126         /* [HGM] write true castling rights */
18127         if( nrCastlingRights == 6 ) {
18128             int q, k=0;
18129             if(boards[move][CASTLING][0] != NoRights &&
18130                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
18131             q = (boards[move][CASTLING][1] != NoRights &&
18132                  boards[move][CASTLING][2] != NoRights  );
18133             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
18134                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18135                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
18136                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
18137             }
18138             if(q) *p++ = 'Q';
18139             k = 0;
18140             if(boards[move][CASTLING][3] != NoRights &&
18141                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
18142             q = (boards[move][CASTLING][4] != NoRights &&
18143                  boards[move][CASTLING][5] != NoRights  );
18144             if(handB) {
18145                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
18146                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
18147                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
18148             }
18149             if(q) *p++ = 'q';
18150         }
18151      }
18152      if (q == p) *p++ = '-'; /* No castling rights */
18153      *p++ = ' ';
18154   }
18155
18156   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18157      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18158      gameInfo.variant != VariantMakruk   && gameInfo.variant != VariantASEAN ) {
18159     /* En passant target square */
18160     if (move > backwardMostMove) {
18161         fromX = moveList[move - 1][0] - AAA;
18162         fromY = moveList[move - 1][1] - ONE;
18163         toX = moveList[move - 1][2] - AAA;
18164         toY = moveList[move - 1][3] - ONE;
18165         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
18166             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
18167             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
18168             fromX == toX) {
18169             /* 2-square pawn move just happened */
18170             *p++ = toX + AAA;
18171             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18172         } else {
18173             *p++ = '-';
18174         }
18175     } else if(move == backwardMostMove) {
18176         // [HGM] perhaps we should always do it like this, and forget the above?
18177         if((signed char)boards[move][EP_STATUS] >= 0) {
18178             *p++ = boards[move][EP_STATUS] + AAA;
18179             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
18180         } else {
18181             *p++ = '-';
18182         }
18183     } else {
18184         *p++ = '-';
18185     }
18186     *p++ = ' ';
18187   }
18188   }
18189
18190     if(moveCounts)
18191     {   int i = 0, j=move;
18192
18193         /* [HGM] find reversible plies */
18194         if (appData.debugMode) { int k;
18195             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
18196             for(k=backwardMostMove; k<=forwardMostMove; k++)
18197                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
18198
18199         }
18200
18201         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
18202         if( j == backwardMostMove ) i += initialRulePlies;
18203         sprintf(p, "%d ", i);
18204         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
18205
18206         /* Fullmove number */
18207         sprintf(p, "%d", (move / 2) + 1);
18208     } else *--p = NULLCHAR;
18209
18210     return StrSave(buf);
18211 }
18212
18213 Boolean
18214 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
18215 {
18216     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
18217     char *p, c;
18218     int emptycount, virgin[BOARD_FILES];
18219     ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
18220
18221     p = fen;
18222
18223     /* Piece placement data */
18224     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
18225         j = 0;
18226         for (;;) {
18227             if (*p == '/' || *p == ' ' || *p == '[' ) {
18228                 if(j > w) w = j;
18229                 emptycount = gameInfo.boardWidth - j;
18230                 while (emptycount--)
18231                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18232                 if (*p == '/') p++;
18233                 else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
18234                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
18235                         for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
18236                     }
18237                     appData.NrRanks = gameInfo.boardHeight - i; i=0;
18238                 }
18239                 break;
18240 #if(BOARD_FILES >= 10)*0
18241             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
18242                 p++; emptycount=10;
18243                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18244                 while (emptycount--)
18245                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18246 #endif
18247             } else if (*p == '*') {
18248                 board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
18249             } else if (isdigit(*p)) {
18250                 emptycount = *p++ - '0';
18251                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
18252                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
18253                 while (emptycount--)
18254                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
18255             } else if (*p == '<') {
18256                 if(i == BOARD_HEIGHT-1) shuffle = 1;
18257                 else if (i != 0 || !shuffle) return FALSE;
18258                 p++;
18259             } else if (shuffle && *p == '>') {
18260                 p++; // for now ignore closing shuffle range, and assume rank-end
18261             } else if (*p == '?') {
18262                 if (j >= gameInfo.boardWidth) return FALSE;
18263                 if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
18264                 board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
18265             } else if (*p == '+' || isalpha(*p)) {
18266                 char *q, *s = SUFFIXES;
18267                 if (j >= gameInfo.boardWidth) return FALSE;
18268                 if(*p=='+') {
18269                     char c = *++p;
18270                     if(q = strchr(s, p[1])) p++;
18271                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18272                     if(piece == EmptySquare) return FALSE; /* unknown piece */
18273                     piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
18274                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
18275                 } else {
18276                     char c = *p++;
18277                     if(q = strchr(s, *p)) p++;
18278                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
18279                 }
18280
18281                 if(piece==EmptySquare) return FALSE; /* unknown piece */
18282                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
18283                     piece = (ChessSquare) (PROMOTED(piece));
18284                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
18285                     p++;
18286                 }
18287                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
18288                 if(piece == king) wKingRank = i;
18289                 if(piece == WHITE_TO_BLACK king) bKingRank = i;
18290             } else {
18291                 return FALSE;
18292             }
18293         }
18294     }
18295     while (*p == '/' || *p == ' ') p++;
18296
18297     if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
18298
18299     /* [HGM] by default clear Crazyhouse holdings, if present */
18300     if(gameInfo.holdingsWidth) {
18301        for(i=0; i<BOARD_HEIGHT; i++) {
18302            board[i][0]             = EmptySquare; /* black holdings */
18303            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
18304            board[i][1]             = (ChessSquare) 0; /* black counts */
18305            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
18306        }
18307     }
18308
18309     /* [HGM] look for Crazyhouse holdings here */
18310     while(*p==' ') p++;
18311     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
18312         int swap=0, wcnt=0, bcnt=0;
18313         if(*p == '[') p++;
18314         if(*p == '<') swap++, p++;
18315         if(*p == '-' ) p++; /* empty holdings */ else {
18316             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
18317             /* if we would allow FEN reading to set board size, we would   */
18318             /* have to add holdings and shift the board read so far here   */
18319             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
18320                 p++;
18321                 if((int) piece >= (int) BlackPawn ) {
18322                     i = (int)piece - (int)BlackPawn;
18323                     i = PieceToNumber((ChessSquare)i);
18324                     if( i >= gameInfo.holdingsSize ) return FALSE;
18325                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
18326                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
18327                     bcnt++;
18328                 } else {
18329                     i = (int)piece - (int)WhitePawn;
18330                     i = PieceToNumber((ChessSquare)i);
18331                     if( i >= gameInfo.holdingsSize ) return FALSE;
18332                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
18333                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
18334                     wcnt++;
18335                 }
18336             }
18337             if(subst) { // substitute back-rank question marks by holdings pieces
18338                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
18339                     int k, m, n = bcnt + 1;
18340                     if(board[0][j] == ClearBoard) {
18341                         if(!wcnt) return FALSE;
18342                         n = rand() % wcnt;
18343                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
18344                             board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
18345                             if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
18346                             break;
18347                         }
18348                     }
18349                     if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
18350                         if(!bcnt) return FALSE;
18351                         if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
18352                         for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
18353                             board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
18354                             if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
18355                             break;
18356                         }
18357                     }
18358                 }
18359                 subst = 0;
18360             }
18361         }
18362         if(*p == ']') p++;
18363     }
18364
18365     if(subst) return FALSE; // substitution requested, but no holdings
18366
18367     while(*p == ' ') p++;
18368
18369     /* Active color */
18370     c = *p++;
18371     if(appData.colorNickNames) {
18372       if( c == appData.colorNickNames[0] ) c = 'w'; else
18373       if( c == appData.colorNickNames[1] ) c = 'b';
18374     }
18375     switch (c) {
18376       case 'w':
18377         *blackPlaysFirst = FALSE;
18378         break;
18379       case 'b':
18380         *blackPlaysFirst = TRUE;
18381         break;
18382       default:
18383         return FALSE;
18384     }
18385
18386     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
18387     /* return the extra info in global variiables             */
18388
18389     while(*p==' ') p++;
18390
18391     if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
18392         if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
18393         if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
18394     }
18395
18396     /* set defaults in case FEN is incomplete */
18397     board[EP_STATUS] = EP_UNKNOWN;
18398     board[TOUCHED_W] = board[TOUCHED_B] = 0;
18399     for(i=0; i<nrCastlingRights; i++ ) {
18400         board[CASTLING][i] =
18401             appData.fischerCastling ? NoRights : initialRights[i];
18402     }   /* assume possible unless obviously impossible */
18403     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
18404     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
18405     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
18406                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
18407     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
18408     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
18409     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
18410                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
18411     FENrulePlies = 0;
18412
18413     if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
18414       char *q = p;
18415       int w=0, b=0;
18416       while(isalpha(*p)) {
18417         if(isupper(*p)) w |= 1 << (*p++ - 'A');
18418         if(islower(*p)) b |= 1 << (*p++ - 'a');
18419       }
18420       if(*p == '-') p++;
18421       if(p != q) {
18422         board[TOUCHED_W] = ~w;
18423         board[TOUCHED_B] = ~b;
18424         while(*p == ' ') p++;
18425       }
18426     } else
18427
18428     if(nrCastlingRights) {
18429       int fischer = 0;
18430       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
18431       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
18432           /* castling indicator present, so default becomes no castlings */
18433           for(i=0; i<nrCastlingRights; i++ ) {
18434                  board[CASTLING][i] = NoRights;
18435           }
18436       }
18437       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
18438              (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
18439              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
18440              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
18441         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
18442
18443         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
18444             if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
18445             if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
18446         }
18447         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
18448             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
18449         if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
18450                                      && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
18451         if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
18452                                      && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
18453         switch(c) {
18454           case'K':
18455               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
18456               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
18457               board[CASTLING][2] = whiteKingFile;
18458               if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
18459               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18460               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18461               break;
18462           case'Q':
18463               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
18464               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
18465               board[CASTLING][2] = whiteKingFile;
18466               if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
18467               if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
18468               if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18469               break;
18470           case'k':
18471               for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
18472               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
18473               board[CASTLING][5] = blackKingFile;
18474               if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
18475               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18476               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
18477               break;
18478           case'q':
18479               for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
18480               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
18481               board[CASTLING][5] = blackKingFile;
18482               if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
18483               if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
18484               if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
18485           case '-':
18486               break;
18487           default: /* FRC castlings */
18488               if(c >= 'a') { /* black rights */
18489                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
18490                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18491                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
18492                   if(i == BOARD_RGHT) break;
18493                   board[CASTLING][5] = i;
18494                   c -= AAA;
18495                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
18496                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
18497                   if(c > i)
18498                       board[CASTLING][3] = c;
18499                   else
18500                       board[CASTLING][4] = c;
18501               } else { /* white rights */
18502                   if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
18503                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
18504                     if(board[0][i] == WhiteKing) break;
18505                   if(i == BOARD_RGHT) break;
18506                   board[CASTLING][2] = i;
18507                   c -= AAA - 'a' + 'A';
18508                   if(board[0][c] >= WhiteKing) break;
18509                   if(c > i)
18510                       board[CASTLING][0] = c;
18511                   else
18512                       board[CASTLING][1] = c;
18513               }
18514         }
18515       }
18516       for(i=0; i<nrCastlingRights; i++)
18517         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
18518       if(gameInfo.variant == VariantSChess)
18519         for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
18520       if(fischer && shuffle) appData.fischerCastling = TRUE;
18521     if (appData.debugMode) {
18522         fprintf(debugFP, "FEN castling rights:");
18523         for(i=0; i<nrCastlingRights; i++)
18524         fprintf(debugFP, " %d", board[CASTLING][i]);
18525         fprintf(debugFP, "\n");
18526     }
18527
18528       while(*p==' ') p++;
18529     }
18530
18531     if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
18532
18533     /* read e.p. field in games that know e.p. capture */
18534     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
18535        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&
18536        gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) {
18537       if(*p=='-') {
18538         p++; board[EP_STATUS] = EP_NONE;
18539       } else {
18540          char c = *p++ - AAA;
18541
18542          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
18543          if(*p >= '0' && *p <='9') p++;
18544          board[EP_STATUS] = c;
18545       }
18546     }
18547
18548
18549     if(sscanf(p, "%d", &i) == 1) {
18550         FENrulePlies = i; /* 50-move ply counter */
18551         /* (The move number is still ignored)    */
18552     }
18553
18554     return TRUE;
18555 }
18556
18557 void
18558 EditPositionPasteFEN (char *fen)
18559 {
18560   if (fen != NULL) {
18561     Board initial_position;
18562
18563     if (!ParseFEN(initial_position, &blackPlaysFirst, fen, TRUE)) {
18564       DisplayError(_("Bad FEN position in clipboard"), 0);
18565       return ;
18566     } else {
18567       int savedBlackPlaysFirst = blackPlaysFirst;
18568       EditPositionEvent();
18569       blackPlaysFirst = savedBlackPlaysFirst;
18570       CopyBoard(boards[0], initial_position);
18571       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
18572       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
18573       DisplayBothClocks();
18574       DrawPosition(FALSE, boards[currentMove]);
18575     }
18576   }
18577 }
18578
18579 static char cseq[12] = "\\   ";
18580
18581 Boolean
18582 set_cont_sequence (char *new_seq)
18583 {
18584     int len;
18585     Boolean ret;
18586
18587     // handle bad attempts to set the sequence
18588         if (!new_seq)
18589                 return 0; // acceptable error - no debug
18590
18591     len = strlen(new_seq);
18592     ret = (len > 0) && (len < sizeof(cseq));
18593     if (ret)
18594       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
18595     else if (appData.debugMode)
18596       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
18597     return ret;
18598 }
18599
18600 /*
18601     reformat a source message so words don't cross the width boundary.  internal
18602     newlines are not removed.  returns the wrapped size (no null character unless
18603     included in source message).  If dest is NULL, only calculate the size required
18604     for the dest buffer.  lp argument indicats line position upon entry, and it's
18605     passed back upon exit.
18606 */
18607 int
18608 wrap (char *dest, char *src, int count, int width, int *lp)
18609 {
18610     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
18611
18612     cseq_len = strlen(cseq);
18613     old_line = line = *lp;
18614     ansi = len = clen = 0;
18615
18616     for (i=0; i < count; i++)
18617     {
18618         if (src[i] == '\033')
18619             ansi = 1;
18620
18621         // if we hit the width, back up
18622         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
18623         {
18624             // store i & len in case the word is too long
18625             old_i = i, old_len = len;
18626
18627             // find the end of the last word
18628             while (i && src[i] != ' ' && src[i] != '\n')
18629             {
18630                 i--;
18631                 len--;
18632             }
18633
18634             // word too long?  restore i & len before splitting it
18635             if ((old_i-i+clen) >= width)
18636             {
18637                 i = old_i;
18638                 len = old_len;
18639             }
18640
18641             // extra space?
18642             if (i && src[i-1] == ' ')
18643                 len--;
18644
18645             if (src[i] != ' ' && src[i] != '\n')
18646             {
18647                 i--;
18648                 if (len)
18649                     len--;
18650             }
18651
18652             // now append the newline and continuation sequence
18653             if (dest)
18654                 dest[len] = '\n';
18655             len++;
18656             if (dest)
18657                 strncpy(dest+len, cseq, cseq_len);
18658             len += cseq_len;
18659             line = cseq_len;
18660             clen = cseq_len;
18661             continue;
18662         }
18663
18664         if (dest)
18665             dest[len] = src[i];
18666         len++;
18667         if (!ansi)
18668             line++;
18669         if (src[i] == '\n')
18670             line = 0;
18671         if (src[i] == 'm')
18672             ansi = 0;
18673     }
18674     if (dest && appData.debugMode)
18675     {
18676         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
18677             count, width, line, len, *lp);
18678         show_bytes(debugFP, src, count);
18679         fprintf(debugFP, "\ndest: ");
18680         show_bytes(debugFP, dest, len);
18681         fprintf(debugFP, "\n");
18682     }
18683     *lp = dest ? line : old_line;
18684
18685     return len;
18686 }
18687
18688 // [HGM] vari: routines for shelving variations
18689 Boolean modeRestore = FALSE;
18690
18691 void
18692 PushInner (int firstMove, int lastMove)
18693 {
18694         int i, j, nrMoves = lastMove - firstMove;
18695
18696         // push current tail of game on stack
18697         savedResult[storedGames] = gameInfo.result;
18698         savedDetails[storedGames] = gameInfo.resultDetails;
18699         gameInfo.resultDetails = NULL;
18700         savedFirst[storedGames] = firstMove;
18701         savedLast [storedGames] = lastMove;
18702         savedFramePtr[storedGames] = framePtr;
18703         framePtr -= nrMoves; // reserve space for the boards
18704         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
18705             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
18706             for(j=0; j<MOVE_LEN; j++)
18707                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
18708             for(j=0; j<2*MOVE_LEN; j++)
18709                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
18710             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
18711             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
18712             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
18713             pvInfoList[firstMove+i-1].depth = 0;
18714             commentList[framePtr+i] = commentList[firstMove+i];
18715             commentList[firstMove+i] = NULL;
18716         }
18717
18718         storedGames++;
18719         forwardMostMove = firstMove; // truncate game so we can start variation
18720 }
18721
18722 void
18723 PushTail (int firstMove, int lastMove)
18724 {
18725         if(appData.icsActive) { // only in local mode
18726                 forwardMostMove = currentMove; // mimic old ICS behavior
18727                 return;
18728         }
18729         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
18730
18731         PushInner(firstMove, lastMove);
18732         if(storedGames == 1) GreyRevert(FALSE);
18733         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
18734 }
18735
18736 void
18737 PopInner (Boolean annotate)
18738 {
18739         int i, j, nrMoves;
18740         char buf[8000], moveBuf[20];
18741
18742         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
18743         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
18744         nrMoves = savedLast[storedGames] - currentMove;
18745         if(annotate) {
18746                 int cnt = 10;
18747                 if(!WhiteOnMove(currentMove))
18748                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
18749                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
18750                 for(i=currentMove; i<forwardMostMove; i++) {
18751                         if(WhiteOnMove(i))
18752                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
18753                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
18754                         strcat(buf, moveBuf);
18755                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
18756                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
18757                 }
18758                 strcat(buf, ")");
18759         }
18760         for(i=1; i<=nrMoves; i++) { // copy last variation back
18761             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
18762             for(j=0; j<MOVE_LEN; j++)
18763                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
18764             for(j=0; j<2*MOVE_LEN; j++)
18765                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
18766             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
18767             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
18768             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
18769             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
18770             commentList[currentMove+i] = commentList[framePtr+i];
18771             commentList[framePtr+i] = NULL;
18772         }
18773         if(annotate) AppendComment(currentMove+1, buf, FALSE);
18774         framePtr = savedFramePtr[storedGames];
18775         gameInfo.result = savedResult[storedGames];
18776         if(gameInfo.resultDetails != NULL) {
18777             free(gameInfo.resultDetails);
18778       }
18779         gameInfo.resultDetails = savedDetails[storedGames];
18780         forwardMostMove = currentMove + nrMoves;
18781 }
18782
18783 Boolean
18784 PopTail (Boolean annotate)
18785 {
18786         if(appData.icsActive) return FALSE; // only in local mode
18787         if(!storedGames) return FALSE; // sanity
18788         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
18789
18790         PopInner(annotate);
18791         if(currentMove < forwardMostMove) ForwardEvent(); else
18792         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
18793
18794         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
18795         return TRUE;
18796 }
18797
18798 void
18799 CleanupTail ()
18800 {       // remove all shelved variations
18801         int i;
18802         for(i=0; i<storedGames; i++) {
18803             if(savedDetails[i])
18804                 free(savedDetails[i]);
18805             savedDetails[i] = NULL;
18806         }
18807         for(i=framePtr; i<MAX_MOVES; i++) {
18808                 if(commentList[i]) free(commentList[i]);
18809                 commentList[i] = NULL;
18810         }
18811         framePtr = MAX_MOVES-1;
18812         storedGames = 0;
18813 }
18814
18815 void
18816 LoadVariation (int index, char *text)
18817 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
18818         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
18819         int level = 0, move;
18820
18821         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
18822         // first find outermost bracketing variation
18823         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
18824             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
18825                 if(*p == '{') wait = '}'; else
18826                 if(*p == '[') wait = ']'; else
18827                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
18828                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
18829             }
18830             if(*p == wait) wait = NULLCHAR; // closing ]} found
18831             p++;
18832         }
18833         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
18834         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
18835         end[1] = NULLCHAR; // clip off comment beyond variation
18836         ToNrEvent(currentMove-1);
18837         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
18838         // kludge: use ParsePV() to append variation to game
18839         move = currentMove;
18840         ParsePV(start, TRUE, TRUE);
18841         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
18842         ClearPremoveHighlights();
18843         CommentPopDown();
18844         ToNrEvent(currentMove+1);
18845 }
18846
18847 void
18848 LoadTheme ()
18849 {
18850     char *p, *q, buf[MSG_SIZ];
18851     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
18852         snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
18853         ParseArgsFromString(buf);
18854         ActivateTheme(TRUE); // also redo colors
18855         return;
18856     }
18857     p = nickName;
18858     if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
18859     {
18860         int len;
18861         q = appData.themeNames;
18862         snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
18863       if(appData.useBitmaps) {
18864         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
18865                 appData.liteBackTextureFile, appData.darkBackTextureFile,
18866                 appData.liteBackTextureMode,
18867                 appData.darkBackTextureMode );
18868       } else {
18869         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
18870                 Col2Text(2),   // lightSquareColor
18871                 Col2Text(3) ); // darkSquareColor
18872       }
18873       if(appData.useBorder) {
18874         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
18875                 appData.border);
18876       } else {
18877         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
18878       }
18879       if(appData.useFont) {
18880         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
18881                 appData.renderPiecesWithFont,
18882                 appData.fontToPieceTable,
18883                 Col2Text(9),    // appData.fontBackColorWhite
18884                 Col2Text(10) ); // appData.fontForeColorBlack
18885       } else {
18886         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
18887                 appData.pieceDirectory);
18888         if(!appData.pieceDirectory[0])
18889           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
18890                 Col2Text(0),   // whitePieceColor
18891                 Col2Text(1) ); // blackPieceColor
18892       }
18893       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
18894                 Col2Text(4),   // highlightSquareColor
18895                 Col2Text(5) ); // premoveHighlightColor
18896         appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
18897         if(insert != q) insert[-1] = NULLCHAR;
18898         snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
18899         if(q)   free(q);
18900     }
18901     ActivateTheme(FALSE);
18902 }