Use ngettext() instead of gettext() for a string to allow better translation.
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second, pairing;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int movesPerSession;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
505
506 ChessSquare  FIDEArray[2][BOARD_FILES] = {
507     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510         BlackKing, BlackBishop, BlackKnight, BlackRook }
511 };
512
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517         BlackKing, BlackKing, BlackKnight, BlackRook }
518 };
519
520 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
521     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523     { BlackRook, BlackMan, BlackBishop, BlackQueen,
524         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 };
526
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 };
533
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 };
540
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 };
547
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551     { BlackRook, BlackKnight, BlackMan, BlackFerz,
552         BlackKing, BlackMan, BlackKnight, BlackRook }
553 };
554
555
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 };
563
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 };
570
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 };
577
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 };
584
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
590 };
591
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
597 };
598
599 #ifdef GOTHIC
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 };
606 #else // !GOTHIC
607 #define GothicArray CapablancaArray
608 #endif // !GOTHIC
609
610 #ifdef FALCON
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !FALCON
618 #define FalconArray CapablancaArray
619 #endif // !FALCON
620
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
627
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 };
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
638
639
640 Board initialPosition;
641
642
643 /* Convert str to a rating. Checks for special cases of "----",
644
645    "++++", etc. Also strips ()'s */
646 int
647 string_to_rating (char *str)
648 {
649   while(*str && !isdigit(*str)) ++str;
650   if (!*str)
651     return 0;   /* One of the special "no rating" cases */
652   else
653     return atoi(str);
654 }
655
656 void
657 ClearProgramStats ()
658 {
659     /* Init programStats */
660     programStats.movelist[0] = 0;
661     programStats.depth = 0;
662     programStats.nr_moves = 0;
663     programStats.moves_left = 0;
664     programStats.nodes = 0;
665     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
666     programStats.score = 0;
667     programStats.got_only_move = 0;
668     programStats.got_fail = 0;
669     programStats.line_is_book = 0;
670 }
671
672 void
673 CommonEngineInit ()
674 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675     if (appData.firstPlaysBlack) {
676         first.twoMachinesColor = "black\n";
677         second.twoMachinesColor = "white\n";
678     } else {
679         first.twoMachinesColor = "white\n";
680         second.twoMachinesColor = "black\n";
681     }
682
683     first.other = &second;
684     second.other = &first;
685
686     { float norm = 1;
687         if(appData.timeOddsMode) {
688             norm = appData.timeOdds[0];
689             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690         }
691         first.timeOdds  = appData.timeOdds[0]/norm;
692         second.timeOdds = appData.timeOdds[1]/norm;
693     }
694
695     if(programVersion) free(programVersion);
696     if (appData.noChessProgram) {
697         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698         sprintf(programVersion, "%s", PACKAGE_STRING);
699     } else {
700       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
703     }
704 }
705
706 void
707 UnloadEngine (ChessProgramState *cps)
708 {
709         /* Kill off first chess program */
710         if (cps->isr != NULL)
711           RemoveInputSource(cps->isr);
712         cps->isr = NULL;
713
714         if (cps->pr != NoProc) {
715             ExitAnalyzeMode();
716             DoSleep( appData.delayBeforeQuit );
717             SendToProgram("quit\n", cps);
718             DoSleep( appData.delayAfterQuit );
719             DestroyChildProcess(cps->pr, cps->useSigterm);
720         }
721         cps->pr = NoProc;
722         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
723 }
724
725 void
726 ClearOptions (ChessProgramState *cps)
727 {
728     int i;
729     cps->nrOptions = cps->comboCnt = 0;
730     for(i=0; i<MAX_OPTIONS; i++) {
731         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732         cps->option[i].textValue = 0;
733     }
734 }
735
736 char *engineNames[] = {
737 "first",
738 "second"
739 };
740
741 void
742 InitEngine (ChessProgramState *cps, int n)
743 {   // [HGM] all engine initialiation put in a function that does one engine
744
745     ClearOptions(cps);
746
747     cps->which = engineNames[n];
748     cps->maybeThinking = FALSE;
749     cps->pr = NoProc;
750     cps->isr = NULL;
751     cps->sendTime = 2;
752     cps->sendDrawOffers = 1;
753
754     cps->program = appData.chessProgram[n];
755     cps->host = appData.host[n];
756     cps->dir = appData.directory[n];
757     cps->initString = appData.engInitString[n];
758     cps->computerString = appData.computerString[n];
759     cps->useSigint  = TRUE;
760     cps->useSigterm = TRUE;
761     cps->reuse = appData.reuse[n];
762     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
763     cps->useSetboard = FALSE;
764     cps->useSAN = FALSE;
765     cps->usePing = FALSE;
766     cps->lastPing = 0;
767     cps->lastPong = 0;
768     cps->usePlayother = FALSE;
769     cps->useColors = TRUE;
770     cps->useUsermove = FALSE;
771     cps->sendICS = FALSE;
772     cps->sendName = appData.icsActive;
773     cps->sdKludge = FALSE;
774     cps->stKludge = FALSE;
775     TidyProgramName(cps->program, cps->host, cps->tidy);
776     cps->matchWins = 0;
777     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778     cps->analysisSupport = 2; /* detect */
779     cps->analyzing = FALSE;
780     cps->initDone = FALSE;
781
782     /* New features added by Tord: */
783     cps->useFEN960 = FALSE;
784     cps->useOOCastle = TRUE;
785     /* End of new features added by Tord. */
786     cps->fenOverride  = appData.fenOverride[n];
787
788     /* [HGM] time odds: set factor for each machine */
789     cps->timeOdds  = appData.timeOdds[n];
790
791     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792     cps->accumulateTC = appData.accumulateTC[n];
793     cps->maxNrOfSessions = 1;
794
795     /* [HGM] debug */
796     cps->debug = FALSE;
797
798     cps->supportsNPS = UNKNOWN;
799     cps->memSize = FALSE;
800     cps->maxCores = FALSE;
801     cps->egtFormats[0] = NULLCHAR;
802
803     /* [HGM] options */
804     cps->optionSettings  = appData.engOptions[n];
805
806     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807     cps->isUCI = appData.isUCI[n]; /* [AS] */
808     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809
810     if (appData.protocolVersion[n] > PROTOVER
811         || appData.protocolVersion[n] < 1)
812       {
813         char buf[MSG_SIZ];
814         int len;
815
816         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817                        appData.protocolVersion[n]);
818         if( (len >= MSG_SIZ) && appData.debugMode )
819           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820
821         DisplayFatalError(buf, 0, 2);
822       }
823     else
824       {
825         cps->protocolVersion = appData.protocolVersion[n];
826       }
827
828     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
829     ParseFeatures(appData.featureDefaults, cps);
830 }
831
832 ChessProgramState *savCps;
833
834 void
835 LoadEngine ()
836 {
837     int i;
838     if(WaitForEngine(savCps, LoadEngine)) return;
839     CommonEngineInit(); // recalculate time odds
840     if(gameInfo.variant != StringToVariant(appData.variant)) {
841         // we changed variant when loading the engine; this forces us to reset
842         Reset(TRUE, savCps != &first);
843         EditGameEvent(); // for consistency with other path, as Reset changes mode
844     }
845     InitChessProgram(savCps, FALSE);
846     SendToProgram("force\n", savCps);
847     DisplayMessage("", "");
848     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
850     ThawUI();
851     SetGNUMode();
852 }
853
854 void
855 ReplaceEngine (ChessProgramState *cps, int n)
856 {
857     EditGameEvent();
858     UnloadEngine(cps);
859     appData.noChessProgram = FALSE;
860     appData.clockMode = TRUE;
861     InitEngine(cps, n);
862     UpdateLogos(TRUE);
863     if(n) return; // only startup first engine immediately; second can wait
864     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
865     LoadEngine();
866 }
867
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870
871 static char resetOptions[] = 
872         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 FloatToFront(char **list, char *engineLine)
878 {
879     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
880     int i=0;
881     if(appData.recentEngines <= 0) return;
882     TidyProgramName(engineLine, "localhost", tidy+1);
883     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
884     strncpy(buf+1, *list, MSG_SIZ-50);
885     if(p = strstr(buf, tidy)) { // tidy name appears in list
886         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
887         while(*p++ = *++q); // squeeze out
888     }
889     strcat(tidy, buf+1); // put list behind tidy name
890     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
891     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
892     ASSIGN(*list, tidy+1);
893 }
894
895 void
896 Load (ChessProgramState *cps, int i)
897 {
898     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
899     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
900         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
901         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
902         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
903         appData.firstProtocolVersion = PROTOVER;
904         ParseArgsFromString(buf);
905         SwapEngines(i);
906         ReplaceEngine(cps, i);
907         FloatToFront(&appData.recentEngineList, engineLine);
908         return;
909     }
910     p = engineName;
911     while(q = strchr(p, SLASH)) p = q+1;
912     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
913     if(engineDir[0] != NULLCHAR)
914         appData.directory[i] = engineDir;
915     else if(p != engineName) { // derive directory from engine path, when not given
916         p[-1] = 0;
917         appData.directory[i] = strdup(engineName);
918         p[-1] = SLASH;
919     } else appData.directory[i] = ".";
920     if(params[0]) {
921         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
922         snprintf(command, MSG_SIZ, "%s %s", p, params);
923         p = command;
924     }
925     appData.chessProgram[i] = strdup(p);
926     appData.isUCI[i] = isUCI;
927     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
928     appData.hasOwnBookUCI[i] = hasBook;
929     if(!nickName[0]) useNick = FALSE;
930     if(useNick) ASSIGN(appData.pgnName[i], nickName);
931     if(addToList) {
932         int len;
933         char quote;
934         q = firstChessProgramNames;
935         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
936         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
937         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
938                         quote, p, quote, appData.directory[i], 
939                         useNick ? " -fn \"" : "",
940                         useNick ? nickName : "",
941                         useNick ? "\"" : "",
942                         v1 ? " -firstProtocolVersion 1" : "",
943                         hasBook ? "" : " -fNoOwnBookUCI",
944                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
945                         storeVariant ? " -variant " : "",
946                         storeVariant ? VariantName(gameInfo.variant) : "");
947         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
948         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
949         if(q)   free(q);
950         FloatToFront(&appData.recentEngineList, buf);
951     }
952     ReplaceEngine(cps, i);
953 }
954
955 void
956 InitTimeControls ()
957 {
958     int matched, min, sec;
959     /*
960      * Parse timeControl resource
961      */
962     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
963                           appData.movesPerSession)) {
964         char buf[MSG_SIZ];
965         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
966         DisplayFatalError(buf, 0, 2);
967     }
968
969     /*
970      * Parse searchTime resource
971      */
972     if (*appData.searchTime != NULLCHAR) {
973         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
974         if (matched == 1) {
975             searchTime = min * 60;
976         } else if (matched == 2) {
977             searchTime = min * 60 + sec;
978         } else {
979             char buf[MSG_SIZ];
980             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
981             DisplayFatalError(buf, 0, 2);
982         }
983     }
984 }
985
986 void
987 InitBackEnd1 ()
988 {
989
990     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
991     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
992
993     GetTimeMark(&programStartTime);
994     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
995     appData.seedBase = random() + (random()<<15);
996     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
997
998     ClearProgramStats();
999     programStats.ok_to_send = 1;
1000     programStats.seen_stat = 0;
1001
1002     /*
1003      * Initialize game list
1004      */
1005     ListNew(&gameList);
1006
1007
1008     /*
1009      * Internet chess server status
1010      */
1011     if (appData.icsActive) {
1012         appData.matchMode = FALSE;
1013         appData.matchGames = 0;
1014 #if ZIPPY
1015         appData.noChessProgram = !appData.zippyPlay;
1016 #else
1017         appData.zippyPlay = FALSE;
1018         appData.zippyTalk = FALSE;
1019         appData.noChessProgram = TRUE;
1020 #endif
1021         if (*appData.icsHelper != NULLCHAR) {
1022             appData.useTelnet = TRUE;
1023             appData.telnetProgram = appData.icsHelper;
1024         }
1025     } else {
1026         appData.zippyTalk = appData.zippyPlay = FALSE;
1027     }
1028
1029     /* [AS] Initialize pv info list [HGM] and game state */
1030     {
1031         int i, j;
1032
1033         for( i=0; i<=framePtr; i++ ) {
1034             pvInfoList[i].depth = -1;
1035             boards[i][EP_STATUS] = EP_NONE;
1036             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1037         }
1038     }
1039
1040     InitTimeControls();
1041
1042     /* [AS] Adjudication threshold */
1043     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1044
1045     InitEngine(&first, 0);
1046     InitEngine(&second, 1);
1047     CommonEngineInit();
1048
1049     pairing.which = "pairing"; // pairing engine
1050     pairing.pr = NoProc;
1051     pairing.isr = NULL;
1052     pairing.program = appData.pairingEngine;
1053     pairing.host = "localhost";
1054     pairing.dir = ".";
1055
1056     if (appData.icsActive) {
1057         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1058     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1059         appData.clockMode = FALSE;
1060         first.sendTime = second.sendTime = 0;
1061     }
1062
1063 #if ZIPPY
1064     /* Override some settings from environment variables, for backward
1065        compatibility.  Unfortunately it's not feasible to have the env
1066        vars just set defaults, at least in xboard.  Ugh.
1067     */
1068     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1069       ZippyInit();
1070     }
1071 #endif
1072
1073     if (!appData.icsActive) {
1074       char buf[MSG_SIZ];
1075       int len;
1076
1077       /* Check for variants that are supported only in ICS mode,
1078          or not at all.  Some that are accepted here nevertheless
1079          have bugs; see comments below.
1080       */
1081       VariantClass variant = StringToVariant(appData.variant);
1082       switch (variant) {
1083       case VariantBughouse:     /* need four players and two boards */
1084       case VariantKriegspiel:   /* need to hide pieces and move details */
1085         /* case VariantFischeRandom: (Fabien: moved below) */
1086         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1087         if( (len >= MSG_SIZ) && appData.debugMode )
1088           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1089
1090         DisplayFatalError(buf, 0, 2);
1091         return;
1092
1093       case VariantUnknown:
1094       case VariantLoadable:
1095       case Variant29:
1096       case Variant30:
1097       case Variant31:
1098       case Variant32:
1099       case Variant33:
1100       case Variant34:
1101       case Variant35:
1102       case Variant36:
1103       default:
1104         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1105         if( (len >= MSG_SIZ) && appData.debugMode )
1106           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1107
1108         DisplayFatalError(buf, 0, 2);
1109         return;
1110
1111       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1112       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1113       case VariantGothic:     /* [HGM] should work */
1114       case VariantCapablanca: /* [HGM] should work */
1115       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1116       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1117       case VariantKnightmate: /* [HGM] should work */
1118       case VariantCylinder:   /* [HGM] untested */
1119       case VariantFalcon:     /* [HGM] untested */
1120       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1121                                  offboard interposition not understood */
1122       case VariantNormal:     /* definitely works! */
1123       case VariantWildCastle: /* pieces not automatically shuffled */
1124       case VariantNoCastle:   /* pieces not automatically shuffled */
1125       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1126       case VariantLosers:     /* should work except for win condition,
1127                                  and doesn't know captures are mandatory */
1128       case VariantSuicide:    /* should work except for win condition,
1129                                  and doesn't know captures are mandatory */
1130       case VariantGiveaway:   /* should work except for win condition,
1131                                  and doesn't know captures are mandatory */
1132       case VariantTwoKings:   /* should work */
1133       case VariantAtomic:     /* should work except for win condition */
1134       case Variant3Check:     /* should work except for win condition */
1135       case VariantShatranj:   /* should work except for all win conditions */
1136       case VariantMakruk:     /* should work except for draw countdown */
1137       case VariantBerolina:   /* might work if TestLegality is off */
1138       case VariantCapaRandom: /* should work */
1139       case VariantJanus:      /* should work */
1140       case VariantSuper:      /* experimental */
1141       case VariantGreat:      /* experimental, requires legality testing to be off */
1142       case VariantSChess:     /* S-Chess, should work */
1143       case VariantGrand:      /* should work */
1144       case VariantSpartan:    /* should work */
1145         break;
1146       }
1147     }
1148
1149 }
1150
1151 int
1152 NextIntegerFromString (char ** str, long * value)
1153 {
1154     int result = -1;
1155     char * s = *str;
1156
1157     while( *s == ' ' || *s == '\t' ) {
1158         s++;
1159     }
1160
1161     *value = 0;
1162
1163     if( *s >= '0' && *s <= '9' ) {
1164         while( *s >= '0' && *s <= '9' ) {
1165             *value = *value * 10 + (*s - '0');
1166             s++;
1167         }
1168
1169         result = 0;
1170     }
1171
1172     *str = s;
1173
1174     return result;
1175 }
1176
1177 int
1178 NextTimeControlFromString (char ** str, long * value)
1179 {
1180     long temp;
1181     int result = NextIntegerFromString( str, &temp );
1182
1183     if( result == 0 ) {
1184         *value = temp * 60; /* Minutes */
1185         if( **str == ':' ) {
1186             (*str)++;
1187             result = NextIntegerFromString( str, &temp );
1188             *value += temp; /* Seconds */
1189         }
1190     }
1191
1192     return result;
1193 }
1194
1195 int
1196 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1197 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1198     int result = -1, type = 0; long temp, temp2;
1199
1200     if(**str != ':') return -1; // old params remain in force!
1201     (*str)++;
1202     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1203     if( NextIntegerFromString( str, &temp ) ) return -1;
1204     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1205
1206     if(**str != '/') {
1207         /* time only: incremental or sudden-death time control */
1208         if(**str == '+') { /* increment follows; read it */
1209             (*str)++;
1210             if(**str == '!') type = *(*str)++; // Bronstein TC
1211             if(result = NextIntegerFromString( str, &temp2)) return -1;
1212             *inc = temp2 * 1000;
1213             if(**str == '.') { // read fraction of increment
1214                 char *start = ++(*str);
1215                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1216                 temp2 *= 1000;
1217                 while(start++ < *str) temp2 /= 10;
1218                 *inc += temp2;
1219             }
1220         } else *inc = 0;
1221         *moves = 0; *tc = temp * 1000; *incType = type;
1222         return 0;
1223     }
1224
1225     (*str)++; /* classical time control */
1226     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1227
1228     if(result == 0) {
1229         *moves = temp;
1230         *tc    = temp2 * 1000;
1231         *inc   = 0;
1232         *incType = type;
1233     }
1234     return result;
1235 }
1236
1237 int
1238 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1239 {   /* [HGM] get time to add from the multi-session time-control string */
1240     int incType, moves=1; /* kludge to force reading of first session */
1241     long time, increment;
1242     char *s = tcString;
1243
1244     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1245     do {
1246         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1247         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1248         if(movenr == -1) return time;    /* last move before new session     */
1249         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1250         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1251         if(!moves) return increment;     /* current session is incremental   */
1252         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1253     } while(movenr >= -1);               /* try again for next session       */
1254
1255     return 0; // no new time quota on this move
1256 }
1257
1258 int
1259 ParseTimeControl (char *tc, float ti, int mps)
1260 {
1261   long tc1;
1262   long tc2;
1263   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1264   int min, sec=0;
1265
1266   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1267   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1268       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1269   if(ti > 0) {
1270
1271     if(mps)
1272       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1273     else 
1274       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1275   } else {
1276     if(mps)
1277       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1278     else 
1279       snprintf(buf, MSG_SIZ, ":%s", mytc);
1280   }
1281   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1282   
1283   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1284     return FALSE;
1285   }
1286
1287   if( *tc == '/' ) {
1288     /* Parse second time control */
1289     tc++;
1290
1291     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1292       return FALSE;
1293     }
1294
1295     if( tc2 == 0 ) {
1296       return FALSE;
1297     }
1298
1299     timeControl_2 = tc2 * 1000;
1300   }
1301   else {
1302     timeControl_2 = 0;
1303   }
1304
1305   if( tc1 == 0 ) {
1306     return FALSE;
1307   }
1308
1309   timeControl = tc1 * 1000;
1310
1311   if (ti >= 0) {
1312     timeIncrement = ti * 1000;  /* convert to ms */
1313     movesPerSession = 0;
1314   } else {
1315     timeIncrement = 0;
1316     movesPerSession = mps;
1317   }
1318   return TRUE;
1319 }
1320
1321 void
1322 InitBackEnd2 ()
1323 {
1324     if (appData.debugMode) {
1325         fprintf(debugFP, "%s\n", programVersion);
1326     }
1327     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1328
1329     set_cont_sequence(appData.wrapContSeq);
1330     if (appData.matchGames > 0) {
1331         appData.matchMode = TRUE;
1332     } else if (appData.matchMode) {
1333         appData.matchGames = 1;
1334     }
1335     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1336         appData.matchGames = appData.sameColorGames;
1337     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1338         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1339         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1340     }
1341     Reset(TRUE, FALSE);
1342     if (appData.noChessProgram || first.protocolVersion == 1) {
1343       InitBackEnd3();
1344     } else {
1345       /* kludge: allow timeout for initial "feature" commands */
1346       FreezeUI();
1347       DisplayMessage("", _("Starting chess program"));
1348       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1349     }
1350 }
1351
1352 int
1353 CalculateIndex (int index, int gameNr)
1354 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1355     int res;
1356     if(index > 0) return index; // fixed nmber
1357     if(index == 0) return 1;
1358     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1359     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1360     return res;
1361 }
1362
1363 int
1364 LoadGameOrPosition (int gameNr)
1365 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1366     if (*appData.loadGameFile != NULLCHAR) {
1367         if (!LoadGameFromFile(appData.loadGameFile,
1368                 CalculateIndex(appData.loadGameIndex, gameNr),
1369                               appData.loadGameFile, FALSE)) {
1370             DisplayFatalError(_("Bad game file"), 0, 1);
1371             return 0;
1372         }
1373     } else if (*appData.loadPositionFile != NULLCHAR) {
1374         if (!LoadPositionFromFile(appData.loadPositionFile,
1375                 CalculateIndex(appData.loadPositionIndex, gameNr),
1376                                   appData.loadPositionFile)) {
1377             DisplayFatalError(_("Bad position file"), 0, 1);
1378             return 0;
1379         }
1380     }
1381     return 1;
1382 }
1383
1384 void
1385 ReserveGame (int gameNr, char resChar)
1386 {
1387     FILE *tf = fopen(appData.tourneyFile, "r+");
1388     char *p, *q, c, buf[MSG_SIZ];
1389     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1390     safeStrCpy(buf, lastMsg, MSG_SIZ);
1391     DisplayMessage(_("Pick new game"), "");
1392     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1393     ParseArgsFromFile(tf);
1394     p = q = appData.results;
1395     if(appData.debugMode) {
1396       char *r = appData.participants;
1397       fprintf(debugFP, "results = '%s'\n", p);
1398       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1399       fprintf(debugFP, "\n");
1400     }
1401     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1402     nextGame = q - p;
1403     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1404     safeStrCpy(q, p, strlen(p) + 2);
1405     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1406     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1407     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1408         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1409         q[nextGame] = '*';
1410     }
1411     fseek(tf, -(strlen(p)+4), SEEK_END);
1412     c = fgetc(tf);
1413     if(c != '"') // depending on DOS or Unix line endings we can be one off
1414          fseek(tf, -(strlen(p)+2), SEEK_END);
1415     else fseek(tf, -(strlen(p)+3), SEEK_END);
1416     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1417     DisplayMessage(buf, "");
1418     free(p); appData.results = q;
1419     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1420        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1421       int round = appData.defaultMatchGames * appData.tourneyType;
1422       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1423          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1424         UnloadEngine(&first);  // next game belongs to other pairing;
1425         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1426     }
1427     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1428 }
1429
1430 void
1431 MatchEvent (int mode)
1432 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1433         int dummy;
1434         if(matchMode) { // already in match mode: switch it off
1435             abortMatch = TRUE;
1436             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1437             return;
1438         }
1439 //      if(gameMode != BeginningOfGame) {
1440 //          DisplayError(_("You can only start a match from the initial position."), 0);
1441 //          return;
1442 //      }
1443         abortMatch = FALSE;
1444         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1445         /* Set up machine vs. machine match */
1446         nextGame = 0;
1447         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1448         if(appData.tourneyFile[0]) {
1449             ReserveGame(-1, 0);
1450             if(nextGame > appData.matchGames) {
1451                 char buf[MSG_SIZ];
1452                 if(strchr(appData.results, '*') == NULL) {
1453                     FILE *f;
1454                     appData.tourneyCycles++;
1455                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1456                         fclose(f);
1457                         NextTourneyGame(-1, &dummy);
1458                         ReserveGame(-1, 0);
1459                         if(nextGame <= appData.matchGames) {
1460                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1461                             matchMode = mode;
1462                             ScheduleDelayedEvent(NextMatchGame, 10000);
1463                             return;
1464                         }
1465                     }
1466                 }
1467                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1468                 DisplayError(buf, 0);
1469                 appData.tourneyFile[0] = 0;
1470                 return;
1471             }
1472         } else
1473         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1474             DisplayFatalError(_("Can't have a match with no chess programs"),
1475                               0, 2);
1476             return;
1477         }
1478         matchMode = mode;
1479         matchGame = roundNr = 1;
1480         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1481         NextMatchGame();
1482 }
1483
1484 void
1485 InitBackEnd3 P((void))
1486 {
1487     GameMode initialMode;
1488     char buf[MSG_SIZ];
1489     int err, len;
1490
1491     InitChessProgram(&first, startedFromSetupPosition);
1492
1493     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1494         free(programVersion);
1495         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1496         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1497         FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1498     }
1499
1500     if (appData.icsActive) {
1501 #ifdef WIN32
1502         /* [DM] Make a console window if needed [HGM] merged ifs */
1503         ConsoleCreate();
1504 #endif
1505         err = establish();
1506         if (err != 0)
1507           {
1508             if (*appData.icsCommPort != NULLCHAR)
1509               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1510                              appData.icsCommPort);
1511             else
1512               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1513                         appData.icsHost, appData.icsPort);
1514
1515             if( (len >= MSG_SIZ) && appData.debugMode )
1516               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1517
1518             DisplayFatalError(buf, err, 1);
1519             return;
1520         }
1521         SetICSMode();
1522         telnetISR =
1523           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1524         fromUserISR =
1525           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1526         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1527             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1528     } else if (appData.noChessProgram) {
1529         SetNCPMode();
1530     } else {
1531         SetGNUMode();
1532     }
1533
1534     if (*appData.cmailGameName != NULLCHAR) {
1535         SetCmailMode();
1536         OpenLoopback(&cmailPR);
1537         cmailISR =
1538           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1539     }
1540
1541     ThawUI();
1542     DisplayMessage("", "");
1543     if (StrCaseCmp(appData.initialMode, "") == 0) {
1544       initialMode = BeginningOfGame;
1545       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1546         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1547         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1548         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1549         ModeHighlight();
1550       }
1551     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1552       initialMode = TwoMachinesPlay;
1553     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1554       initialMode = AnalyzeFile;
1555     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1556       initialMode = AnalyzeMode;
1557     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1558       initialMode = MachinePlaysWhite;
1559     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1560       initialMode = MachinePlaysBlack;
1561     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1562       initialMode = EditGame;
1563     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1564       initialMode = EditPosition;
1565     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1566       initialMode = Training;
1567     } else {
1568       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1569       if( (len >= MSG_SIZ) && appData.debugMode )
1570         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1571
1572       DisplayFatalError(buf, 0, 2);
1573       return;
1574     }
1575
1576     if (appData.matchMode) {
1577         if(appData.tourneyFile[0]) { // start tourney from command line
1578             FILE *f;
1579             if(f = fopen(appData.tourneyFile, "r")) {
1580                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1581                 fclose(f);
1582                 appData.clockMode = TRUE;
1583                 SetGNUMode();
1584             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1585         }
1586         MatchEvent(TRUE);
1587     } else if (*appData.cmailGameName != NULLCHAR) {
1588         /* Set up cmail mode */
1589         ReloadCmailMsgEvent(TRUE);
1590     } else {
1591         /* Set up other modes */
1592         if (initialMode == AnalyzeFile) {
1593           if (*appData.loadGameFile == NULLCHAR) {
1594             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1595             return;
1596           }
1597         }
1598         if (*appData.loadGameFile != NULLCHAR) {
1599             (void) LoadGameFromFile(appData.loadGameFile,
1600                                     appData.loadGameIndex,
1601                                     appData.loadGameFile, TRUE);
1602         } else if (*appData.loadPositionFile != NULLCHAR) {
1603             (void) LoadPositionFromFile(appData.loadPositionFile,
1604                                         appData.loadPositionIndex,
1605                                         appData.loadPositionFile);
1606             /* [HGM] try to make self-starting even after FEN load */
1607             /* to allow automatic setup of fairy variants with wtm */
1608             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1609                 gameMode = BeginningOfGame;
1610                 setboardSpoiledMachineBlack = 1;
1611             }
1612             /* [HGM] loadPos: make that every new game uses the setup */
1613             /* from file as long as we do not switch variant          */
1614             if(!blackPlaysFirst) {
1615                 startedFromPositionFile = TRUE;
1616                 CopyBoard(filePosition, boards[0]);
1617             }
1618         }
1619         if (initialMode == AnalyzeMode) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1626             return;
1627           }
1628           AnalyzeModeEvent();
1629         } else if (initialMode == AnalyzeFile) {
1630           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1631           ShowThinkingEvent();
1632           AnalyzeFileEvent();
1633           AnalysisPeriodicEvent(1);
1634         } else if (initialMode == MachinePlaysWhite) {
1635           if (appData.noChessProgram) {
1636             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1637                               0, 2);
1638             return;
1639           }
1640           if (appData.icsActive) {
1641             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1642                               0, 2);
1643             return;
1644           }
1645           MachineWhiteEvent();
1646         } else if (initialMode == MachinePlaysBlack) {
1647           if (appData.noChessProgram) {
1648             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1649                               0, 2);
1650             return;
1651           }
1652           if (appData.icsActive) {
1653             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1654                               0, 2);
1655             return;
1656           }
1657           MachineBlackEvent();
1658         } else if (initialMode == TwoMachinesPlay) {
1659           if (appData.noChessProgram) {
1660             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1661                               0, 2);
1662             return;
1663           }
1664           if (appData.icsActive) {
1665             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1666                               0, 2);
1667             return;
1668           }
1669           TwoMachinesEvent();
1670         } else if (initialMode == EditGame) {
1671           EditGameEvent();
1672         } else if (initialMode == EditPosition) {
1673           EditPositionEvent();
1674         } else if (initialMode == Training) {
1675           if (*appData.loadGameFile == NULLCHAR) {
1676             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1677             return;
1678           }
1679           TrainingEvent();
1680         }
1681     }
1682 }
1683
1684 void
1685 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1686 {
1687     DisplayBook(current+1);
1688
1689     MoveHistorySet( movelist, first, last, current, pvInfoList );
1690
1691     EvalGraphSet( first, last, current, pvInfoList );
1692
1693     MakeEngineOutputTitle();
1694 }
1695
1696 /*
1697  * Establish will establish a contact to a remote host.port.
1698  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1699  *  used to talk to the host.
1700  * Returns 0 if okay, error code if not.
1701  */
1702 int
1703 establish ()
1704 {
1705     char buf[MSG_SIZ];
1706
1707     if (*appData.icsCommPort != NULLCHAR) {
1708         /* Talk to the host through a serial comm port */
1709         return OpenCommPort(appData.icsCommPort, &icsPR);
1710
1711     } else if (*appData.gateway != NULLCHAR) {
1712         if (*appData.remoteShell == NULLCHAR) {
1713             /* Use the rcmd protocol to run telnet program on a gateway host */
1714             snprintf(buf, sizeof(buf), "%s %s %s",
1715                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1716             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1717
1718         } else {
1719             /* Use the rsh program to run telnet program on a gateway host */
1720             if (*appData.remoteUser == NULLCHAR) {
1721                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1722                         appData.gateway, appData.telnetProgram,
1723                         appData.icsHost, appData.icsPort);
1724             } else {
1725                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1726                         appData.remoteShell, appData.gateway,
1727                         appData.remoteUser, appData.telnetProgram,
1728                         appData.icsHost, appData.icsPort);
1729             }
1730             return StartChildProcess(buf, "", &icsPR);
1731
1732         }
1733     } else if (appData.useTelnet) {
1734         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1735
1736     } else {
1737         /* TCP socket interface differs somewhat between
1738            Unix and NT; handle details in the front end.
1739            */
1740         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1741     }
1742 }
1743
1744 void
1745 EscapeExpand (char *p, char *q)
1746 {       // [HGM] initstring: routine to shape up string arguments
1747         while(*p++ = *q++) if(p[-1] == '\\')
1748             switch(*q++) {
1749                 case 'n': p[-1] = '\n'; break;
1750                 case 'r': p[-1] = '\r'; break;
1751                 case 't': p[-1] = '\t'; break;
1752                 case '\\': p[-1] = '\\'; break;
1753                 case 0: *p = 0; return;
1754                 default: p[-1] = q[-1]; break;
1755             }
1756 }
1757
1758 void
1759 show_bytes (FILE *fp, char *buf, int count)
1760 {
1761     while (count--) {
1762         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1763             fprintf(fp, "\\%03o", *buf & 0xff);
1764         } else {
1765             putc(*buf, fp);
1766         }
1767         buf++;
1768     }
1769     fflush(fp);
1770 }
1771
1772 /* Returns an errno value */
1773 int
1774 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1775 {
1776     char buf[8192], *p, *q, *buflim;
1777     int left, newcount, outcount;
1778
1779     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1780         *appData.gateway != NULLCHAR) {
1781         if (appData.debugMode) {
1782             fprintf(debugFP, ">ICS: ");
1783             show_bytes(debugFP, message, count);
1784             fprintf(debugFP, "\n");
1785         }
1786         return OutputToProcess(pr, message, count, outError);
1787     }
1788
1789     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1790     p = message;
1791     q = buf;
1792     left = count;
1793     newcount = 0;
1794     while (left) {
1795         if (q >= buflim) {
1796             if (appData.debugMode) {
1797                 fprintf(debugFP, ">ICS: ");
1798                 show_bytes(debugFP, buf, newcount);
1799                 fprintf(debugFP, "\n");
1800             }
1801             outcount = OutputToProcess(pr, buf, newcount, outError);
1802             if (outcount < newcount) return -1; /* to be sure */
1803             q = buf;
1804             newcount = 0;
1805         }
1806         if (*p == '\n') {
1807             *q++ = '\r';
1808             newcount++;
1809         } else if (((unsigned char) *p) == TN_IAC) {
1810             *q++ = (char) TN_IAC;
1811             newcount ++;
1812         }
1813         *q++ = *p++;
1814         newcount++;
1815         left--;
1816     }
1817     if (appData.debugMode) {
1818         fprintf(debugFP, ">ICS: ");
1819         show_bytes(debugFP, buf, newcount);
1820         fprintf(debugFP, "\n");
1821     }
1822     outcount = OutputToProcess(pr, buf, newcount, outError);
1823     if (outcount < newcount) return -1; /* to be sure */
1824     return count;
1825 }
1826
1827 void
1828 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1829 {
1830     int outError, outCount;
1831     static int gotEof = 0;
1832
1833     /* Pass data read from player on to ICS */
1834     if (count > 0) {
1835         gotEof = 0;
1836         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1837         if (outCount < count) {
1838             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1839         }
1840     } else if (count < 0) {
1841         RemoveInputSource(isr);
1842         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1843     } else if (gotEof++ > 0) {
1844         RemoveInputSource(isr);
1845         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1846     }
1847 }
1848
1849 void
1850 KeepAlive ()
1851 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1852     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1853     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1854     SendToICS("date\n");
1855     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1856 }
1857
1858 /* added routine for printf style output to ics */
1859 void
1860 ics_printf (char *format, ...)
1861 {
1862     char buffer[MSG_SIZ];
1863     va_list args;
1864
1865     va_start(args, format);
1866     vsnprintf(buffer, sizeof(buffer), format, args);
1867     buffer[sizeof(buffer)-1] = '\0';
1868     SendToICS(buffer);
1869     va_end(args);
1870 }
1871
1872 void
1873 SendToICS (char *s)
1874 {
1875     int count, outCount, outError;
1876
1877     if (icsPR == NoProc) return;
1878
1879     count = strlen(s);
1880     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1881     if (outCount < count) {
1882         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1883     }
1884 }
1885
1886 /* This is used for sending logon scripts to the ICS. Sending
1887    without a delay causes problems when using timestamp on ICC
1888    (at least on my machine). */
1889 void
1890 SendToICSDelayed (char *s, long msdelay)
1891 {
1892     int count, outCount, outError;
1893
1894     if (icsPR == NoProc) return;
1895
1896     count = strlen(s);
1897     if (appData.debugMode) {
1898         fprintf(debugFP, ">ICS: ");
1899         show_bytes(debugFP, s, count);
1900         fprintf(debugFP, "\n");
1901     }
1902     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1903                                       msdelay);
1904     if (outCount < count) {
1905         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1906     }
1907 }
1908
1909
1910 /* Remove all highlighting escape sequences in s
1911    Also deletes any suffix starting with '('
1912    */
1913 char *
1914 StripHighlightAndTitle (char *s)
1915 {
1916     static char retbuf[MSG_SIZ];
1917     char *p = retbuf;
1918
1919     while (*s != NULLCHAR) {
1920         while (*s == '\033') {
1921             while (*s != NULLCHAR && !isalpha(*s)) s++;
1922             if (*s != NULLCHAR) s++;
1923         }
1924         while (*s != NULLCHAR && *s != '\033') {
1925             if (*s == '(' || *s == '[') {
1926                 *p = NULLCHAR;
1927                 return retbuf;
1928             }
1929             *p++ = *s++;
1930         }
1931     }
1932     *p = NULLCHAR;
1933     return retbuf;
1934 }
1935
1936 /* Remove all highlighting escape sequences in s */
1937 char *
1938 StripHighlight (char *s)
1939 {
1940     static char retbuf[MSG_SIZ];
1941     char *p = retbuf;
1942
1943     while (*s != NULLCHAR) {
1944         while (*s == '\033') {
1945             while (*s != NULLCHAR && !isalpha(*s)) s++;
1946             if (*s != NULLCHAR) s++;
1947         }
1948         while (*s != NULLCHAR && *s != '\033') {
1949             *p++ = *s++;
1950         }
1951     }
1952     *p = NULLCHAR;
1953     return retbuf;
1954 }
1955
1956 char *variantNames[] = VARIANT_NAMES;
1957 char *
1958 VariantName (VariantClass v)
1959 {
1960     return variantNames[v];
1961 }
1962
1963
1964 /* Identify a variant from the strings the chess servers use or the
1965    PGN Variant tag names we use. */
1966 VariantClass
1967 StringToVariant (char *e)
1968 {
1969     char *p;
1970     int wnum = -1;
1971     VariantClass v = VariantNormal;
1972     int i, found = FALSE;
1973     char buf[MSG_SIZ];
1974     int len;
1975
1976     if (!e) return v;
1977
1978     /* [HGM] skip over optional board-size prefixes */
1979     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1980         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1981         while( *e++ != '_');
1982     }
1983
1984     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1985         v = VariantNormal;
1986         found = TRUE;
1987     } else
1988     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1989       if (StrCaseStr(e, variantNames[i])) {
1990         v = (VariantClass) i;
1991         found = TRUE;
1992         break;
1993       }
1994     }
1995
1996     if (!found) {
1997       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1998           || StrCaseStr(e, "wild/fr")
1999           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2000         v = VariantFischeRandom;
2001       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2002                  (i = 1, p = StrCaseStr(e, "w"))) {
2003         p += i;
2004         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2005         if (isdigit(*p)) {
2006           wnum = atoi(p);
2007         } else {
2008           wnum = -1;
2009         }
2010         switch (wnum) {
2011         case 0: /* FICS only, actually */
2012         case 1:
2013           /* Castling legal even if K starts on d-file */
2014           v = VariantWildCastle;
2015           break;
2016         case 2:
2017         case 3:
2018         case 4:
2019           /* Castling illegal even if K & R happen to start in
2020              normal positions. */
2021           v = VariantNoCastle;
2022           break;
2023         case 5:
2024         case 7:
2025         case 8:
2026         case 10:
2027         case 11:
2028         case 12:
2029         case 13:
2030         case 14:
2031         case 15:
2032         case 18:
2033         case 19:
2034           /* Castling legal iff K & R start in normal positions */
2035           v = VariantNormal;
2036           break;
2037         case 6:
2038         case 20:
2039         case 21:
2040           /* Special wilds for position setup; unclear what to do here */
2041           v = VariantLoadable;
2042           break;
2043         case 9:
2044           /* Bizarre ICC game */
2045           v = VariantTwoKings;
2046           break;
2047         case 16:
2048           v = VariantKriegspiel;
2049           break;
2050         case 17:
2051           v = VariantLosers;
2052           break;
2053         case 22:
2054           v = VariantFischeRandom;
2055           break;
2056         case 23:
2057           v = VariantCrazyhouse;
2058           break;
2059         case 24:
2060           v = VariantBughouse;
2061           break;
2062         case 25:
2063           v = Variant3Check;
2064           break;
2065         case 26:
2066           /* Not quite the same as FICS suicide! */
2067           v = VariantGiveaway;
2068           break;
2069         case 27:
2070           v = VariantAtomic;
2071           break;
2072         case 28:
2073           v = VariantShatranj;
2074           break;
2075
2076         /* Temporary names for future ICC types.  The name *will* change in
2077            the next xboard/WinBoard release after ICC defines it. */
2078         case 29:
2079           v = Variant29;
2080           break;
2081         case 30:
2082           v = Variant30;
2083           break;
2084         case 31:
2085           v = Variant31;
2086           break;
2087         case 32:
2088           v = Variant32;
2089           break;
2090         case 33:
2091           v = Variant33;
2092           break;
2093         case 34:
2094           v = Variant34;
2095           break;
2096         case 35:
2097           v = Variant35;
2098           break;
2099         case 36:
2100           v = Variant36;
2101           break;
2102         case 37:
2103           v = VariantShogi;
2104           break;
2105         case 38:
2106           v = VariantXiangqi;
2107           break;
2108         case 39:
2109           v = VariantCourier;
2110           break;
2111         case 40:
2112           v = VariantGothic;
2113           break;
2114         case 41:
2115           v = VariantCapablanca;
2116           break;
2117         case 42:
2118           v = VariantKnightmate;
2119           break;
2120         case 43:
2121           v = VariantFairy;
2122           break;
2123         case 44:
2124           v = VariantCylinder;
2125           break;
2126         case 45:
2127           v = VariantFalcon;
2128           break;
2129         case 46:
2130           v = VariantCapaRandom;
2131           break;
2132         case 47:
2133           v = VariantBerolina;
2134           break;
2135         case 48:
2136           v = VariantJanus;
2137           break;
2138         case 49:
2139           v = VariantSuper;
2140           break;
2141         case 50:
2142           v = VariantGreat;
2143           break;
2144         case -1:
2145           /* Found "wild" or "w" in the string but no number;
2146              must assume it's normal chess. */
2147           v = VariantNormal;
2148           break;
2149         default:
2150           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2151           if( (len >= MSG_SIZ) && appData.debugMode )
2152             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2153
2154           DisplayError(buf, 0);
2155           v = VariantUnknown;
2156           break;
2157         }
2158       }
2159     }
2160     if (appData.debugMode) {
2161       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2162               e, wnum, VariantName(v));
2163     }
2164     return v;
2165 }
2166
2167 static int leftover_start = 0, leftover_len = 0;
2168 char star_match[STAR_MATCH_N][MSG_SIZ];
2169
2170 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2171    advance *index beyond it, and set leftover_start to the new value of
2172    *index; else return FALSE.  If pattern contains the character '*', it
2173    matches any sequence of characters not containing '\r', '\n', or the
2174    character following the '*' (if any), and the matched sequence(s) are
2175    copied into star_match.
2176    */
2177 int
2178 looking_at ( char *buf, int *index, char *pattern)
2179 {
2180     char *bufp = &buf[*index], *patternp = pattern;
2181     int star_count = 0;
2182     char *matchp = star_match[0];
2183
2184     for (;;) {
2185         if (*patternp == NULLCHAR) {
2186             *index = leftover_start = bufp - buf;
2187             *matchp = NULLCHAR;
2188             return TRUE;
2189         }
2190         if (*bufp == NULLCHAR) return FALSE;
2191         if (*patternp == '*') {
2192             if (*bufp == *(patternp + 1)) {
2193                 *matchp = NULLCHAR;
2194                 matchp = star_match[++star_count];
2195                 patternp += 2;
2196                 bufp++;
2197                 continue;
2198             } else if (*bufp == '\n' || *bufp == '\r') {
2199                 patternp++;
2200                 if (*patternp == NULLCHAR)
2201                   continue;
2202                 else
2203                   return FALSE;
2204             } else {
2205                 *matchp++ = *bufp++;
2206                 continue;
2207             }
2208         }
2209         if (*patternp != *bufp) return FALSE;
2210         patternp++;
2211         bufp++;
2212     }
2213 }
2214
2215 void
2216 SendToPlayer (char *data, int length)
2217 {
2218     int error, outCount;
2219     outCount = OutputToProcess(NoProc, data, length, &error);
2220     if (outCount < length) {
2221         DisplayFatalError(_("Error writing to display"), error, 1);
2222     }
2223 }
2224
2225 void
2226 PackHolding (char packed[], char *holding)
2227 {
2228     char *p = holding;
2229     char *q = packed;
2230     int runlength = 0;
2231     int curr = 9999;
2232     do {
2233         if (*p == curr) {
2234             runlength++;
2235         } else {
2236             switch (runlength) {
2237               case 0:
2238                 break;
2239               case 1:
2240                 *q++ = curr;
2241                 break;
2242               case 2:
2243                 *q++ = curr;
2244                 *q++ = curr;
2245                 break;
2246               default:
2247                 sprintf(q, "%d", runlength);
2248                 while (*q) q++;
2249                 *q++ = curr;
2250                 break;
2251             }
2252             runlength = 1;
2253             curr = *p;
2254         }
2255     } while (*p++);
2256     *q = NULLCHAR;
2257 }
2258
2259 /* Telnet protocol requests from the front end */
2260 void
2261 TelnetRequest (unsigned char ddww, unsigned char option)
2262 {
2263     unsigned char msg[3];
2264     int outCount, outError;
2265
2266     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2267
2268     if (appData.debugMode) {
2269         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2270         switch (ddww) {
2271           case TN_DO:
2272             ddwwStr = "DO";
2273             break;
2274           case TN_DONT:
2275             ddwwStr = "DONT";
2276             break;
2277           case TN_WILL:
2278             ddwwStr = "WILL";
2279             break;
2280           case TN_WONT:
2281             ddwwStr = "WONT";
2282             break;
2283           default:
2284             ddwwStr = buf1;
2285             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2286             break;
2287         }
2288         switch (option) {
2289           case TN_ECHO:
2290             optionStr = "ECHO";
2291             break;
2292           default:
2293             optionStr = buf2;
2294             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2295             break;
2296         }
2297         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2298     }
2299     msg[0] = TN_IAC;
2300     msg[1] = ddww;
2301     msg[2] = option;
2302     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2303     if (outCount < 3) {
2304         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2305     }
2306 }
2307
2308 void
2309 DoEcho ()
2310 {
2311     if (!appData.icsActive) return;
2312     TelnetRequest(TN_DO, TN_ECHO);
2313 }
2314
2315 void
2316 DontEcho ()
2317 {
2318     if (!appData.icsActive) return;
2319     TelnetRequest(TN_DONT, TN_ECHO);
2320 }
2321
2322 void
2323 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2324 {
2325     /* put the holdings sent to us by the server on the board holdings area */
2326     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2327     char p;
2328     ChessSquare piece;
2329
2330     if(gameInfo.holdingsWidth < 2)  return;
2331     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2332         return; // prevent overwriting by pre-board holdings
2333
2334     if( (int)lowestPiece >= BlackPawn ) {
2335         holdingsColumn = 0;
2336         countsColumn = 1;
2337         holdingsStartRow = BOARD_HEIGHT-1;
2338         direction = -1;
2339     } else {
2340         holdingsColumn = BOARD_WIDTH-1;
2341         countsColumn = BOARD_WIDTH-2;
2342         holdingsStartRow = 0;
2343         direction = 1;
2344     }
2345
2346     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2347         board[i][holdingsColumn] = EmptySquare;
2348         board[i][countsColumn]   = (ChessSquare) 0;
2349     }
2350     while( (p=*holdings++) != NULLCHAR ) {
2351         piece = CharToPiece( ToUpper(p) );
2352         if(piece == EmptySquare) continue;
2353         /*j = (int) piece - (int) WhitePawn;*/
2354         j = PieceToNumber(piece);
2355         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2356         if(j < 0) continue;               /* should not happen */
2357         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2358         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2359         board[holdingsStartRow+j*direction][countsColumn]++;
2360     }
2361 }
2362
2363
2364 void
2365 VariantSwitch (Board board, VariantClass newVariant)
2366 {
2367    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2368    static Board oldBoard;
2369
2370    startedFromPositionFile = FALSE;
2371    if(gameInfo.variant == newVariant) return;
2372
2373    /* [HGM] This routine is called each time an assignment is made to
2374     * gameInfo.variant during a game, to make sure the board sizes
2375     * are set to match the new variant. If that means adding or deleting
2376     * holdings, we shift the playing board accordingly
2377     * This kludge is needed because in ICS observe mode, we get boards
2378     * of an ongoing game without knowing the variant, and learn about the
2379     * latter only later. This can be because of the move list we requested,
2380     * in which case the game history is refilled from the beginning anyway,
2381     * but also when receiving holdings of a crazyhouse game. In the latter
2382     * case we want to add those holdings to the already received position.
2383     */
2384
2385
2386    if (appData.debugMode) {
2387      fprintf(debugFP, "Switch board from %s to %s\n",
2388              VariantName(gameInfo.variant), VariantName(newVariant));
2389      setbuf(debugFP, NULL);
2390    }
2391    shuffleOpenings = 0;       /* [HGM] shuffle */
2392    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2393    switch(newVariant)
2394      {
2395      case VariantShogi:
2396        newWidth = 9;  newHeight = 9;
2397        gameInfo.holdingsSize = 7;
2398      case VariantBughouse:
2399      case VariantCrazyhouse:
2400        newHoldingsWidth = 2; break;
2401      case VariantGreat:
2402        newWidth = 10;
2403      case VariantSuper:
2404        newHoldingsWidth = 2;
2405        gameInfo.holdingsSize = 8;
2406        break;
2407      case VariantGothic:
2408      case VariantCapablanca:
2409      case VariantCapaRandom:
2410        newWidth = 10;
2411      default:
2412        newHoldingsWidth = gameInfo.holdingsSize = 0;
2413      };
2414
2415    if(newWidth  != gameInfo.boardWidth  ||
2416       newHeight != gameInfo.boardHeight ||
2417       newHoldingsWidth != gameInfo.holdingsWidth ) {
2418
2419      /* shift position to new playing area, if needed */
2420      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2421        for(i=0; i<BOARD_HEIGHT; i++)
2422          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2423            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2424              board[i][j];
2425        for(i=0; i<newHeight; i++) {
2426          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2427          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2428        }
2429      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2430        for(i=0; i<BOARD_HEIGHT; i++)
2431          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2432            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2433              board[i][j];
2434      }
2435      gameInfo.boardWidth  = newWidth;
2436      gameInfo.boardHeight = newHeight;
2437      gameInfo.holdingsWidth = newHoldingsWidth;
2438      gameInfo.variant = newVariant;
2439      InitDrawingSizes(-2, 0);
2440    } else gameInfo.variant = newVariant;
2441    CopyBoard(oldBoard, board);   // remember correctly formatted board
2442      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2443    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2444 }
2445
2446 static int loggedOn = FALSE;
2447
2448 /*-- Game start info cache: --*/
2449 int gs_gamenum;
2450 char gs_kind[MSG_SIZ];
2451 static char player1Name[128] = "";
2452 static char player2Name[128] = "";
2453 static char cont_seq[] = "\n\\   ";
2454 static int player1Rating = -1;
2455 static int player2Rating = -1;
2456 /*----------------------------*/
2457
2458 ColorClass curColor = ColorNormal;
2459 int suppressKibitz = 0;
2460
2461 // [HGM] seekgraph
2462 Boolean soughtPending = FALSE;
2463 Boolean seekGraphUp;
2464 #define MAX_SEEK_ADS 200
2465 #define SQUARE 0x80
2466 char *seekAdList[MAX_SEEK_ADS];
2467 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2468 float tcList[MAX_SEEK_ADS];
2469 char colorList[MAX_SEEK_ADS];
2470 int nrOfSeekAds = 0;
2471 int minRating = 1010, maxRating = 2800;
2472 int hMargin = 10, vMargin = 20, h, w;
2473 extern int squareSize, lineGap;
2474
2475 void
2476 PlotSeekAd (int i)
2477 {
2478         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2479         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2480         if(r < minRating+100 && r >=0 ) r = minRating+100;
2481         if(r > maxRating) r = maxRating;
2482         if(tc < 1.) tc = 1.;
2483         if(tc > 95.) tc = 95.;
2484         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2485         y = ((double)r - minRating)/(maxRating - minRating)
2486             * (h-vMargin-squareSize/8-1) + vMargin;
2487         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2488         if(strstr(seekAdList[i], " u ")) color = 1;
2489         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2490            !strstr(seekAdList[i], "bullet") &&
2491            !strstr(seekAdList[i], "blitz") &&
2492            !strstr(seekAdList[i], "standard") ) color = 2;
2493         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2494         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2495 }
2496
2497 void
2498 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2499 {
2500         char buf[MSG_SIZ], *ext = "";
2501         VariantClass v = StringToVariant(type);
2502         if(strstr(type, "wild")) {
2503             ext = type + 4; // append wild number
2504             if(v == VariantFischeRandom) type = "chess960"; else
2505             if(v == VariantLoadable) type = "setup"; else
2506             type = VariantName(v);
2507         }
2508         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2509         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2510             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2511             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2512             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2513             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2514             seekNrList[nrOfSeekAds] = nr;
2515             zList[nrOfSeekAds] = 0;
2516             seekAdList[nrOfSeekAds++] = StrSave(buf);
2517             if(plot) PlotSeekAd(nrOfSeekAds-1);
2518         }
2519 }
2520
2521 void
2522 EraseSeekDot (int i)
2523 {
2524     int x = xList[i], y = yList[i], d=squareSize/4, k;
2525     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2526     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2527     // now replot every dot that overlapped
2528     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2529         int xx = xList[k], yy = yList[k];
2530         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2531             DrawSeekDot(xx, yy, colorList[k]);
2532     }
2533 }
2534
2535 void
2536 RemoveSeekAd (int nr)
2537 {
2538         int i;
2539         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2540             EraseSeekDot(i);
2541             if(seekAdList[i]) free(seekAdList[i]);
2542             seekAdList[i] = seekAdList[--nrOfSeekAds];
2543             seekNrList[i] = seekNrList[nrOfSeekAds];
2544             ratingList[i] = ratingList[nrOfSeekAds];
2545             colorList[i]  = colorList[nrOfSeekAds];
2546             tcList[i] = tcList[nrOfSeekAds];
2547             xList[i]  = xList[nrOfSeekAds];
2548             yList[i]  = yList[nrOfSeekAds];
2549             zList[i]  = zList[nrOfSeekAds];
2550             seekAdList[nrOfSeekAds] = NULL;
2551             break;
2552         }
2553 }
2554
2555 Boolean
2556 MatchSoughtLine (char *line)
2557 {
2558     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2559     int nr, base, inc, u=0; char dummy;
2560
2561     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2562        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2563        (u=1) &&
2564        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2565         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2566         // match: compact and save the line
2567         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2568         return TRUE;
2569     }
2570     return FALSE;
2571 }
2572
2573 int
2574 DrawSeekGraph ()
2575 {
2576     int i;
2577     if(!seekGraphUp) return FALSE;
2578     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2579     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2580
2581     DrawSeekBackground(0, 0, w, h);
2582     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2583     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2584     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2585         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2586         yy = h-1-yy;
2587         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2588         if(i%500 == 0) {
2589             char buf[MSG_SIZ];
2590             snprintf(buf, MSG_SIZ, "%d", i);
2591             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2592         }
2593     }
2594     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2595     for(i=1; i<100; i+=(i<10?1:5)) {
2596         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2597         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2598         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2599             char buf[MSG_SIZ];
2600             snprintf(buf, MSG_SIZ, "%d", i);
2601             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2602         }
2603     }
2604     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2605     return TRUE;
2606 }
2607
2608 int
2609 SeekGraphClick (ClickType click, int x, int y, int moving)
2610 {
2611     static int lastDown = 0, displayed = 0, lastSecond;
2612     if(y < 0) return FALSE;
2613     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2614         if(click == Release || moving) return FALSE;
2615         nrOfSeekAds = 0;
2616         soughtPending = TRUE;
2617         SendToICS(ics_prefix);
2618         SendToICS("sought\n"); // should this be "sought all"?
2619     } else { // issue challenge based on clicked ad
2620         int dist = 10000; int i, closest = 0, second = 0;
2621         for(i=0; i<nrOfSeekAds; i++) {
2622             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2623             if(d < dist) { dist = d; closest = i; }
2624             second += (d - zList[i] < 120); // count in-range ads
2625             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2626         }
2627         if(dist < 120) {
2628             char buf[MSG_SIZ];
2629             second = (second > 1);
2630             if(displayed != closest || second != lastSecond) {
2631                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2632                 lastSecond = second; displayed = closest;
2633             }
2634             if(click == Press) {
2635                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2636                 lastDown = closest;
2637                 return TRUE;
2638             } // on press 'hit', only show info
2639             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2640             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2641             SendToICS(ics_prefix);
2642             SendToICS(buf);
2643             return TRUE; // let incoming board of started game pop down the graph
2644         } else if(click == Release) { // release 'miss' is ignored
2645             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2646             if(moving == 2) { // right up-click
2647                 nrOfSeekAds = 0; // refresh graph
2648                 soughtPending = TRUE;
2649                 SendToICS(ics_prefix);
2650                 SendToICS("sought\n"); // should this be "sought all"?
2651             }
2652             return TRUE;
2653         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2654         // press miss or release hit 'pop down' seek graph
2655         seekGraphUp = FALSE;
2656         DrawPosition(TRUE, NULL);
2657     }
2658     return TRUE;
2659 }
2660
2661 void
2662 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2663 {
2664 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2665 #define STARTED_NONE 0
2666 #define STARTED_MOVES 1
2667 #define STARTED_BOARD 2
2668 #define STARTED_OBSERVE 3
2669 #define STARTED_HOLDINGS 4
2670 #define STARTED_CHATTER 5
2671 #define STARTED_COMMENT 6
2672 #define STARTED_MOVES_NOHIDE 7
2673
2674     static int started = STARTED_NONE;
2675     static char parse[20000];
2676     static int parse_pos = 0;
2677     static char buf[BUF_SIZE + 1];
2678     static int firstTime = TRUE, intfSet = FALSE;
2679     static ColorClass prevColor = ColorNormal;
2680     static int savingComment = FALSE;
2681     static int cmatch = 0; // continuation sequence match
2682     char *bp;
2683     char str[MSG_SIZ];
2684     int i, oldi;
2685     int buf_len;
2686     int next_out;
2687     int tkind;
2688     int backup;    /* [DM] For zippy color lines */
2689     char *p;
2690     char talker[MSG_SIZ]; // [HGM] chat
2691     int channel;
2692
2693     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2694
2695     if (appData.debugMode) {
2696       if (!error) {
2697         fprintf(debugFP, "<ICS: ");
2698         show_bytes(debugFP, data, count);
2699         fprintf(debugFP, "\n");
2700       }
2701     }
2702
2703     if (appData.debugMode) { int f = forwardMostMove;
2704         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2705                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2706                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2707     }
2708     if (count > 0) {
2709         /* If last read ended with a partial line that we couldn't parse,
2710            prepend it to the new read and try again. */
2711         if (leftover_len > 0) {
2712             for (i=0; i<leftover_len; i++)
2713               buf[i] = buf[leftover_start + i];
2714         }
2715
2716     /* copy new characters into the buffer */
2717     bp = buf + leftover_len;
2718     buf_len=leftover_len;
2719     for (i=0; i<count; i++)
2720     {
2721         // ignore these
2722         if (data[i] == '\r')
2723             continue;
2724
2725         // join lines split by ICS?
2726         if (!appData.noJoin)
2727         {
2728             /*
2729                 Joining just consists of finding matches against the
2730                 continuation sequence, and discarding that sequence
2731                 if found instead of copying it.  So, until a match
2732                 fails, there's nothing to do since it might be the
2733                 complete sequence, and thus, something we don't want
2734                 copied.
2735             */
2736             if (data[i] == cont_seq[cmatch])
2737             {
2738                 cmatch++;
2739                 if (cmatch == strlen(cont_seq))
2740                 {
2741                     cmatch = 0; // complete match.  just reset the counter
2742
2743                     /*
2744                         it's possible for the ICS to not include the space
2745                         at the end of the last word, making our [correct]
2746                         join operation fuse two separate words.  the server
2747                         does this when the space occurs at the width setting.
2748                     */
2749                     if (!buf_len || buf[buf_len-1] != ' ')
2750                     {
2751                         *bp++ = ' ';
2752                         buf_len++;
2753                     }
2754                 }
2755                 continue;
2756             }
2757             else if (cmatch)
2758             {
2759                 /*
2760                     match failed, so we have to copy what matched before
2761                     falling through and copying this character.  In reality,
2762                     this will only ever be just the newline character, but
2763                     it doesn't hurt to be precise.
2764                 */
2765                 strncpy(bp, cont_seq, cmatch);
2766                 bp += cmatch;
2767                 buf_len += cmatch;
2768                 cmatch = 0;
2769             }
2770         }
2771
2772         // copy this char
2773         *bp++ = data[i];
2774         buf_len++;
2775     }
2776
2777         buf[buf_len] = NULLCHAR;
2778 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2779         next_out = 0;
2780         leftover_start = 0;
2781
2782         i = 0;
2783         while (i < buf_len) {
2784             /* Deal with part of the TELNET option negotiation
2785                protocol.  We refuse to do anything beyond the
2786                defaults, except that we allow the WILL ECHO option,
2787                which ICS uses to turn off password echoing when we are
2788                directly connected to it.  We reject this option
2789                if localLineEditing mode is on (always on in xboard)
2790                and we are talking to port 23, which might be a real
2791                telnet server that will try to keep WILL ECHO on permanently.
2792              */
2793             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2794                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2795                 unsigned char option;
2796                 oldi = i;
2797                 switch ((unsigned char) buf[++i]) {
2798                   case TN_WILL:
2799                     if (appData.debugMode)
2800                       fprintf(debugFP, "\n<WILL ");
2801                     switch (option = (unsigned char) buf[++i]) {
2802                       case TN_ECHO:
2803                         if (appData.debugMode)
2804                           fprintf(debugFP, "ECHO ");
2805                         /* Reply only if this is a change, according
2806                            to the protocol rules. */
2807                         if (remoteEchoOption) break;
2808                         if (appData.localLineEditing &&
2809                             atoi(appData.icsPort) == TN_PORT) {
2810                             TelnetRequest(TN_DONT, TN_ECHO);
2811                         } else {
2812                             EchoOff();
2813                             TelnetRequest(TN_DO, TN_ECHO);
2814                             remoteEchoOption = TRUE;
2815                         }
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", option);
2820                         /* Whatever this is, we don't want it. */
2821                         TelnetRequest(TN_DONT, option);
2822                         break;
2823                     }
2824                     break;
2825                   case TN_WONT:
2826                     if (appData.debugMode)
2827                       fprintf(debugFP, "\n<WONT ");
2828                     switch (option = (unsigned char) buf[++i]) {
2829                       case TN_ECHO:
2830                         if (appData.debugMode)
2831                           fprintf(debugFP, "ECHO ");
2832                         /* Reply only if this is a change, according
2833                            to the protocol rules. */
2834                         if (!remoteEchoOption) break;
2835                         EchoOn();
2836                         TelnetRequest(TN_DONT, TN_ECHO);
2837                         remoteEchoOption = FALSE;
2838                         break;
2839                       default:
2840                         if (appData.debugMode)
2841                           fprintf(debugFP, "%d ", (unsigned char) option);
2842                         /* Whatever this is, it must already be turned
2843                            off, because we never agree to turn on
2844                            anything non-default, so according to the
2845                            protocol rules, we don't reply. */
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DO:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DO ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         /* Whatever this is, we refuse to do it. */
2855                         if (appData.debugMode)
2856                           fprintf(debugFP, "%d ", option);
2857                         TelnetRequest(TN_WONT, option);
2858                         break;
2859                     }
2860                     break;
2861                   case TN_DONT:
2862                     if (appData.debugMode)
2863                       fprintf(debugFP, "\n<DONT ");
2864                     switch (option = (unsigned char) buf[++i]) {
2865                       default:
2866                         if (appData.debugMode)
2867                           fprintf(debugFP, "%d ", option);
2868                         /* Whatever this is, we are already not doing
2869                            it, because we never agree to do anything
2870                            non-default, so according to the protocol
2871                            rules, we don't reply. */
2872                         break;
2873                     }
2874                     break;
2875                   case TN_IAC:
2876                     if (appData.debugMode)
2877                       fprintf(debugFP, "\n<IAC ");
2878                     /* Doubled IAC; pass it through */
2879                     i--;
2880                     break;
2881                   default:
2882                     if (appData.debugMode)
2883                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2884                     /* Drop all other telnet commands on the floor */
2885                     break;
2886                 }
2887                 if (oldi > next_out)
2888                   SendToPlayer(&buf[next_out], oldi - next_out);
2889                 if (++i > next_out)
2890                   next_out = i;
2891                 continue;
2892             }
2893
2894             /* OK, this at least will *usually* work */
2895             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2896                 loggedOn = TRUE;
2897             }
2898
2899             if (loggedOn && !intfSet) {
2900                 if (ics_type == ICS_ICC) {
2901                   snprintf(str, MSG_SIZ,
2902                           "/set-quietly interface %s\n/set-quietly style 12\n",
2903                           programVersion);
2904                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2905                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2906                 } else if (ics_type == ICS_CHESSNET) {
2907                   snprintf(str, MSG_SIZ, "/style 12\n");
2908                 } else {
2909                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2910                   strcat(str, programVersion);
2911                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2912                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2914 #ifdef WIN32
2915                   strcat(str, "$iset nohighlight 1\n");
2916 #endif
2917                   strcat(str, "$iset lock 1\n$style 12\n");
2918                 }
2919                 SendToICS(str);
2920                 NotifyFrontendLogin();
2921                 intfSet = TRUE;
2922             }
2923
2924             if (started == STARTED_COMMENT) {
2925                 /* Accumulate characters in comment */
2926                 parse[parse_pos++] = buf[i];
2927                 if (buf[i] == '\n') {
2928                     parse[parse_pos] = NULLCHAR;
2929                     if(chattingPartner>=0) {
2930                         char mess[MSG_SIZ];
2931                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2932                         OutputChatMessage(chattingPartner, mess);
2933                         chattingPartner = -1;
2934                         next_out = i+1; // [HGM] suppress printing in ICS window
2935                     } else
2936                     if(!suppressKibitz) // [HGM] kibitz
2937                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2938                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2939                         int nrDigit = 0, nrAlph = 0, j;
2940                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2941                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2942                         parse[parse_pos] = NULLCHAR;
2943                         // try to be smart: if it does not look like search info, it should go to
2944                         // ICS interaction window after all, not to engine-output window.
2945                         for(j=0; j<parse_pos; j++) { // count letters and digits
2946                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2947                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2948                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2949                         }
2950                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2951                             int depth=0; float score;
2952                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2953                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2954                                 pvInfoList[forwardMostMove-1].depth = depth;
2955                                 pvInfoList[forwardMostMove-1].score = 100*score;
2956                             }
2957                             OutputKibitz(suppressKibitz, parse);
2958                         } else {
2959                             char tmp[MSG_SIZ];
2960                             if(gameMode == IcsObserving) // restore original ICS messages
2961                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2962                             else
2963                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2964                             SendToPlayer(tmp, strlen(tmp));
2965                         }
2966                         next_out = i+1; // [HGM] suppress printing in ICS window
2967                     }
2968                     started = STARTED_NONE;
2969                 } else {
2970                     /* Don't match patterns against characters in comment */
2971                     i++;
2972                     continue;
2973                 }
2974             }
2975             if (started == STARTED_CHATTER) {
2976                 if (buf[i] != '\n') {
2977                     /* Don't match patterns against characters in chatter */
2978                     i++;
2979                     continue;
2980                 }
2981                 started = STARTED_NONE;
2982                 if(suppressKibitz) next_out = i+1;
2983             }
2984
2985             /* Kludge to deal with rcmd protocol */
2986             if (firstTime && looking_at(buf, &i, "\001*")) {
2987                 DisplayFatalError(&buf[1], 0, 1);
2988                 continue;
2989             } else {
2990                 firstTime = FALSE;
2991             }
2992
2993             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2994                 ics_type = ICS_ICC;
2995                 ics_prefix = "/";
2996                 if (appData.debugMode)
2997                   fprintf(debugFP, "ics_type %d\n", ics_type);
2998                 continue;
2999             }
3000             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3001                 ics_type = ICS_FICS;
3002                 ics_prefix = "$";
3003                 if (appData.debugMode)
3004                   fprintf(debugFP, "ics_type %d\n", ics_type);
3005                 continue;
3006             }
3007             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3008                 ics_type = ICS_CHESSNET;
3009                 ics_prefix = "/";
3010                 if (appData.debugMode)
3011                   fprintf(debugFP, "ics_type %d\n", ics_type);
3012                 continue;
3013             }
3014
3015             if (!loggedOn &&
3016                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3017                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3018                  looking_at(buf, &i, "will be \"*\""))) {
3019               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3020               continue;
3021             }
3022
3023             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3024               char buf[MSG_SIZ];
3025               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3026               DisplayIcsInteractionTitle(buf);
3027               have_set_title = TRUE;
3028             }
3029
3030             /* skip finger notes */
3031             if (started == STARTED_NONE &&
3032                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3033                  (buf[i] == '1' && buf[i+1] == '0')) &&
3034                 buf[i+2] == ':' && buf[i+3] == ' ') {
3035               started = STARTED_CHATTER;
3036               i += 3;
3037               continue;
3038             }
3039
3040             oldi = i;
3041             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3042             if(appData.seekGraph) {
3043                 if(soughtPending && MatchSoughtLine(buf+i)) {
3044                     i = strstr(buf+i, "rated") - buf;
3045                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3046                     next_out = leftover_start = i;
3047                     started = STARTED_CHATTER;
3048                     suppressKibitz = TRUE;
3049                     continue;
3050                 }
3051                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3052                         && looking_at(buf, &i, "* ads displayed")) {
3053                     soughtPending = FALSE;
3054                     seekGraphUp = TRUE;
3055                     DrawSeekGraph();
3056                     continue;
3057                 }
3058                 if(appData.autoRefresh) {
3059                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3060                         int s = (ics_type == ICS_ICC); // ICC format differs
3061                         if(seekGraphUp)
3062                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3063                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3064                         looking_at(buf, &i, "*% "); // eat prompt
3065                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3066                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067                         next_out = i; // suppress
3068                         continue;
3069                     }
3070                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3071                         char *p = star_match[0];
3072                         while(*p) {
3073                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3074                             while(*p && *p++ != ' '); // next
3075                         }
3076                         looking_at(buf, &i, "*% "); // eat prompt
3077                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3078                         next_out = i;
3079                         continue;
3080                     }
3081                 }
3082             }
3083
3084             /* skip formula vars */
3085             if (started == STARTED_NONE &&
3086                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3087               started = STARTED_CHATTER;
3088               i += 3;
3089               continue;
3090             }
3091
3092             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3093             if (appData.autoKibitz && started == STARTED_NONE &&
3094                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3095                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3096                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3097                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3098                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3099                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3100                         suppressKibitz = TRUE;
3101                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3102                         next_out = i;
3103                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3104                                 && (gameMode == IcsPlayingWhite)) ||
3105                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3106                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3107                             started = STARTED_CHATTER; // own kibitz we simply discard
3108                         else {
3109                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3110                             parse_pos = 0; parse[0] = NULLCHAR;
3111                             savingComment = TRUE;
3112                             suppressKibitz = gameMode != IcsObserving ? 2 :
3113                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3114                         }
3115                         continue;
3116                 } else
3117                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3118                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3119                          && atoi(star_match[0])) {
3120                     // suppress the acknowledgements of our own autoKibitz
3121                     char *p;
3122                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3124                     SendToPlayer(star_match[0], strlen(star_match[0]));
3125                     if(looking_at(buf, &i, "*% ")) // eat prompt
3126                         suppressKibitz = FALSE;
3127                     next_out = i;
3128                     continue;
3129                 }
3130             } // [HGM] kibitz: end of patch
3131
3132             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3133
3134             // [HGM] chat: intercept tells by users for which we have an open chat window
3135             channel = -1;
3136             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3137                                            looking_at(buf, &i, "* whispers:") ||
3138                                            looking_at(buf, &i, "* kibitzes:") ||
3139                                            looking_at(buf, &i, "* shouts:") ||
3140                                            looking_at(buf, &i, "* c-shouts:") ||
3141                                            looking_at(buf, &i, "--> * ") ||
3142                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3143                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3144                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3145                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3146                 int p;
3147                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3148                 chattingPartner = -1;
3149
3150                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3151                 for(p=0; p<MAX_CHAT; p++) {
3152                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3153                     talker[0] = '['; strcat(talker, "] ");
3154                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3155                     chattingPartner = p; break;
3156                     }
3157                 } else
3158                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3159                 for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("kibitzes", chatPartner[p])) {
3161                         talker[0] = '['; strcat(talker, "] ");
3162                         chattingPartner = p; break;
3163                     }
3164                 } else
3165                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3166                 for(p=0; p<MAX_CHAT; p++) {
3167                     if(!strcmp("whispers", chatPartner[p])) {
3168                         talker[0] = '['; strcat(talker, "] ");
3169                         chattingPartner = p; break;
3170                     }
3171                 } else
3172                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3173                   if(buf[i-8] == '-' && buf[i-3] == 't')
3174                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3175                     if(!strcmp("c-shouts", chatPartner[p])) {
3176                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3177                         chattingPartner = p; break;
3178                     }
3179                   }
3180                   if(chattingPartner < 0)
3181                   for(p=0; p<MAX_CHAT; p++) {
3182                     if(!strcmp("shouts", chatPartner[p])) {
3183                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3184                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3185                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3186                         chattingPartner = p; break;
3187                     }
3188                   }
3189                 }
3190                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3191                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3192                     talker[0] = 0; Colorize(ColorTell, FALSE);
3193                     chattingPartner = p; break;
3194                 }
3195                 if(chattingPartner<0) i = oldi; else {
3196                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3197                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3198                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199                     started = STARTED_COMMENT;
3200                     parse_pos = 0; parse[0] = NULLCHAR;
3201                     savingComment = 3 + chattingPartner; // counts as TRUE
3202                     suppressKibitz = TRUE;
3203                     continue;
3204                 }
3205             } // [HGM] chat: end of patch
3206
3207           backup = i;
3208             if (appData.zippyTalk || appData.zippyPlay) {
3209                 /* [DM] Backup address for color zippy lines */
3210 #if ZIPPY
3211                if (loggedOn == TRUE)
3212                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3213                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3214 #endif
3215             } // [DM] 'else { ' deleted
3216                 if (
3217                     /* Regular tells and says */
3218                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3219                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3220                     looking_at(buf, &i, "* says: ") ||
3221                     /* Don't color "message" or "messages" output */
3222                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3223                     looking_at(buf, &i, "*. * at *:*: ") ||
3224                     looking_at(buf, &i, "--* (*:*): ") ||
3225                     /* Message notifications (same color as tells) */
3226                     looking_at(buf, &i, "* has left a message ") ||
3227                     looking_at(buf, &i, "* just sent you a message:\n") ||
3228                     /* Whispers and kibitzes */
3229                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3230                     looking_at(buf, &i, "* kibitzes: ") ||
3231                     /* Channel tells */
3232                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3233
3234                   if (tkind == 1 && strchr(star_match[0], ':')) {
3235                       /* Avoid "tells you:" spoofs in channels */
3236                      tkind = 3;
3237                   }
3238                   if (star_match[0][0] == NULLCHAR ||
3239                       strchr(star_match[0], ' ') ||
3240                       (tkind == 3 && strchr(star_match[1], ' '))) {
3241                     /* Reject bogus matches */
3242                     i = oldi;
3243                   } else {
3244                     if (appData.colorize) {
3245                       if (oldi > next_out) {
3246                         SendToPlayer(&buf[next_out], oldi - next_out);
3247                         next_out = oldi;
3248                       }
3249                       switch (tkind) {
3250                       case 1:
3251                         Colorize(ColorTell, FALSE);
3252                         curColor = ColorTell;
3253                         break;
3254                       case 2:
3255                         Colorize(ColorKibitz, FALSE);
3256                         curColor = ColorKibitz;
3257                         break;
3258                       case 3:
3259                         p = strrchr(star_match[1], '(');
3260                         if (p == NULL) {
3261                           p = star_match[1];
3262                         } else {
3263                           p++;
3264                         }
3265                         if (atoi(p) == 1) {
3266                           Colorize(ColorChannel1, FALSE);
3267                           curColor = ColorChannel1;
3268                         } else {
3269                           Colorize(ColorChannel, FALSE);
3270                           curColor = ColorChannel;
3271                         }
3272                         break;
3273                       case 5:
3274                         curColor = ColorNormal;
3275                         break;
3276                       }
3277                     }
3278                     if (started == STARTED_NONE && appData.autoComment &&
3279                         (gameMode == IcsObserving ||
3280                          gameMode == IcsPlayingWhite ||
3281                          gameMode == IcsPlayingBlack)) {
3282                       parse_pos = i - oldi;
3283                       memcpy(parse, &buf[oldi], parse_pos);
3284                       parse[parse_pos] = NULLCHAR;
3285                       started = STARTED_COMMENT;
3286                       savingComment = TRUE;
3287                     } else {
3288                       started = STARTED_CHATTER;
3289                       savingComment = FALSE;
3290                     }
3291                     loggedOn = TRUE;
3292                     continue;
3293                   }
3294                 }
3295
3296                 if (looking_at(buf, &i, "* s-shouts: ") ||
3297                     looking_at(buf, &i, "* c-shouts: ")) {
3298                     if (appData.colorize) {
3299                         if (oldi > next_out) {
3300                             SendToPlayer(&buf[next_out], oldi - next_out);
3301                             next_out = oldi;
3302                         }
3303                         Colorize(ColorSShout, FALSE);
3304                         curColor = ColorSShout;
3305                     }
3306                     loggedOn = TRUE;
3307                     started = STARTED_CHATTER;
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "--->")) {
3312                     loggedOn = TRUE;
3313                     continue;
3314                 }
3315
3316                 if (looking_at(buf, &i, "* shouts: ") ||
3317                     looking_at(buf, &i, "--> ")) {
3318                     if (appData.colorize) {
3319                         if (oldi > next_out) {
3320                             SendToPlayer(&buf[next_out], oldi - next_out);
3321                             next_out = oldi;
3322                         }
3323                         Colorize(ColorShout, FALSE);
3324                         curColor = ColorShout;
3325                     }
3326                     loggedOn = TRUE;
3327                     started = STARTED_CHATTER;
3328                     continue;
3329                 }
3330
3331                 if (looking_at( buf, &i, "Challenge:")) {
3332                     if (appData.colorize) {
3333                         if (oldi > next_out) {
3334                             SendToPlayer(&buf[next_out], oldi - next_out);
3335                             next_out = oldi;
3336                         }
3337                         Colorize(ColorChallenge, FALSE);
3338                         curColor = ColorChallenge;
3339                     }
3340                     loggedOn = TRUE;
3341                     continue;
3342                 }
3343
3344                 if (looking_at(buf, &i, "* offers you") ||
3345                     looking_at(buf, &i, "* offers to be") ||
3346                     looking_at(buf, &i, "* would like to") ||
3347                     looking_at(buf, &i, "* requests to") ||
3348                     looking_at(buf, &i, "Your opponent offers") ||
3349                     looking_at(buf, &i, "Your opponent requests")) {
3350
3351                     if (appData.colorize) {
3352                         if (oldi > next_out) {
3353                             SendToPlayer(&buf[next_out], oldi - next_out);
3354                             next_out = oldi;
3355                         }
3356                         Colorize(ColorRequest, FALSE);
3357                         curColor = ColorRequest;
3358                     }
3359                     continue;
3360                 }
3361
3362                 if (looking_at(buf, &i, "* (*) seeking")) {
3363                     if (appData.colorize) {
3364                         if (oldi > next_out) {
3365                             SendToPlayer(&buf[next_out], oldi - next_out);
3366                             next_out = oldi;
3367                         }
3368                         Colorize(ColorSeek, FALSE);
3369                         curColor = ColorSeek;
3370                     }
3371                     continue;
3372             }
3373
3374           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3375
3376             if (looking_at(buf, &i, "\\   ")) {
3377                 if (prevColor != ColorNormal) {
3378                     if (oldi > next_out) {
3379                         SendToPlayer(&buf[next_out], oldi - next_out);
3380                         next_out = oldi;
3381                     }
3382                     Colorize(prevColor, TRUE);
3383                     curColor = prevColor;
3384                 }
3385                 if (savingComment) {
3386                     parse_pos = i - oldi;
3387                     memcpy(parse, &buf[oldi], parse_pos);
3388                     parse[parse_pos] = NULLCHAR;
3389                     started = STARTED_COMMENT;
3390                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3391                         chattingPartner = savingComment - 3; // kludge to remember the box
3392                 } else {
3393                     started = STARTED_CHATTER;
3394                 }
3395                 continue;
3396             }
3397
3398             if (looking_at(buf, &i, "Black Strength :") ||
3399                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3400                 looking_at(buf, &i, "<10>") ||
3401                 looking_at(buf, &i, "#@#")) {
3402                 /* Wrong board style */
3403                 loggedOn = TRUE;
3404                 SendToICS(ics_prefix);
3405                 SendToICS("set style 12\n");
3406                 SendToICS(ics_prefix);
3407                 SendToICS("refresh\n");
3408                 continue;
3409             }
3410
3411             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3412                 ICSInitScript();
3413                 have_sent_ICS_logon = 1;
3414                 continue;
3415             }
3416
3417             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3418                 (looking_at(buf, &i, "\n<12> ") ||
3419                  looking_at(buf, &i, "<12> "))) {
3420                 loggedOn = TRUE;
3421                 if (oldi > next_out) {
3422                     SendToPlayer(&buf[next_out], oldi - next_out);
3423                 }
3424                 next_out = i;
3425                 started = STARTED_BOARD;
3426                 parse_pos = 0;
3427                 continue;
3428             }
3429
3430             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3431                 looking_at(buf, &i, "<b1> ")) {
3432                 if (oldi > next_out) {
3433                     SendToPlayer(&buf[next_out], oldi - next_out);
3434                 }
3435                 next_out = i;
3436                 started = STARTED_HOLDINGS;
3437                 parse_pos = 0;
3438                 continue;
3439             }
3440
3441             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3442                 loggedOn = TRUE;
3443                 /* Header for a move list -- first line */
3444
3445                 switch (ics_getting_history) {
3446                   case H_FALSE:
3447                     switch (gameMode) {
3448                       case IcsIdle:
3449                       case BeginningOfGame:
3450                         /* User typed "moves" or "oldmoves" while we
3451                            were idle.  Pretend we asked for these
3452                            moves and soak them up so user can step
3453                            through them and/or save them.
3454                            */
3455                         Reset(FALSE, TRUE);
3456                         gameMode = IcsObserving;
3457                         ModeHighlight();
3458                         ics_gamenum = -1;
3459                         ics_getting_history = H_GOT_UNREQ_HEADER;
3460                         break;
3461                       case EditGame: /*?*/
3462                       case EditPosition: /*?*/
3463                         /* Should above feature work in these modes too? */
3464                         /* For now it doesn't */
3465                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3466                         break;
3467                       default:
3468                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3469                         break;
3470                     }
3471                     break;
3472                   case H_REQUESTED:
3473                     /* Is this the right one? */
3474                     if (gameInfo.white && gameInfo.black &&
3475                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3476                         strcmp(gameInfo.black, star_match[2]) == 0) {
3477                         /* All is well */
3478                         ics_getting_history = H_GOT_REQ_HEADER;
3479                     }
3480                     break;
3481                   case H_GOT_REQ_HEADER:
3482                   case H_GOT_UNREQ_HEADER:
3483                   case H_GOT_UNWANTED_HEADER:
3484                   case H_GETTING_MOVES:
3485                     /* Should not happen */
3486                     DisplayError(_("Error gathering move list: two headers"), 0);
3487                     ics_getting_history = H_FALSE;
3488                     break;
3489                 }
3490
3491                 /* Save player ratings into gameInfo if needed */
3492                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3493                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3494                     (gameInfo.whiteRating == -1 ||
3495                      gameInfo.blackRating == -1)) {
3496
3497                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3498                     gameInfo.blackRating = string_to_rating(star_match[3]);
3499                     if (appData.debugMode)
3500                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3501                               gameInfo.whiteRating, gameInfo.blackRating);
3502                 }
3503                 continue;
3504             }
3505
3506             if (looking_at(buf, &i,
3507               "* * match, initial time: * minute*, increment: * second")) {
3508                 /* Header for a move list -- second line */
3509                 /* Initial board will follow if this is a wild game */
3510                 if (gameInfo.event != NULL) free(gameInfo.event);
3511                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3512                 gameInfo.event = StrSave(str);
3513                 /* [HGM] we switched variant. Translate boards if needed. */
3514                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3515                 continue;
3516             }
3517
3518             if (looking_at(buf, &i, "Move  ")) {
3519                 /* Beginning of a move list */
3520                 switch (ics_getting_history) {
3521                   case H_FALSE:
3522                     /* Normally should not happen */
3523                     /* Maybe user hit reset while we were parsing */
3524                     break;
3525                   case H_REQUESTED:
3526                     /* Happens if we are ignoring a move list that is not
3527                      * the one we just requested.  Common if the user
3528                      * tries to observe two games without turning off
3529                      * getMoveList */
3530                     break;
3531                   case H_GETTING_MOVES:
3532                     /* Should not happen */
3533                     DisplayError(_("Error gathering move list: nested"), 0);
3534                     ics_getting_history = H_FALSE;
3535                     break;
3536                   case H_GOT_REQ_HEADER:
3537                     ics_getting_history = H_GETTING_MOVES;
3538                     started = STARTED_MOVES;
3539                     parse_pos = 0;
3540                     if (oldi > next_out) {
3541                         SendToPlayer(&buf[next_out], oldi - next_out);
3542                     }
3543                     break;
3544                   case H_GOT_UNREQ_HEADER:
3545                     ics_getting_history = H_GETTING_MOVES;
3546                     started = STARTED_MOVES_NOHIDE;
3547                     parse_pos = 0;
3548                     break;
3549                   case H_GOT_UNWANTED_HEADER:
3550                     ics_getting_history = H_FALSE;
3551                     break;
3552                 }
3553                 continue;
3554             }
3555
3556             if (looking_at(buf, &i, "% ") ||
3557                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3558                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3559                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3560                     soughtPending = FALSE;
3561                     seekGraphUp = TRUE;
3562                     DrawSeekGraph();
3563                 }
3564                 if(suppressKibitz) next_out = i;
3565                 savingComment = FALSE;
3566                 suppressKibitz = 0;
3567                 switch (started) {
3568                   case STARTED_MOVES:
3569                   case STARTED_MOVES_NOHIDE:
3570                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3571                     parse[parse_pos + i - oldi] = NULLCHAR;
3572                     ParseGameHistory(parse);
3573 #if ZIPPY
3574                     if (appData.zippyPlay && first.initDone) {
3575                         FeedMovesToProgram(&first, forwardMostMove);
3576                         if (gameMode == IcsPlayingWhite) {
3577                             if (WhiteOnMove(forwardMostMove)) {
3578                                 if (first.sendTime) {
3579                                   if (first.useColors) {
3580                                     SendToProgram("black\n", &first);
3581                                   }
3582                                   SendTimeRemaining(&first, TRUE);
3583                                 }
3584                                 if (first.useColors) {
3585                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3586                                 }
3587                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3588                                 first.maybeThinking = TRUE;
3589                             } else {
3590                                 if (first.usePlayother) {
3591                                   if (first.sendTime) {
3592                                     SendTimeRemaining(&first, TRUE);
3593                                   }
3594                                   SendToProgram("playother\n", &first);
3595                                   firstMove = FALSE;
3596                                 } else {
3597                                   firstMove = TRUE;
3598                                 }
3599                             }
3600                         } else if (gameMode == IcsPlayingBlack) {
3601                             if (!WhiteOnMove(forwardMostMove)) {
3602                                 if (first.sendTime) {
3603                                   if (first.useColors) {
3604                                     SendToProgram("white\n", &first);
3605                                   }
3606                                   SendTimeRemaining(&first, FALSE);
3607                                 }
3608                                 if (first.useColors) {
3609                                   SendToProgram("black\n", &first);
3610                                 }
3611                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3612                                 first.maybeThinking = TRUE;
3613                             } else {
3614                                 if (first.usePlayother) {
3615                                   if (first.sendTime) {
3616                                     SendTimeRemaining(&first, FALSE);
3617                                   }
3618                                   SendToProgram("playother\n", &first);
3619                                   firstMove = FALSE;
3620                                 } else {
3621                                   firstMove = TRUE;
3622                                 }
3623                             }
3624                         }
3625                     }
3626 #endif
3627                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3628                         /* Moves came from oldmoves or moves command
3629                            while we weren't doing anything else.
3630                            */
3631                         currentMove = forwardMostMove;
3632                         ClearHighlights();/*!!could figure this out*/
3633                         flipView = appData.flipView;
3634                         DrawPosition(TRUE, boards[currentMove]);
3635                         DisplayBothClocks();
3636                         snprintf(str, MSG_SIZ, "%s %s %s",
3637                                 gameInfo.white, _("vs."),  gameInfo.black);
3638                         DisplayTitle(str);
3639                         gameMode = IcsIdle;
3640                     } else {
3641                         /* Moves were history of an active game */
3642                         if (gameInfo.resultDetails != NULL) {
3643                             free(gameInfo.resultDetails);
3644                             gameInfo.resultDetails = NULL;
3645                         }
3646                     }
3647                     HistorySet(parseList, backwardMostMove,
3648                                forwardMostMove, currentMove-1);
3649                     DisplayMove(currentMove - 1);
3650                     if (started == STARTED_MOVES) next_out = i;
3651                     started = STARTED_NONE;
3652                     ics_getting_history = H_FALSE;
3653                     break;
3654
3655                   case STARTED_OBSERVE:
3656                     started = STARTED_NONE;
3657                     SendToICS(ics_prefix);
3658                     SendToICS("refresh\n");
3659                     break;
3660
3661                   default:
3662                     break;
3663                 }
3664                 if(bookHit) { // [HGM] book: simulate book reply
3665                     static char bookMove[MSG_SIZ]; // a bit generous?
3666
3667                     programStats.nodes = programStats.depth = programStats.time =
3668                     programStats.score = programStats.got_only_move = 0;
3669                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3670
3671                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3672                     strcat(bookMove, bookHit);
3673                     HandleMachineMove(bookMove, &first);
3674                 }
3675                 continue;
3676             }
3677
3678             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3679                  started == STARTED_HOLDINGS ||
3680                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3681                 /* Accumulate characters in move list or board */
3682                 parse[parse_pos++] = buf[i];
3683             }
3684
3685             /* Start of game messages.  Mostly we detect start of game
3686                when the first board image arrives.  On some versions
3687                of the ICS, though, we need to do a "refresh" after starting
3688                to observe in order to get the current board right away. */
3689             if (looking_at(buf, &i, "Adding game * to observation list")) {
3690                 started = STARTED_OBSERVE;
3691                 continue;
3692             }
3693
3694             /* Handle auto-observe */
3695             if (appData.autoObserve &&
3696                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3697                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3698                 char *player;
3699                 /* Choose the player that was highlighted, if any. */
3700                 if (star_match[0][0] == '\033' ||
3701                     star_match[1][0] != '\033') {
3702                     player = star_match[0];
3703                 } else {
3704                     player = star_match[2];
3705                 }
3706                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3707                         ics_prefix, StripHighlightAndTitle(player));
3708                 SendToICS(str);
3709
3710                 /* Save ratings from notify string */
3711                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3712                 player1Rating = string_to_rating(star_match[1]);
3713                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3714                 player2Rating = string_to_rating(star_match[3]);
3715
3716                 if (appData.debugMode)
3717                   fprintf(debugFP,
3718                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3719                           player1Name, player1Rating,
3720                           player2Name, player2Rating);
3721
3722                 continue;
3723             }
3724
3725             /* Deal with automatic examine mode after a game,
3726                and with IcsObserving -> IcsExamining transition */
3727             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3728                 looking_at(buf, &i, "has made you an examiner of game *")) {
3729
3730                 int gamenum = atoi(star_match[0]);
3731                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3732                     gamenum == ics_gamenum) {
3733                     /* We were already playing or observing this game;
3734                        no need to refetch history */
3735                     gameMode = IcsExamining;
3736                     if (pausing) {
3737                         pauseExamForwardMostMove = forwardMostMove;
3738                     } else if (currentMove < forwardMostMove) {
3739                         ForwardInner(forwardMostMove);
3740                     }
3741                 } else {
3742                     /* I don't think this case really can happen */
3743                     SendToICS(ics_prefix);
3744                     SendToICS("refresh\n");
3745                 }
3746                 continue;
3747             }
3748
3749             /* Error messages */
3750 //          if (ics_user_moved) {
3751             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3752                 if (looking_at(buf, &i, "Illegal move") ||
3753                     looking_at(buf, &i, "Not a legal move") ||
3754                     looking_at(buf, &i, "Your king is in check") ||
3755                     looking_at(buf, &i, "It isn't your turn") ||
3756                     looking_at(buf, &i, "It is not your move")) {
3757                     /* Illegal move */
3758                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3759                         currentMove = forwardMostMove-1;
3760                         DisplayMove(currentMove - 1); /* before DMError */
3761                         DrawPosition(FALSE, boards[currentMove]);
3762                         SwitchClocks(forwardMostMove-1); // [HGM] race
3763                         DisplayBothClocks();
3764                     }
3765                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3766                     ics_user_moved = 0;
3767                     continue;
3768                 }
3769             }
3770
3771             if (looking_at(buf, &i, "still have time") ||
3772                 looking_at(buf, &i, "not out of time") ||
3773                 looking_at(buf, &i, "either player is out of time") ||
3774                 looking_at(buf, &i, "has timeseal; checking")) {
3775                 /* We must have called his flag a little too soon */
3776                 whiteFlag = blackFlag = FALSE;
3777                 continue;
3778             }
3779
3780             if (looking_at(buf, &i, "added * seconds to") ||
3781                 looking_at(buf, &i, "seconds were added to")) {
3782                 /* Update the clocks */
3783                 SendToICS(ics_prefix);
3784                 SendToICS("refresh\n");
3785                 continue;
3786             }
3787
3788             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3789                 ics_clock_paused = TRUE;
3790                 StopClocks();
3791                 continue;
3792             }
3793
3794             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3795                 ics_clock_paused = FALSE;
3796                 StartClocks();
3797                 continue;
3798             }
3799
3800             /* Grab player ratings from the Creating: message.
3801                Note we have to check for the special case when
3802                the ICS inserts things like [white] or [black]. */
3803             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3804                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3805                 /* star_matches:
3806                    0    player 1 name (not necessarily white)
3807                    1    player 1 rating
3808                    2    empty, white, or black (IGNORED)
3809                    3    player 2 name (not necessarily black)
3810                    4    player 2 rating
3811
3812                    The names/ratings are sorted out when the game
3813                    actually starts (below).
3814                 */
3815                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3816                 player1Rating = string_to_rating(star_match[1]);
3817                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3818                 player2Rating = string_to_rating(star_match[4]);
3819
3820                 if (appData.debugMode)
3821                   fprintf(debugFP,
3822                           "Ratings from 'Creating:' %s %d, %s %d\n",
3823                           player1Name, player1Rating,
3824                           player2Name, player2Rating);
3825
3826                 continue;
3827             }
3828
3829             /* Improved generic start/end-of-game messages */
3830             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3831                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3832                 /* If tkind == 0: */
3833                 /* star_match[0] is the game number */
3834                 /*           [1] is the white player's name */
3835                 /*           [2] is the black player's name */
3836                 /* For end-of-game: */
3837                 /*           [3] is the reason for the game end */
3838                 /*           [4] is a PGN end game-token, preceded by " " */
3839                 /* For start-of-game: */
3840                 /*           [3] begins with "Creating" or "Continuing" */
3841                 /*           [4] is " *" or empty (don't care). */
3842                 int gamenum = atoi(star_match[0]);
3843                 char *whitename, *blackname, *why, *endtoken;
3844                 ChessMove endtype = EndOfFile;
3845
3846                 if (tkind == 0) {
3847                   whitename = star_match[1];
3848                   blackname = star_match[2];
3849                   why = star_match[3];
3850                   endtoken = star_match[4];
3851                 } else {
3852                   whitename = star_match[1];
3853                   blackname = star_match[3];
3854                   why = star_match[5];
3855                   endtoken = star_match[6];
3856                 }
3857
3858                 /* Game start messages */
3859                 if (strncmp(why, "Creating ", 9) == 0 ||
3860                     strncmp(why, "Continuing ", 11) == 0) {
3861                     gs_gamenum = gamenum;
3862                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3863                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3864                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3865 #if ZIPPY
3866                     if (appData.zippyPlay) {
3867                         ZippyGameStart(whitename, blackname);
3868                     }
3869 #endif /*ZIPPY*/
3870                     partnerBoardValid = FALSE; // [HGM] bughouse
3871                     continue;
3872                 }
3873
3874                 /* Game end messages */
3875                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3876                     ics_gamenum != gamenum) {
3877                     continue;
3878                 }
3879                 while (endtoken[0] == ' ') endtoken++;
3880                 switch (endtoken[0]) {
3881                   case '*':
3882                   default:
3883                     endtype = GameUnfinished;
3884                     break;
3885                   case '0':
3886                     endtype = BlackWins;
3887                     break;
3888                   case '1':
3889                     if (endtoken[1] == '/')
3890                       endtype = GameIsDrawn;
3891                     else
3892                       endtype = WhiteWins;
3893                     break;
3894                 }
3895                 GameEnds(endtype, why, GE_ICS);
3896 #if ZIPPY
3897                 if (appData.zippyPlay && first.initDone) {
3898                     ZippyGameEnd(endtype, why);
3899                     if (first.pr == NoProc) {
3900                       /* Start the next process early so that we'll
3901                          be ready for the next challenge */
3902                       StartChessProgram(&first);
3903                     }
3904                     /* Send "new" early, in case this command takes
3905                        a long time to finish, so that we'll be ready
3906                        for the next challenge. */
3907                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3908                     Reset(TRUE, TRUE);
3909                 }
3910 #endif /*ZIPPY*/
3911                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3912                 continue;
3913             }
3914
3915             if (looking_at(buf, &i, "Removing game * from observation") ||
3916                 looking_at(buf, &i, "no longer observing game *") ||
3917                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3918                 if (gameMode == IcsObserving &&
3919                     atoi(star_match[0]) == ics_gamenum)
3920                   {
3921                       /* icsEngineAnalyze */
3922                       if (appData.icsEngineAnalyze) {
3923                             ExitAnalyzeMode();
3924                             ModeHighlight();
3925                       }
3926                       StopClocks();
3927                       gameMode = IcsIdle;
3928                       ics_gamenum = -1;
3929                       ics_user_moved = FALSE;
3930                   }
3931                 continue;
3932             }
3933
3934             if (looking_at(buf, &i, "no longer examining game *")) {
3935                 if (gameMode == IcsExamining &&
3936                     atoi(star_match[0]) == ics_gamenum)
3937                   {
3938                       gameMode = IcsIdle;
3939                       ics_gamenum = -1;
3940                       ics_user_moved = FALSE;
3941                   }
3942                 continue;
3943             }
3944
3945             /* Advance leftover_start past any newlines we find,
3946                so only partial lines can get reparsed */
3947             if (looking_at(buf, &i, "\n")) {
3948                 prevColor = curColor;
3949                 if (curColor != ColorNormal) {
3950                     if (oldi > next_out) {
3951                         SendToPlayer(&buf[next_out], oldi - next_out);
3952                         next_out = oldi;
3953                     }
3954                     Colorize(ColorNormal, FALSE);
3955                     curColor = ColorNormal;
3956                 }
3957                 if (started == STARTED_BOARD) {
3958                     started = STARTED_NONE;
3959                     parse[parse_pos] = NULLCHAR;
3960                     ParseBoard12(parse);
3961                     ics_user_moved = 0;
3962
3963                     /* Send premove here */
3964                     if (appData.premove) {
3965                       char str[MSG_SIZ];
3966                       if (currentMove == 0 &&
3967                           gameMode == IcsPlayingWhite &&
3968                           appData.premoveWhite) {
3969                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3970                         if (appData.debugMode)
3971                           fprintf(debugFP, "Sending premove:\n");
3972                         SendToICS(str);
3973                       } else if (currentMove == 1 &&
3974                                  gameMode == IcsPlayingBlack &&
3975                                  appData.premoveBlack) {
3976                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3977                         if (appData.debugMode)
3978                           fprintf(debugFP, "Sending premove:\n");
3979                         SendToICS(str);
3980                       } else if (gotPremove) {
3981                         gotPremove = 0;
3982                         ClearPremoveHighlights();
3983                         if (appData.debugMode)
3984                           fprintf(debugFP, "Sending premove:\n");
3985                           UserMoveEvent(premoveFromX, premoveFromY,
3986                                         premoveToX, premoveToY,
3987                                         premovePromoChar);
3988                       }
3989                     }
3990
3991                     /* Usually suppress following prompt */
3992                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3993                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3994                         if (looking_at(buf, &i, "*% ")) {
3995                             savingComment = FALSE;
3996                             suppressKibitz = 0;
3997                         }
3998                     }
3999                     next_out = i;
4000                 } else if (started == STARTED_HOLDINGS) {
4001                     int gamenum;
4002                     char new_piece[MSG_SIZ];
4003                     started = STARTED_NONE;
4004                     parse[parse_pos] = NULLCHAR;
4005                     if (appData.debugMode)
4006                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4007                                                         parse, currentMove);
4008                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4009                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4010                         if (gameInfo.variant == VariantNormal) {
4011                           /* [HGM] We seem to switch variant during a game!
4012                            * Presumably no holdings were displayed, so we have
4013                            * to move the position two files to the right to
4014                            * create room for them!
4015                            */
4016                           VariantClass newVariant;
4017                           switch(gameInfo.boardWidth) { // base guess on board width
4018                                 case 9:  newVariant = VariantShogi; break;
4019                                 case 10: newVariant = VariantGreat; break;
4020                                 default: newVariant = VariantCrazyhouse; break;
4021                           }
4022                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4023                           /* Get a move list just to see the header, which
4024                              will tell us whether this is really bug or zh */
4025                           if (ics_getting_history == H_FALSE) {
4026                             ics_getting_history = H_REQUESTED;
4027                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4028                             SendToICS(str);
4029                           }
4030                         }
4031                         new_piece[0] = NULLCHAR;
4032                         sscanf(parse, "game %d white [%s black [%s <- %s",
4033                                &gamenum, white_holding, black_holding,
4034                                new_piece);
4035                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4036                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4037                         /* [HGM] copy holdings to board holdings area */
4038                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4039                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4040                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4041 #if ZIPPY
4042                         if (appData.zippyPlay && first.initDone) {
4043                             ZippyHoldings(white_holding, black_holding,
4044                                           new_piece);
4045                         }
4046 #endif /*ZIPPY*/
4047                         if (tinyLayout || smallLayout) {
4048                             char wh[16], bh[16];
4049                             PackHolding(wh, white_holding);
4050                             PackHolding(bh, black_holding);
4051                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4052                                     gameInfo.white, gameInfo.black);
4053                         } else {
4054                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4055                                     gameInfo.white, white_holding, _("vs."),
4056                                     gameInfo.black, black_holding);
4057                         }
4058                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4059                         DrawPosition(FALSE, boards[currentMove]);
4060                         DisplayTitle(str);
4061                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4062                         sscanf(parse, "game %d white [%s black [%s <- %s",
4063                                &gamenum, white_holding, black_holding,
4064                                new_piece);
4065                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4066                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4067                         /* [HGM] copy holdings to partner-board holdings area */
4068                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4069                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4070                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4071                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4072                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4073                       }
4074                     }
4075                     /* Suppress following prompt */
4076                     if (looking_at(buf, &i, "*% ")) {
4077                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4078                         savingComment = FALSE;
4079                         suppressKibitz = 0;
4080                     }
4081                     next_out = i;
4082                 }
4083                 continue;
4084             }
4085
4086             i++;                /* skip unparsed character and loop back */
4087         }
4088
4089         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4090 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4091 //          SendToPlayer(&buf[next_out], i - next_out);
4092             started != STARTED_HOLDINGS && leftover_start > next_out) {
4093             SendToPlayer(&buf[next_out], leftover_start - next_out);
4094             next_out = i;
4095         }
4096
4097         leftover_len = buf_len - leftover_start;
4098         /* if buffer ends with something we couldn't parse,
4099            reparse it after appending the next read */
4100
4101     } else if (count == 0) {
4102         RemoveInputSource(isr);
4103         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4104     } else {
4105         DisplayFatalError(_("Error reading from ICS"), error, 1);
4106     }
4107 }
4108
4109
4110 /* Board style 12 looks like this:
4111
4112    <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
4113
4114  * The "<12> " is stripped before it gets to this routine.  The two
4115  * trailing 0's (flip state and clock ticking) are later addition, and
4116  * some chess servers may not have them, or may have only the first.
4117  * Additional trailing fields may be added in the future.
4118  */
4119
4120 #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"
4121
4122 #define RELATION_OBSERVING_PLAYED    0
4123 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4124 #define RELATION_PLAYING_MYMOVE      1
4125 #define RELATION_PLAYING_NOTMYMOVE  -1
4126 #define RELATION_EXAMINING           2
4127 #define RELATION_ISOLATED_BOARD     -3
4128 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4129
4130 void
4131 ParseBoard12 (char *string)
4132 {
4133     GameMode newGameMode;
4134     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4135     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4136     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4137     char to_play, board_chars[200];
4138     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4139     char black[32], white[32];
4140     Board board;
4141     int prevMove = currentMove;
4142     int ticking = 2;
4143     ChessMove moveType;
4144     int fromX, fromY, toX, toY;
4145     char promoChar;
4146     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4147     char *bookHit = NULL; // [HGM] book
4148     Boolean weird = FALSE, reqFlag = FALSE;
4149
4150     fromX = fromY = toX = toY = -1;
4151
4152     newGame = FALSE;
4153
4154     if (appData.debugMode)
4155       fprintf(debugFP, _("Parsing board: %s\n"), string);
4156
4157     move_str[0] = NULLCHAR;
4158     elapsed_time[0] = NULLCHAR;
4159     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4160         int  i = 0, j;
4161         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4162             if(string[i] == ' ') { ranks++; files = 0; }
4163             else files++;
4164             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4165             i++;
4166         }
4167         for(j = 0; j <i; j++) board_chars[j] = string[j];
4168         board_chars[i] = '\0';
4169         string += i + 1;
4170     }
4171     n = sscanf(string, PATTERN, &to_play, &double_push,
4172                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4173                &gamenum, white, black, &relation, &basetime, &increment,
4174                &white_stren, &black_stren, &white_time, &black_time,
4175                &moveNum, str, elapsed_time, move_str, &ics_flip,
4176                &ticking);
4177
4178     if (n < 21) {
4179         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4180         DisplayError(str, 0);
4181         return;
4182     }
4183
4184     /* Convert the move number to internal form */
4185     moveNum = (moveNum - 1) * 2;
4186     if (to_play == 'B') moveNum++;
4187     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4188       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4189                         0, 1);
4190       return;
4191     }
4192
4193     switch (relation) {
4194       case RELATION_OBSERVING_PLAYED:
4195       case RELATION_OBSERVING_STATIC:
4196         if (gamenum == -1) {
4197             /* Old ICC buglet */
4198             relation = RELATION_OBSERVING_STATIC;
4199         }
4200         newGameMode = IcsObserving;
4201         break;
4202       case RELATION_PLAYING_MYMOVE:
4203       case RELATION_PLAYING_NOTMYMOVE:
4204         newGameMode =
4205           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4206             IcsPlayingWhite : IcsPlayingBlack;
4207         break;
4208       case RELATION_EXAMINING:
4209         newGameMode = IcsExamining;
4210         break;
4211       case RELATION_ISOLATED_BOARD:
4212       default:
4213         /* Just display this board.  If user was doing something else,
4214            we will forget about it until the next board comes. */
4215         newGameMode = IcsIdle;
4216         break;
4217       case RELATION_STARTING_POSITION:
4218         newGameMode = gameMode;
4219         break;
4220     }
4221
4222     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4223          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4224       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4225       char *toSqr;
4226       for (k = 0; k < ranks; k++) {
4227         for (j = 0; j < files; j++)
4228           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4229         if(gameInfo.holdingsWidth > 1) {
4230              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4231              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4232         }
4233       }
4234       CopyBoard(partnerBoard, board);
4235       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4236         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4237         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4238       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4239       if(toSqr = strchr(str, '-')) {
4240         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4241         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4242       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4243       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4244       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4245       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4246       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4247       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4248                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4249       DisplayMessage(partnerStatus, "");
4250         partnerBoardValid = TRUE;
4251       return;
4252     }
4253
4254     /* Modify behavior for initial board display on move listing
4255        of wild games.
4256        */
4257     switch (ics_getting_history) {
4258       case H_FALSE:
4259       case H_REQUESTED:
4260         break;
4261       case H_GOT_REQ_HEADER:
4262       case H_GOT_UNREQ_HEADER:
4263         /* This is the initial position of the current game */
4264         gamenum = ics_gamenum;
4265         moveNum = 0;            /* old ICS bug workaround */
4266         if (to_play == 'B') {
4267           startedFromSetupPosition = TRUE;
4268           blackPlaysFirst = TRUE;
4269           moveNum = 1;
4270           if (forwardMostMove == 0) forwardMostMove = 1;
4271           if (backwardMostMove == 0) backwardMostMove = 1;
4272           if (currentMove == 0) currentMove = 1;
4273         }
4274         newGameMode = gameMode;
4275         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4276         break;
4277       case H_GOT_UNWANTED_HEADER:
4278         /* This is an initial board that we don't want */
4279         return;
4280       case H_GETTING_MOVES:
4281         /* Should not happen */
4282         DisplayError(_("Error gathering move list: extra board"), 0);
4283         ics_getting_history = H_FALSE;
4284         return;
4285     }
4286
4287    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4288                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4289      /* [HGM] We seem to have switched variant unexpectedly
4290       * Try to guess new variant from board size
4291       */
4292           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4293           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4294           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4295           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4296           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4297           if(!weird) newVariant = VariantNormal;
4298           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4299           /* Get a move list just to see the header, which
4300              will tell us whether this is really bug or zh */
4301           if (ics_getting_history == H_FALSE) {
4302             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4303             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4304             SendToICS(str);
4305           }
4306     }
4307
4308     /* Take action if this is the first board of a new game, or of a
4309        different game than is currently being displayed.  */
4310     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4311         relation == RELATION_ISOLATED_BOARD) {
4312
4313         /* Forget the old game and get the history (if any) of the new one */
4314         if (gameMode != BeginningOfGame) {
4315           Reset(TRUE, TRUE);
4316         }
4317         newGame = TRUE;
4318         if (appData.autoRaiseBoard) BoardToTop();
4319         prevMove = -3;
4320         if (gamenum == -1) {
4321             newGameMode = IcsIdle;
4322         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4323                    appData.getMoveList && !reqFlag) {
4324             /* Need to get game history */
4325             ics_getting_history = H_REQUESTED;
4326             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4327             SendToICS(str);
4328         }
4329
4330         /* Initially flip the board to have black on the bottom if playing
4331            black or if the ICS flip flag is set, but let the user change
4332            it with the Flip View button. */
4333         flipView = appData.autoFlipView ?
4334           (newGameMode == IcsPlayingBlack) || ics_flip :
4335           appData.flipView;
4336
4337         /* Done with values from previous mode; copy in new ones */
4338         gameMode = newGameMode;
4339         ModeHighlight();
4340         ics_gamenum = gamenum;
4341         if (gamenum == gs_gamenum) {
4342             int klen = strlen(gs_kind);
4343             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4344             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4345             gameInfo.event = StrSave(str);
4346         } else {
4347             gameInfo.event = StrSave("ICS game");
4348         }
4349         gameInfo.site = StrSave(appData.icsHost);
4350         gameInfo.date = PGNDate();
4351         gameInfo.round = StrSave("-");
4352         gameInfo.white = StrSave(white);
4353         gameInfo.black = StrSave(black);
4354         timeControl = basetime * 60 * 1000;
4355         timeControl_2 = 0;
4356         timeIncrement = increment * 1000;
4357         movesPerSession = 0;
4358         gameInfo.timeControl = TimeControlTagValue();
4359         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4360   if (appData.debugMode) {
4361     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4362     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4363     setbuf(debugFP, NULL);
4364   }
4365
4366         gameInfo.outOfBook = NULL;
4367
4368         /* Do we have the ratings? */
4369         if (strcmp(player1Name, white) == 0 &&
4370             strcmp(player2Name, black) == 0) {
4371             if (appData.debugMode)
4372               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4373                       player1Rating, player2Rating);
4374             gameInfo.whiteRating = player1Rating;
4375             gameInfo.blackRating = player2Rating;
4376         } else if (strcmp(player2Name, white) == 0 &&
4377                    strcmp(player1Name, black) == 0) {
4378             if (appData.debugMode)
4379               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4380                       player2Rating, player1Rating);
4381             gameInfo.whiteRating = player2Rating;
4382             gameInfo.blackRating = player1Rating;
4383         }
4384         player1Name[0] = player2Name[0] = NULLCHAR;
4385
4386         /* Silence shouts if requested */
4387         if (appData.quietPlay &&
4388             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4389             SendToICS(ics_prefix);
4390             SendToICS("set shout 0\n");
4391         }
4392     }
4393
4394     /* Deal with midgame name changes */
4395     if (!newGame) {
4396         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4397             if (gameInfo.white) free(gameInfo.white);
4398             gameInfo.white = StrSave(white);
4399         }
4400         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4401             if (gameInfo.black) free(gameInfo.black);
4402             gameInfo.black = StrSave(black);
4403         }
4404     }
4405
4406     /* Throw away game result if anything actually changes in examine mode */
4407     if (gameMode == IcsExamining && !newGame) {
4408         gameInfo.result = GameUnfinished;
4409         if (gameInfo.resultDetails != NULL) {
4410             free(gameInfo.resultDetails);
4411             gameInfo.resultDetails = NULL;
4412         }
4413     }
4414
4415     /* In pausing && IcsExamining mode, we ignore boards coming
4416        in if they are in a different variation than we are. */
4417     if (pauseExamInvalid) return;
4418     if (pausing && gameMode == IcsExamining) {
4419         if (moveNum <= pauseExamForwardMostMove) {
4420             pauseExamInvalid = TRUE;
4421             forwardMostMove = pauseExamForwardMostMove;
4422             return;
4423         }
4424     }
4425
4426   if (appData.debugMode) {
4427     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4428   }
4429     /* Parse the board */
4430     for (k = 0; k < ranks; k++) {
4431       for (j = 0; j < files; j++)
4432         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4433       if(gameInfo.holdingsWidth > 1) {
4434            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4435            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4436       }
4437     }
4438     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4439       board[5][BOARD_RGHT+1] = WhiteAngel;
4440       board[6][BOARD_RGHT+1] = WhiteMarshall;
4441       board[1][0] = BlackMarshall;
4442       board[2][0] = BlackAngel;
4443       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4444     }
4445     CopyBoard(boards[moveNum], board);
4446     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4447     if (moveNum == 0) {
4448         startedFromSetupPosition =
4449           !CompareBoards(board, initialPosition);
4450         if(startedFromSetupPosition)
4451             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4452     }
4453
4454     /* [HGM] Set castling rights. Take the outermost Rooks,
4455        to make it also work for FRC opening positions. Note that board12
4456        is really defective for later FRC positions, as it has no way to
4457        indicate which Rook can castle if they are on the same side of King.
4458        For the initial position we grant rights to the outermost Rooks,
4459        and remember thos rights, and we then copy them on positions
4460        later in an FRC game. This means WB might not recognize castlings with
4461        Rooks that have moved back to their original position as illegal,
4462        but in ICS mode that is not its job anyway.
4463     */
4464     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4465     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4466
4467         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4468             if(board[0][i] == WhiteRook) j = i;
4469         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4470         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4471             if(board[0][i] == WhiteRook) j = i;
4472         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4473         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4474             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4475         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4476         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4477             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4478         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4479
4480         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4481         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4482         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4483             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4484         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4485             if(board[BOARD_HEIGHT-1][k] == bKing)
4486                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4487         if(gameInfo.variant == VariantTwoKings) {
4488             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4489             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4490             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4491         }
4492     } else { int r;
4493         r = boards[moveNum][CASTLING][0] = initialRights[0];
4494         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4495         r = boards[moveNum][CASTLING][1] = initialRights[1];
4496         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4497         r = boards[moveNum][CASTLING][3] = initialRights[3];
4498         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4499         r = boards[moveNum][CASTLING][4] = initialRights[4];
4500         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4501         /* wildcastle kludge: always assume King has rights */
4502         r = boards[moveNum][CASTLING][2] = initialRights[2];
4503         r = boards[moveNum][CASTLING][5] = initialRights[5];
4504     }
4505     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4506     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4507
4508
4509     if (ics_getting_history == H_GOT_REQ_HEADER ||
4510         ics_getting_history == H_GOT_UNREQ_HEADER) {
4511         /* This was an initial position from a move list, not
4512            the current position */
4513         return;
4514     }
4515
4516     /* Update currentMove and known move number limits */
4517     newMove = newGame || moveNum > forwardMostMove;
4518
4519     if (newGame) {
4520         forwardMostMove = backwardMostMove = currentMove = moveNum;
4521         if (gameMode == IcsExamining && moveNum == 0) {
4522           /* Workaround for ICS limitation: we are not told the wild
4523              type when starting to examine a game.  But if we ask for
4524              the move list, the move list header will tell us */
4525             ics_getting_history = H_REQUESTED;
4526             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4527             SendToICS(str);
4528         }
4529     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4530                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4531 #if ZIPPY
4532         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4533         /* [HGM] applied this also to an engine that is silently watching        */
4534         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4535             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4536             gameInfo.variant == currentlyInitializedVariant) {
4537           takeback = forwardMostMove - moveNum;
4538           for (i = 0; i < takeback; i++) {
4539             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4540             SendToProgram("undo\n", &first);
4541           }
4542         }
4543 #endif
4544
4545         forwardMostMove = moveNum;
4546         if (!pausing || currentMove > forwardMostMove)
4547           currentMove = forwardMostMove;
4548     } else {
4549         /* New part of history that is not contiguous with old part */
4550         if (pausing && gameMode == IcsExamining) {
4551             pauseExamInvalid = TRUE;
4552             forwardMostMove = pauseExamForwardMostMove;
4553             return;
4554         }
4555         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4556 #if ZIPPY
4557             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4558                 // [HGM] when we will receive the move list we now request, it will be
4559                 // fed to the engine from the first move on. So if the engine is not
4560                 // in the initial position now, bring it there.
4561                 InitChessProgram(&first, 0);
4562             }
4563 #endif
4564             ics_getting_history = H_REQUESTED;
4565             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4566             SendToICS(str);
4567         }
4568         forwardMostMove = backwardMostMove = currentMove = moveNum;
4569     }
4570
4571     /* Update the clocks */
4572     if (strchr(elapsed_time, '.')) {
4573       /* Time is in ms */
4574       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4575       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4576     } else {
4577       /* Time is in seconds */
4578       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4579       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4580     }
4581
4582
4583 #if ZIPPY
4584     if (appData.zippyPlay && newGame &&
4585         gameMode != IcsObserving && gameMode != IcsIdle &&
4586         gameMode != IcsExamining)
4587       ZippyFirstBoard(moveNum, basetime, increment);
4588 #endif
4589
4590     /* Put the move on the move list, first converting
4591        to canonical algebraic form. */
4592     if (moveNum > 0) {
4593   if (appData.debugMode) {
4594     if (appData.debugMode) { int f = forwardMostMove;
4595         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4596                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4597                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4598     }
4599     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4600     fprintf(debugFP, "moveNum = %d\n", moveNum);
4601     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4602     setbuf(debugFP, NULL);
4603   }
4604         if (moveNum <= backwardMostMove) {
4605             /* We don't know what the board looked like before
4606                this move.  Punt. */
4607           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4608             strcat(parseList[moveNum - 1], " ");
4609             strcat(parseList[moveNum - 1], elapsed_time);
4610             moveList[moveNum - 1][0] = NULLCHAR;
4611         } else if (strcmp(move_str, "none") == 0) {
4612             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4613             /* Again, we don't know what the board looked like;
4614                this is really the start of the game. */
4615             parseList[moveNum - 1][0] = NULLCHAR;
4616             moveList[moveNum - 1][0] = NULLCHAR;
4617             backwardMostMove = moveNum;
4618             startedFromSetupPosition = TRUE;
4619             fromX = fromY = toX = toY = -1;
4620         } else {
4621           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4622           //                 So we parse the long-algebraic move string in stead of the SAN move
4623           int valid; char buf[MSG_SIZ], *prom;
4624
4625           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4626                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4627           // str looks something like "Q/a1-a2"; kill the slash
4628           if(str[1] == '/')
4629             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4630           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4631           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4632                 strcat(buf, prom); // long move lacks promo specification!
4633           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4634                 if(appData.debugMode)
4635                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4636                 safeStrCpy(move_str, buf, MSG_SIZ);
4637           }
4638           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4639                                 &fromX, &fromY, &toX, &toY, &promoChar)
4640                || ParseOneMove(buf, moveNum - 1, &moveType,
4641                                 &fromX, &fromY, &toX, &toY, &promoChar);
4642           // end of long SAN patch
4643           if (valid) {
4644             (void) CoordsToAlgebraic(boards[moveNum - 1],
4645                                      PosFlags(moveNum - 1),
4646                                      fromY, fromX, toY, toX, promoChar,
4647                                      parseList[moveNum-1]);
4648             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4649               case MT_NONE:
4650               case MT_STALEMATE:
4651               default:
4652                 break;
4653               case MT_CHECK:
4654                 if(gameInfo.variant != VariantShogi)
4655                     strcat(parseList[moveNum - 1], "+");
4656                 break;
4657               case MT_CHECKMATE:
4658               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4659                 strcat(parseList[moveNum - 1], "#");
4660                 break;
4661             }
4662             strcat(parseList[moveNum - 1], " ");
4663             strcat(parseList[moveNum - 1], elapsed_time);
4664             /* currentMoveString is set as a side-effect of ParseOneMove */
4665             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4666             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4667             strcat(moveList[moveNum - 1], "\n");
4668
4669             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4670                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4671               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4672                 ChessSquare old, new = boards[moveNum][k][j];
4673                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4674                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4675                   if(old == new) continue;
4676                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4677                   else if(new == WhiteWazir || new == BlackWazir) {
4678                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4679                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4680                       else boards[moveNum][k][j] = old; // preserve type of Gold
4681                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4682                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4683               }
4684           } else {
4685             /* Move from ICS was illegal!?  Punt. */
4686             if (appData.debugMode) {
4687               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4688               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4689             }
4690             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4691             strcat(parseList[moveNum - 1], " ");
4692             strcat(parseList[moveNum - 1], elapsed_time);
4693             moveList[moveNum - 1][0] = NULLCHAR;
4694             fromX = fromY = toX = toY = -1;
4695           }
4696         }
4697   if (appData.debugMode) {
4698     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4699     setbuf(debugFP, NULL);
4700   }
4701
4702 #if ZIPPY
4703         /* Send move to chess program (BEFORE animating it). */
4704         if (appData.zippyPlay && !newGame && newMove &&
4705            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4706
4707             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4708                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4709                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4710                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4711                             move_str);
4712                     DisplayError(str, 0);
4713                 } else {
4714                     if (first.sendTime) {
4715                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4716                     }
4717                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4718                     if (firstMove && !bookHit) {
4719                         firstMove = FALSE;
4720                         if (first.useColors) {
4721                           SendToProgram(gameMode == IcsPlayingWhite ?
4722                                         "white\ngo\n" :
4723                                         "black\ngo\n", &first);
4724                         } else {
4725                           SendToProgram("go\n", &first);
4726                         }
4727                         first.maybeThinking = TRUE;
4728                     }
4729                 }
4730             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4731               if (moveList[moveNum - 1][0] == NULLCHAR) {
4732                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4733                 DisplayError(str, 0);
4734               } else {
4735                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4736                 SendMoveToProgram(moveNum - 1, &first);
4737               }
4738             }
4739         }
4740 #endif
4741     }
4742
4743     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4744         /* If move comes from a remote source, animate it.  If it
4745            isn't remote, it will have already been animated. */
4746         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4747             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4748         }
4749         if (!pausing && appData.highlightLastMove) {
4750             SetHighlights(fromX, fromY, toX, toY);
4751         }
4752     }
4753
4754     /* Start the clocks */
4755     whiteFlag = blackFlag = FALSE;
4756     appData.clockMode = !(basetime == 0 && increment == 0);
4757     if (ticking == 0) {
4758       ics_clock_paused = TRUE;
4759       StopClocks();
4760     } else if (ticking == 1) {
4761       ics_clock_paused = FALSE;
4762     }
4763     if (gameMode == IcsIdle ||
4764         relation == RELATION_OBSERVING_STATIC ||
4765         relation == RELATION_EXAMINING ||
4766         ics_clock_paused)
4767       DisplayBothClocks();
4768     else
4769       StartClocks();
4770
4771     /* Display opponents and material strengths */
4772     if (gameInfo.variant != VariantBughouse &&
4773         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4774         if (tinyLayout || smallLayout) {
4775             if(gameInfo.variant == VariantNormal)
4776               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4777                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4778                     basetime, increment);
4779             else
4780               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4781                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4782                     basetime, increment, (int) gameInfo.variant);
4783         } else {
4784             if(gameInfo.variant == VariantNormal)
4785               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4786                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4787                     basetime, increment);
4788             else
4789               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4790                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4791                     basetime, increment, VariantName(gameInfo.variant));
4792         }
4793         DisplayTitle(str);
4794   if (appData.debugMode) {
4795     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4796   }
4797     }
4798
4799
4800     /* Display the board */
4801     if (!pausing && !appData.noGUI) {
4802
4803       if (appData.premove)
4804           if (!gotPremove ||
4805              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4806              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4807               ClearPremoveHighlights();
4808
4809       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4810         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4811       DrawPosition(j, boards[currentMove]);
4812
4813       DisplayMove(moveNum - 1);
4814       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4815             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4816               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4817         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4818       }
4819     }
4820
4821     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4822 #if ZIPPY
4823     if(bookHit) { // [HGM] book: simulate book reply
4824         static char bookMove[MSG_SIZ]; // a bit generous?
4825
4826         programStats.nodes = programStats.depth = programStats.time =
4827         programStats.score = programStats.got_only_move = 0;
4828         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4829
4830         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4831         strcat(bookMove, bookHit);
4832         HandleMachineMove(bookMove, &first);
4833     }
4834 #endif
4835 }
4836
4837 void
4838 GetMoveListEvent ()
4839 {
4840     char buf[MSG_SIZ];
4841     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4842         ics_getting_history = H_REQUESTED;
4843         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4844         SendToICS(buf);
4845     }
4846 }
4847
4848 void
4849 AnalysisPeriodicEvent (int force)
4850 {
4851     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4852          && !force) || !appData.periodicUpdates)
4853       return;
4854
4855     /* Send . command to Crafty to collect stats */
4856     SendToProgram(".\n", &first);
4857
4858     /* Don't send another until we get a response (this makes
4859        us stop sending to old Crafty's which don't understand
4860        the "." command (sending illegal cmds resets node count & time,
4861        which looks bad)) */
4862     programStats.ok_to_send = 0;
4863 }
4864
4865 void
4866 ics_update_width (int new_width)
4867 {
4868         ics_printf("set width %d\n", new_width);
4869 }
4870
4871 void
4872 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4873 {
4874     char buf[MSG_SIZ];
4875
4876     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4877         // null move in variant where engine does not understand it (for analysis purposes)
4878         SendBoard(cps, moveNum + 1); // send position after move in stead.
4879         return;
4880     }
4881     if (cps->useUsermove) {
4882       SendToProgram("usermove ", cps);
4883     }
4884     if (cps->useSAN) {
4885       char *space;
4886       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4887         int len = space - parseList[moveNum];
4888         memcpy(buf, parseList[moveNum], len);
4889         buf[len++] = '\n';
4890         buf[len] = NULLCHAR;
4891       } else {
4892         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4893       }
4894       SendToProgram(buf, cps);
4895     } else {
4896       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4897         AlphaRank(moveList[moveNum], 4);
4898         SendToProgram(moveList[moveNum], cps);
4899         AlphaRank(moveList[moveNum], 4); // and back
4900       } else
4901       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4902        * the engine. It would be nice to have a better way to identify castle
4903        * moves here. */
4904       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4905                                                                          && cps->useOOCastle) {
4906         int fromX = moveList[moveNum][0] - AAA;
4907         int fromY = moveList[moveNum][1] - ONE;
4908         int toX = moveList[moveNum][2] - AAA;
4909         int toY = moveList[moveNum][3] - ONE;
4910         if((boards[moveNum][fromY][fromX] == WhiteKing
4911             && boards[moveNum][toY][toX] == WhiteRook)
4912            || (boards[moveNum][fromY][fromX] == BlackKing
4913                && boards[moveNum][toY][toX] == BlackRook)) {
4914           if(toX > fromX) SendToProgram("O-O\n", cps);
4915           else SendToProgram("O-O-O\n", cps);
4916         }
4917         else SendToProgram(moveList[moveNum], cps);
4918       } else
4919       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4920         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4921           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4922           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4923                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4924         } else
4925           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4926                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4927         SendToProgram(buf, cps);
4928       }
4929       else SendToProgram(moveList[moveNum], cps);
4930       /* End of additions by Tord */
4931     }
4932
4933     /* [HGM] setting up the opening has brought engine in force mode! */
4934     /*       Send 'go' if we are in a mode where machine should play. */
4935     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4936         (gameMode == TwoMachinesPlay   ||
4937 #if ZIPPY
4938          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4939 #endif
4940          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4941         SendToProgram("go\n", cps);
4942   if (appData.debugMode) {
4943     fprintf(debugFP, "(extra)\n");
4944   }
4945     }
4946     setboardSpoiledMachineBlack = 0;
4947 }
4948
4949 void
4950 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4951 {
4952     char user_move[MSG_SIZ];
4953     char suffix[4];
4954
4955     if(gameInfo.variant == VariantSChess && promoChar) {
4956         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4957         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4958     } else suffix[0] = NULLCHAR;
4959
4960     switch (moveType) {
4961       default:
4962         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4963                 (int)moveType, fromX, fromY, toX, toY);
4964         DisplayError(user_move + strlen("say "), 0);
4965         break;
4966       case WhiteKingSideCastle:
4967       case BlackKingSideCastle:
4968       case WhiteQueenSideCastleWild:
4969       case BlackQueenSideCastleWild:
4970       /* PUSH Fabien */
4971       case WhiteHSideCastleFR:
4972       case BlackHSideCastleFR:
4973       /* POP Fabien */
4974         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4975         break;
4976       case WhiteQueenSideCastle:
4977       case BlackQueenSideCastle:
4978       case WhiteKingSideCastleWild:
4979       case BlackKingSideCastleWild:
4980       /* PUSH Fabien */
4981       case WhiteASideCastleFR:
4982       case BlackASideCastleFR:
4983       /* POP Fabien */
4984         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4985         break;
4986       case WhiteNonPromotion:
4987       case BlackNonPromotion:
4988         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4989         break;
4990       case WhitePromotion:
4991       case BlackPromotion:
4992         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4993           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4994                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4995                 PieceToChar(WhiteFerz));
4996         else if(gameInfo.variant == VariantGreat)
4997           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4998                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4999                 PieceToChar(WhiteMan));
5000         else
5001           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5002                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5003                 promoChar);
5004         break;
5005       case WhiteDrop:
5006       case BlackDrop:
5007       drop:
5008         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5009                  ToUpper(PieceToChar((ChessSquare) fromX)),
5010                  AAA + toX, ONE + toY);
5011         break;
5012       case IllegalMove:  /* could be a variant we don't quite understand */
5013         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5014       case NormalMove:
5015       case WhiteCapturesEnPassant:
5016       case BlackCapturesEnPassant:
5017         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5018                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5019         break;
5020     }
5021     SendToICS(user_move);
5022     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5023         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5024 }
5025
5026 void
5027 UploadGameEvent ()
5028 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5029     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5030     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5031     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5032       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5033       return;
5034     }
5035     if(gameMode != IcsExamining) { // is this ever not the case?
5036         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5037
5038         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5039           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5040         } else { // on FICS we must first go to general examine mode
5041           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5042         }
5043         if(gameInfo.variant != VariantNormal) {
5044             // try figure out wild number, as xboard names are not always valid on ICS
5045             for(i=1; i<=36; i++) {
5046               snprintf(buf, MSG_SIZ, "wild/%d", i);
5047                 if(StringToVariant(buf) == gameInfo.variant) break;
5048             }
5049             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5050             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5051             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5052         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5053         SendToICS(ics_prefix);
5054         SendToICS(buf);
5055         if(startedFromSetupPosition || backwardMostMove != 0) {
5056           fen = PositionToFEN(backwardMostMove, NULL);
5057           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5058             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5059             SendToICS(buf);
5060           } else { // FICS: everything has to set by separate bsetup commands
5061             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5062             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5063             SendToICS(buf);
5064             if(!WhiteOnMove(backwardMostMove)) {
5065                 SendToICS("bsetup tomove black\n");
5066             }
5067             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5068             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5069             SendToICS(buf);
5070             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5071             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5072             SendToICS(buf);
5073             i = boards[backwardMostMove][EP_STATUS];
5074             if(i >= 0) { // set e.p.
5075               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5076                 SendToICS(buf);
5077             }
5078             bsetup++;
5079           }
5080         }
5081       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5082             SendToICS("bsetup done\n"); // switch to normal examining.
5083     }
5084     for(i = backwardMostMove; i<last; i++) {
5085         char buf[20];
5086         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5087         SendToICS(buf);
5088     }
5089     SendToICS(ics_prefix);
5090     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5091 }
5092
5093 void
5094 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5095 {
5096     if (rf == DROP_RANK) {
5097       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5098       sprintf(move, "%c@%c%c\n",
5099                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5100     } else {
5101         if (promoChar == 'x' || promoChar == NULLCHAR) {
5102           sprintf(move, "%c%c%c%c\n",
5103                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5104         } else {
5105             sprintf(move, "%c%c%c%c%c\n",
5106                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5107         }
5108     }
5109 }
5110
5111 void
5112 ProcessICSInitScript (FILE *f)
5113 {
5114     char buf[MSG_SIZ];
5115
5116     while (fgets(buf, MSG_SIZ, f)) {
5117         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5118     }
5119
5120     fclose(f);
5121 }
5122
5123
5124 static int lastX, lastY, selectFlag, dragging;
5125
5126 void
5127 Sweep (int step)
5128 {
5129     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5130     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5131     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5132     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5133     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5134     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5135     do {
5136         promoSweep -= step;
5137         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5138         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5139         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5140         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5141         if(!step) step = -1;
5142     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5143             appData.testLegality && (promoSweep == king ||
5144             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5145     ChangeDragPiece(promoSweep);
5146 }
5147
5148 int
5149 PromoScroll (int x, int y)
5150 {
5151   int step = 0;
5152
5153   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5154   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5155   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5156   if(!step) return FALSE;
5157   lastX = x; lastY = y;
5158   if((promoSweep < BlackPawn) == flipView) step = -step;
5159   if(step > 0) selectFlag = 1;
5160   if(!selectFlag) Sweep(step);
5161   return FALSE;
5162 }
5163
5164 void
5165 NextPiece (int step)
5166 {
5167     ChessSquare piece = boards[currentMove][toY][toX];
5168     do {
5169         pieceSweep -= step;
5170         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5171         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5172         if(!step) step = -1;
5173     } while(PieceToChar(pieceSweep) == '.');
5174     boards[currentMove][toY][toX] = pieceSweep;
5175     DrawPosition(FALSE, boards[currentMove]);
5176     boards[currentMove][toY][toX] = piece;
5177 }
5178 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5179 void
5180 AlphaRank (char *move, int n)
5181 {
5182 //    char *p = move, c; int x, y;
5183
5184     if (appData.debugMode) {
5185         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5186     }
5187
5188     if(move[1]=='*' &&
5189        move[2]>='0' && move[2]<='9' &&
5190        move[3]>='a' && move[3]<='x'    ) {
5191         move[1] = '@';
5192         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5193         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5194     } else
5195     if(move[0]>='0' && move[0]<='9' &&
5196        move[1]>='a' && move[1]<='x' &&
5197        move[2]>='0' && move[2]<='9' &&
5198        move[3]>='a' && move[3]<='x'    ) {
5199         /* input move, Shogi -> normal */
5200         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5201         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5202         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5203         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5204     } else
5205     if(move[1]=='@' &&
5206        move[3]>='0' && move[3]<='9' &&
5207        move[2]>='a' && move[2]<='x'    ) {
5208         move[1] = '*';
5209         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5210         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5211     } else
5212     if(
5213        move[0]>='a' && move[0]<='x' &&
5214        move[3]>='0' && move[3]<='9' &&
5215        move[2]>='a' && move[2]<='x'    ) {
5216          /* output move, normal -> Shogi */
5217         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5218         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5219         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5220         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5221         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5222     }
5223     if (appData.debugMode) {
5224         fprintf(debugFP, "   out = '%s'\n", move);
5225     }
5226 }
5227
5228 char yy_textstr[8000];
5229
5230 /* Parser for moves from gnuchess, ICS, or user typein box */
5231 Boolean
5232 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5233 {
5234     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5235
5236     switch (*moveType) {
5237       case WhitePromotion:
5238       case BlackPromotion:
5239       case WhiteNonPromotion:
5240       case BlackNonPromotion:
5241       case NormalMove:
5242       case WhiteCapturesEnPassant:
5243       case BlackCapturesEnPassant:
5244       case WhiteKingSideCastle:
5245       case WhiteQueenSideCastle:
5246       case BlackKingSideCastle:
5247       case BlackQueenSideCastle:
5248       case WhiteKingSideCastleWild:
5249       case WhiteQueenSideCastleWild:
5250       case BlackKingSideCastleWild:
5251       case BlackQueenSideCastleWild:
5252       /* Code added by Tord: */
5253       case WhiteHSideCastleFR:
5254       case WhiteASideCastleFR:
5255       case BlackHSideCastleFR:
5256       case BlackASideCastleFR:
5257       /* End of code added by Tord */
5258       case IllegalMove:         /* bug or odd chess variant */
5259         *fromX = currentMoveString[0] - AAA;
5260         *fromY = currentMoveString[1] - ONE;
5261         *toX = currentMoveString[2] - AAA;
5262         *toY = currentMoveString[3] - ONE;
5263         *promoChar = currentMoveString[4];
5264         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5265             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5266     if (appData.debugMode) {
5267         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5268     }
5269             *fromX = *fromY = *toX = *toY = 0;
5270             return FALSE;
5271         }
5272         if (appData.testLegality) {
5273           return (*moveType != IllegalMove);
5274         } else {
5275           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5276                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5277         }
5278
5279       case WhiteDrop:
5280       case BlackDrop:
5281         *fromX = *moveType == WhiteDrop ?
5282           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5283           (int) CharToPiece(ToLower(currentMoveString[0]));
5284         *fromY = DROP_RANK;
5285         *toX = currentMoveString[2] - AAA;
5286         *toY = currentMoveString[3] - ONE;
5287         *promoChar = NULLCHAR;
5288         return TRUE;
5289
5290       case AmbiguousMove:
5291       case ImpossibleMove:
5292       case EndOfFile:
5293       case ElapsedTime:
5294       case Comment:
5295       case PGNTag:
5296       case NAG:
5297       case WhiteWins:
5298       case BlackWins:
5299       case GameIsDrawn:
5300       default:
5301     if (appData.debugMode) {
5302         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5303     }
5304         /* bug? */
5305         *fromX = *fromY = *toX = *toY = 0;
5306         *promoChar = NULLCHAR;
5307         return FALSE;
5308     }
5309 }
5310
5311 Boolean pushed = FALSE;
5312 char *lastParseAttempt;
5313
5314 void
5315 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5316 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5317   int fromX, fromY, toX, toY; char promoChar;
5318   ChessMove moveType;
5319   Boolean valid;
5320   int nr = 0;
5321
5322   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5323     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5324     pushed = TRUE;
5325   }
5326   endPV = forwardMostMove;
5327   do {
5328     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5329     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5330     lastParseAttempt = pv;
5331     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5332     if(!valid && nr == 0 &&
5333        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5334         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5335         // Hande case where played move is different from leading PV move
5336         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5337         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5338         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5339         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5340           endPV += 2; // if position different, keep this
5341           moveList[endPV-1][0] = fromX + AAA;
5342           moveList[endPV-1][1] = fromY + ONE;
5343           moveList[endPV-1][2] = toX + AAA;
5344           moveList[endPV-1][3] = toY + ONE;
5345           parseList[endPV-1][0] = NULLCHAR;
5346           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5347         }
5348       }
5349     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5350     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5351     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5352     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5353         valid++; // allow comments in PV
5354         continue;
5355     }
5356     nr++;
5357     if(endPV+1 > framePtr) break; // no space, truncate
5358     if(!valid) break;
5359     endPV++;
5360     CopyBoard(boards[endPV], boards[endPV-1]);
5361     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5362     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5363     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5364     CoordsToAlgebraic(boards[endPV - 1],
5365                              PosFlags(endPV - 1),
5366                              fromY, fromX, toY, toX, promoChar,
5367                              parseList[endPV - 1]);
5368   } while(valid);
5369   if(atEnd == 2) return; // used hidden, for PV conversion
5370   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5371   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5372   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5373                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5374   DrawPosition(TRUE, boards[currentMove]);
5375 }
5376
5377 int
5378 MultiPV (ChessProgramState *cps)
5379 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5380         int i;
5381         for(i=0; i<cps->nrOptions; i++)
5382             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5383                 return i;
5384         return -1;
5385 }
5386
5387 Boolean
5388 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5389 {
5390         int startPV, multi, lineStart, origIndex = index;
5391         char *p, buf2[MSG_SIZ];
5392
5393         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5394         lastX = x; lastY = y;
5395         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5396         lineStart = startPV = index;
5397         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5398         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5399         index = startPV;
5400         do{ while(buf[index] && buf[index] != '\n') index++;
5401         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5402         buf[index] = 0;
5403         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5404                 int n = first.option[multi].value;
5405                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5406                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5407                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5408                 first.option[multi].value = n;
5409                 *start = *end = 0;
5410                 return FALSE;
5411         }
5412         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5413         *start = startPV; *end = index-1;
5414         return TRUE;
5415 }
5416
5417 char *
5418 PvToSAN (char *pv)
5419 {
5420         static char buf[10*MSG_SIZ];
5421         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5422         *buf = NULLCHAR;
5423         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5424         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5425         for(i = forwardMostMove; i<endPV; i++){
5426             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5427             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5428             k += strlen(buf+k);
5429         }
5430         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5431         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5432         endPV = savedEnd;
5433         return buf;
5434 }
5435
5436 Boolean
5437 LoadPV (int x, int y)
5438 { // called on right mouse click to load PV
5439   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5440   lastX = x; lastY = y;
5441   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5442   return TRUE;
5443 }
5444
5445 void
5446 UnLoadPV ()
5447 {
5448   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5449   if(endPV < 0) return;
5450   endPV = -1;
5451   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5452         Boolean saveAnimate = appData.animate;
5453         if(pushed) {
5454             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5455                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5456             } else storedGames--; // abandon shelved tail of original game
5457         }
5458         pushed = FALSE;
5459         forwardMostMove = currentMove;
5460         currentMove = oldFMM;
5461         appData.animate = FALSE;
5462         ToNrEvent(forwardMostMove);
5463         appData.animate = saveAnimate;
5464   }
5465   currentMove = forwardMostMove;
5466   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5467   ClearPremoveHighlights();
5468   DrawPosition(TRUE, boards[currentMove]);
5469 }
5470
5471 void
5472 MovePV (int x, int y, int h)
5473 { // step through PV based on mouse coordinates (called on mouse move)
5474   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5475
5476   // we must somehow check if right button is still down (might be released off board!)
5477   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5478   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5479   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5480   if(!step) return;
5481   lastX = x; lastY = y;
5482
5483   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5484   if(endPV < 0) return;
5485   if(y < margin) step = 1; else
5486   if(y > h - margin) step = -1;
5487   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5488   currentMove += step;
5489   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5490   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5491                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5492   DrawPosition(FALSE, boards[currentMove]);
5493 }
5494
5495
5496 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5497 // All positions will have equal probability, but the current method will not provide a unique
5498 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5499 #define DARK 1
5500 #define LITE 2
5501 #define ANY 3
5502
5503 int squaresLeft[4];
5504 int piecesLeft[(int)BlackPawn];
5505 int seed, nrOfShuffles;
5506
5507 void
5508 GetPositionNumber ()
5509 {       // sets global variable seed
5510         int i;
5511
5512         seed = appData.defaultFrcPosition;
5513         if(seed < 0) { // randomize based on time for negative FRC position numbers
5514                 for(i=0; i<50; i++) seed += random();
5515                 seed = random() ^ random() >> 8 ^ random() << 8;
5516                 if(seed<0) seed = -seed;
5517         }
5518 }
5519
5520 int
5521 put (Board board, int pieceType, int rank, int n, int shade)
5522 // put the piece on the (n-1)-th empty squares of the given shade
5523 {
5524         int i;
5525
5526         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5527                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5528                         board[rank][i] = (ChessSquare) pieceType;
5529                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5530                         squaresLeft[ANY]--;
5531                         piecesLeft[pieceType]--;
5532                         return i;
5533                 }
5534         }
5535         return -1;
5536 }
5537
5538
5539 void
5540 AddOnePiece (Board board, int pieceType, int rank, int shade)
5541 // calculate where the next piece goes, (any empty square), and put it there
5542 {
5543         int i;
5544
5545         i = seed % squaresLeft[shade];
5546         nrOfShuffles *= squaresLeft[shade];
5547         seed /= squaresLeft[shade];
5548         put(board, pieceType, rank, i, shade);
5549 }
5550
5551 void
5552 AddTwoPieces (Board board, int pieceType, int rank)
5553 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5554 {
5555         int i, n=squaresLeft[ANY], j=n-1, k;
5556
5557         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5558         i = seed % k;  // pick one
5559         nrOfShuffles *= k;
5560         seed /= k;
5561         while(i >= j) i -= j--;
5562         j = n - 1 - j; i += j;
5563         put(board, pieceType, rank, j, ANY);
5564         put(board, pieceType, rank, i, ANY);
5565 }
5566
5567 void
5568 SetUpShuffle (Board board, int number)
5569 {
5570         int i, p, first=1;
5571
5572         GetPositionNumber(); nrOfShuffles = 1;
5573
5574         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5575         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5576         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5577
5578         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5579
5580         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5581             p = (int) board[0][i];
5582             if(p < (int) BlackPawn) piecesLeft[p] ++;
5583             board[0][i] = EmptySquare;
5584         }
5585
5586         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5587             // shuffles restricted to allow normal castling put KRR first
5588             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5589                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5590             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5591                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5592             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5593                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5594             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5595                 put(board, WhiteRook, 0, 0, ANY);
5596             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5597         }
5598
5599         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5600             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5601             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5602                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5603                 while(piecesLeft[p] >= 2) {
5604                     AddOnePiece(board, p, 0, LITE);
5605                     AddOnePiece(board, p, 0, DARK);
5606                 }
5607                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5608             }
5609
5610         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5611             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5612             // but we leave King and Rooks for last, to possibly obey FRC restriction
5613             if(p == (int)WhiteRook) continue;
5614             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5615             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5616         }
5617
5618         // now everything is placed, except perhaps King (Unicorn) and Rooks
5619
5620         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5621             // Last King gets castling rights
5622             while(piecesLeft[(int)WhiteUnicorn]) {
5623                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5624                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5625             }
5626
5627             while(piecesLeft[(int)WhiteKing]) {
5628                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5629                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5630             }
5631
5632
5633         } else {
5634             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5635             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5636         }
5637
5638         // Only Rooks can be left; simply place them all
5639         while(piecesLeft[(int)WhiteRook]) {
5640                 i = put(board, WhiteRook, 0, 0, ANY);
5641                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5642                         if(first) {
5643                                 first=0;
5644                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5645                         }
5646                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5647                 }
5648         }
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5650             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5651         }
5652
5653         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5654 }
5655
5656 int
5657 SetCharTable (char *table, const char * map)
5658 /* [HGM] moved here from winboard.c because of its general usefulness */
5659 /*       Basically a safe strcpy that uses the last character as King */
5660 {
5661     int result = FALSE; int NrPieces;
5662
5663     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5664                     && NrPieces >= 12 && !(NrPieces&1)) {
5665         int i; /* [HGM] Accept even length from 12 to 34 */
5666
5667         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5668         for( i=0; i<NrPieces/2-1; i++ ) {
5669             table[i] = map[i];
5670             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5671         }
5672         table[(int) WhiteKing]  = map[NrPieces/2-1];
5673         table[(int) BlackKing]  = map[NrPieces-1];
5674
5675         result = TRUE;
5676     }
5677
5678     return result;
5679 }
5680
5681 void
5682 Prelude (Board board)
5683 {       // [HGM] superchess: random selection of exo-pieces
5684         int i, j, k; ChessSquare p;
5685         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5686
5687         GetPositionNumber(); // use FRC position number
5688
5689         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5690             SetCharTable(pieceToChar, appData.pieceToCharTable);
5691             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5692                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5693         }
5694
5695         j = seed%4;                 seed /= 4;
5696         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5697         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5698         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5699         j = seed%3 + (seed%3 >= j); seed /= 3;
5700         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5701         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5702         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5703         j = seed%3;                 seed /= 3;
5704         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5705         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5706         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5707         j = seed%2 + (seed%2 >= j); seed /= 2;
5708         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5709         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5710         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5711         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5712         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5713         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5714         put(board, exoPieces[0],    0, 0, ANY);
5715         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5716 }
5717
5718 void
5719 InitPosition (int redraw)
5720 {
5721     ChessSquare (* pieces)[BOARD_FILES];
5722     int i, j, pawnRow, overrule,
5723     oldx = gameInfo.boardWidth,
5724     oldy = gameInfo.boardHeight,
5725     oldh = gameInfo.holdingsWidth;
5726     static int oldv;
5727
5728     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5729
5730     /* [AS] Initialize pv info list [HGM] and game status */
5731     {
5732         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5733             pvInfoList[i].depth = 0;
5734             boards[i][EP_STATUS] = EP_NONE;
5735             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5736         }
5737
5738         initialRulePlies = 0; /* 50-move counter start */
5739
5740         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5741         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5742     }
5743
5744
5745     /* [HGM] logic here is completely changed. In stead of full positions */
5746     /* the initialized data only consist of the two backranks. The switch */
5747     /* selects which one we will use, which is than copied to the Board   */
5748     /* initialPosition, which for the rest is initialized by Pawns and    */
5749     /* empty squares. This initial position is then copied to boards[0],  */
5750     /* possibly after shuffling, so that it remains available.            */
5751
5752     gameInfo.holdingsWidth = 0; /* default board sizes */
5753     gameInfo.boardWidth    = 8;
5754     gameInfo.boardHeight   = 8;
5755     gameInfo.holdingsSize  = 0;
5756     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5757     for(i=0; i<BOARD_FILES-2; i++)
5758       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5759     initialPosition[EP_STATUS] = EP_NONE;
5760     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5761     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5762          SetCharTable(pieceNickName, appData.pieceNickNames);
5763     else SetCharTable(pieceNickName, "............");
5764     pieces = FIDEArray;
5765
5766     switch (gameInfo.variant) {
5767     case VariantFischeRandom:
5768       shuffleOpenings = TRUE;
5769     default:
5770       break;
5771     case VariantShatranj:
5772       pieces = ShatranjArray;
5773       nrCastlingRights = 0;
5774       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5775       break;
5776     case VariantMakruk:
5777       pieces = makrukArray;
5778       nrCastlingRights = 0;
5779       startedFromSetupPosition = TRUE;
5780       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5781       break;
5782     case VariantTwoKings:
5783       pieces = twoKingsArray;
5784       break;
5785     case VariantGrand:
5786       pieces = GrandArray;
5787       nrCastlingRights = 0;
5788       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789       gameInfo.boardWidth = 10;
5790       gameInfo.boardHeight = 10;
5791       gameInfo.holdingsSize = 7;
5792       break;
5793     case VariantCapaRandom:
5794       shuffleOpenings = TRUE;
5795     case VariantCapablanca:
5796       pieces = CapablancaArray;
5797       gameInfo.boardWidth = 10;
5798       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5799       break;
5800     case VariantGothic:
5801       pieces = GothicArray;
5802       gameInfo.boardWidth = 10;
5803       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5804       break;
5805     case VariantSChess:
5806       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5807       gameInfo.holdingsSize = 7;
5808       break;
5809     case VariantJanus:
5810       pieces = JanusArray;
5811       gameInfo.boardWidth = 10;
5812       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5813       nrCastlingRights = 6;
5814         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5815         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5816         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5817         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5818         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5819         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5820       break;
5821     case VariantFalcon:
5822       pieces = FalconArray;
5823       gameInfo.boardWidth = 10;
5824       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5825       break;
5826     case VariantXiangqi:
5827       pieces = XiangqiArray;
5828       gameInfo.boardWidth  = 9;
5829       gameInfo.boardHeight = 10;
5830       nrCastlingRights = 0;
5831       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5832       break;
5833     case VariantShogi:
5834       pieces = ShogiArray;
5835       gameInfo.boardWidth  = 9;
5836       gameInfo.boardHeight = 9;
5837       gameInfo.holdingsSize = 7;
5838       nrCastlingRights = 0;
5839       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5840       break;
5841     case VariantCourier:
5842       pieces = CourierArray;
5843       gameInfo.boardWidth  = 12;
5844       nrCastlingRights = 0;
5845       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5846       break;
5847     case VariantKnightmate:
5848       pieces = KnightmateArray;
5849       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5850       break;
5851     case VariantSpartan:
5852       pieces = SpartanArray;
5853       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5854       break;
5855     case VariantFairy:
5856       pieces = fairyArray;
5857       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5858       break;
5859     case VariantGreat:
5860       pieces = GreatArray;
5861       gameInfo.boardWidth = 10;
5862       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5863       gameInfo.holdingsSize = 8;
5864       break;
5865     case VariantSuper:
5866       pieces = FIDEArray;
5867       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5868       gameInfo.holdingsSize = 8;
5869       startedFromSetupPosition = TRUE;
5870       break;
5871     case VariantCrazyhouse:
5872     case VariantBughouse:
5873       pieces = FIDEArray;
5874       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5875       gameInfo.holdingsSize = 5;
5876       break;
5877     case VariantWildCastle:
5878       pieces = FIDEArray;
5879       /* !!?shuffle with kings guaranteed to be on d or e file */
5880       shuffleOpenings = 1;
5881       break;
5882     case VariantNoCastle:
5883       pieces = FIDEArray;
5884       nrCastlingRights = 0;
5885       /* !!?unconstrained back-rank shuffle */
5886       shuffleOpenings = 1;
5887       break;
5888     }
5889
5890     overrule = 0;
5891     if(appData.NrFiles >= 0) {
5892         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5893         gameInfo.boardWidth = appData.NrFiles;
5894     }
5895     if(appData.NrRanks >= 0) {
5896         gameInfo.boardHeight = appData.NrRanks;
5897     }
5898     if(appData.holdingsSize >= 0) {
5899         i = appData.holdingsSize;
5900         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5901         gameInfo.holdingsSize = i;
5902     }
5903     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5904     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5905         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5906
5907     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5908     if(pawnRow < 1) pawnRow = 1;
5909     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5910
5911     /* User pieceToChar list overrules defaults */
5912     if(appData.pieceToCharTable != NULL)
5913         SetCharTable(pieceToChar, appData.pieceToCharTable);
5914
5915     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5916
5917         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5918             s = (ChessSquare) 0; /* account holding counts in guard band */
5919         for( i=0; i<BOARD_HEIGHT; i++ )
5920             initialPosition[i][j] = s;
5921
5922         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5923         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5924         initialPosition[pawnRow][j] = WhitePawn;
5925         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5926         if(gameInfo.variant == VariantXiangqi) {
5927             if(j&1) {
5928                 initialPosition[pawnRow][j] =
5929                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5930                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5931                    initialPosition[2][j] = WhiteCannon;
5932                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5933                 }
5934             }
5935         }
5936         if(gameInfo.variant == VariantGrand) {
5937             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5938                initialPosition[0][j] = WhiteRook;
5939                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5940             }
5941         }
5942         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5943     }
5944     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5945
5946             j=BOARD_LEFT+1;
5947             initialPosition[1][j] = WhiteBishop;
5948             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5949             j=BOARD_RGHT-2;
5950             initialPosition[1][j] = WhiteRook;
5951             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5952     }
5953
5954     if( nrCastlingRights == -1) {
5955         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5956         /*       This sets default castling rights from none to normal corners   */
5957         /* Variants with other castling rights must set them themselves above    */
5958         nrCastlingRights = 6;
5959
5960         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5961         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5962         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5963         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5964         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5965         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5966      }
5967
5968      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5969      if(gameInfo.variant == VariantGreat) { // promotion commoners
5970         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5971         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5972         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5973         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5974      }
5975      if( gameInfo.variant == VariantSChess ) {
5976       initialPosition[1][0] = BlackMarshall;
5977       initialPosition[2][0] = BlackAngel;
5978       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5979       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5980       initialPosition[1][1] = initialPosition[2][1] = 
5981       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5982      }
5983   if (appData.debugMode) {
5984     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5985   }
5986     if(shuffleOpenings) {
5987         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5988         startedFromSetupPosition = TRUE;
5989     }
5990     if(startedFromPositionFile) {
5991       /* [HGM] loadPos: use PositionFile for every new game */
5992       CopyBoard(initialPosition, filePosition);
5993       for(i=0; i<nrCastlingRights; i++)
5994           initialRights[i] = filePosition[CASTLING][i];
5995       startedFromSetupPosition = TRUE;
5996     }
5997
5998     CopyBoard(boards[0], initialPosition);
5999
6000     if(oldx != gameInfo.boardWidth ||
6001        oldy != gameInfo.boardHeight ||
6002        oldv != gameInfo.variant ||
6003        oldh != gameInfo.holdingsWidth
6004                                          )
6005             InitDrawingSizes(-2 ,0);
6006
6007     oldv = gameInfo.variant;
6008     if (redraw)
6009       DrawPosition(TRUE, boards[currentMove]);
6010 }
6011
6012 void
6013 SendBoard (ChessProgramState *cps, int moveNum)
6014 {
6015     char message[MSG_SIZ];
6016
6017     if (cps->useSetboard) {
6018       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6019       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6020       SendToProgram(message, cps);
6021       free(fen);
6022
6023     } else {
6024       ChessSquare *bp;
6025       int i, j, left=0, right=BOARD_WIDTH;
6026       /* Kludge to set black to move, avoiding the troublesome and now
6027        * deprecated "black" command.
6028        */
6029       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6030         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6031
6032       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6033
6034       SendToProgram("edit\n", cps);
6035       SendToProgram("#\n", cps);
6036       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6037         bp = &boards[moveNum][i][left];
6038         for (j = left; j < right; j++, bp++) {
6039           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6040           if ((int) *bp < (int) BlackPawn) {
6041             if(j == BOARD_RGHT+1)
6042                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6043             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6044             if(message[0] == '+' || message[0] == '~') {
6045               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6046                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6047                         AAA + j, ONE + i);
6048             }
6049             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6050                 message[1] = BOARD_RGHT   - 1 - j + '1';
6051                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6052             }
6053             SendToProgram(message, cps);
6054           }
6055         }
6056       }
6057
6058       SendToProgram("c\n", cps);
6059       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6060         bp = &boards[moveNum][i][left];
6061         for (j = left; j < right; j++, bp++) {
6062           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6063           if (((int) *bp != (int) EmptySquare)
6064               && ((int) *bp >= (int) BlackPawn)) {
6065             if(j == BOARD_LEFT-2)
6066                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6067             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6068                     AAA + j, ONE + i);
6069             if(message[0] == '+' || message[0] == '~') {
6070               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6071                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6072                         AAA + j, ONE + i);
6073             }
6074             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6075                 message[1] = BOARD_RGHT   - 1 - j + '1';
6076                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6077             }
6078             SendToProgram(message, cps);
6079           }
6080         }
6081       }
6082
6083       SendToProgram(".\n", cps);
6084     }
6085     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6086 }
6087
6088 ChessSquare
6089 DefaultPromoChoice (int white)
6090 {
6091     ChessSquare result;
6092     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6093         result = WhiteFerz; // no choice
6094     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6095         result= WhiteKing; // in Suicide Q is the last thing we want
6096     else if(gameInfo.variant == VariantSpartan)
6097         result = white ? WhiteQueen : WhiteAngel;
6098     else result = WhiteQueen;
6099     if(!white) result = WHITE_TO_BLACK result;
6100     return result;
6101 }
6102
6103 static int autoQueen; // [HGM] oneclick
6104
6105 int
6106 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6107 {
6108     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6109     /* [HGM] add Shogi promotions */
6110     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6111     ChessSquare piece;
6112     ChessMove moveType;
6113     Boolean premove;
6114
6115     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6116     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6117
6118     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6119       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6120         return FALSE;
6121
6122     piece = boards[currentMove][fromY][fromX];
6123     if(gameInfo.variant == VariantShogi) {
6124         promotionZoneSize = BOARD_HEIGHT/3;
6125         highestPromotingPiece = (int)WhiteFerz;
6126     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6127         promotionZoneSize = 3;
6128     }
6129
6130     // Treat Lance as Pawn when it is not representing Amazon
6131     if(gameInfo.variant != VariantSuper) {
6132         if(piece == WhiteLance) piece = WhitePawn; else
6133         if(piece == BlackLance) piece = BlackPawn;
6134     }
6135
6136     // next weed out all moves that do not touch the promotion zone at all
6137     if((int)piece >= BlackPawn) {
6138         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6139              return FALSE;
6140         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6141     } else {
6142         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6143            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6144     }
6145
6146     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6147
6148     // weed out mandatory Shogi promotions
6149     if(gameInfo.variant == VariantShogi) {
6150         if(piece >= BlackPawn) {
6151             if(toY == 0 && piece == BlackPawn ||
6152                toY == 0 && piece == BlackQueen ||
6153                toY <= 1 && piece == BlackKnight) {
6154                 *promoChoice = '+';
6155                 return FALSE;
6156             }
6157         } else {
6158             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6159                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6160                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6161                 *promoChoice = '+';
6162                 return FALSE;
6163             }
6164         }
6165     }
6166
6167     // weed out obviously illegal Pawn moves
6168     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6169         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6170         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6171         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6172         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6173         // note we are not allowed to test for valid (non-)capture, due to premove
6174     }
6175
6176     // we either have a choice what to promote to, or (in Shogi) whether to promote
6177     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6178         *promoChoice = PieceToChar(BlackFerz);  // no choice
6179         return FALSE;
6180     }
6181     // no sense asking what we must promote to if it is going to explode...
6182     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6183         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6184         return FALSE;
6185     }
6186     // give caller the default choice even if we will not make it
6187     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6188     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6189     if(        sweepSelect && gameInfo.variant != VariantGreat
6190                            && gameInfo.variant != VariantGrand
6191                            && gameInfo.variant != VariantSuper) return FALSE;
6192     if(autoQueen) return FALSE; // predetermined
6193
6194     // suppress promotion popup on illegal moves that are not premoves
6195     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6196               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6197     if(appData.testLegality && !premove) {
6198         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6199                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6200         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6201             return FALSE;
6202     }
6203
6204     return TRUE;
6205 }
6206
6207 int
6208 InPalace (int row, int column)
6209 {   /* [HGM] for Xiangqi */
6210     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6211          column < (BOARD_WIDTH + 4)/2 &&
6212          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6213     return FALSE;
6214 }
6215
6216 int
6217 PieceForSquare (int x, int y)
6218 {
6219   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6220      return -1;
6221   else
6222      return boards[currentMove][y][x];
6223 }
6224
6225 int
6226 OKToStartUserMove (int x, int y)
6227 {
6228     ChessSquare from_piece;
6229     int white_piece;
6230
6231     if (matchMode) return FALSE;
6232     if (gameMode == EditPosition) return TRUE;
6233
6234     if (x >= 0 && y >= 0)
6235       from_piece = boards[currentMove][y][x];
6236     else
6237       from_piece = EmptySquare;
6238
6239     if (from_piece == EmptySquare) return FALSE;
6240
6241     white_piece = (int)from_piece >= (int)WhitePawn &&
6242       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6243
6244     switch (gameMode) {
6245       case AnalyzeFile:
6246       case TwoMachinesPlay:
6247       case EndOfGame:
6248         return FALSE;
6249
6250       case IcsObserving:
6251       case IcsIdle:
6252         return FALSE;
6253
6254       case MachinePlaysWhite:
6255       case IcsPlayingBlack:
6256         if (appData.zippyPlay) return FALSE;
6257         if (white_piece) {
6258             DisplayMoveError(_("You are playing Black"));
6259             return FALSE;
6260         }
6261         break;
6262
6263       case MachinePlaysBlack:
6264       case IcsPlayingWhite:
6265         if (appData.zippyPlay) return FALSE;
6266         if (!white_piece) {
6267             DisplayMoveError(_("You are playing White"));
6268             return FALSE;
6269         }
6270         break;
6271
6272       case PlayFromGameFile:
6273             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6274       case EditGame:
6275         if (!white_piece && WhiteOnMove(currentMove)) {
6276             DisplayMoveError(_("It is White's turn"));
6277             return FALSE;
6278         }
6279         if (white_piece && !WhiteOnMove(currentMove)) {
6280             DisplayMoveError(_("It is Black's turn"));
6281             return FALSE;
6282         }
6283         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6284             /* Editing correspondence game history */
6285             /* Could disallow this or prompt for confirmation */
6286             cmailOldMove = -1;
6287         }
6288         break;
6289
6290       case BeginningOfGame:
6291         if (appData.icsActive) return FALSE;
6292         if (!appData.noChessProgram) {
6293             if (!white_piece) {
6294                 DisplayMoveError(_("You are playing White"));
6295                 return FALSE;
6296             }
6297         }
6298         break;
6299
6300       case Training:
6301         if (!white_piece && WhiteOnMove(currentMove)) {
6302             DisplayMoveError(_("It is White's turn"));
6303             return FALSE;
6304         }
6305         if (white_piece && !WhiteOnMove(currentMove)) {
6306             DisplayMoveError(_("It is Black's turn"));
6307             return FALSE;
6308         }
6309         break;
6310
6311       default:
6312       case IcsExamining:
6313         break;
6314     }
6315     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6316         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6317         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6318         && gameMode != AnalyzeFile && gameMode != Training) {
6319         DisplayMoveError(_("Displayed position is not current"));
6320         return FALSE;
6321     }
6322     return TRUE;
6323 }
6324
6325 Boolean
6326 OnlyMove (int *x, int *y, Boolean captures) 
6327 {
6328     DisambiguateClosure cl;
6329     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6330     switch(gameMode) {
6331       case MachinePlaysBlack:
6332       case IcsPlayingWhite:
6333       case BeginningOfGame:
6334         if(!WhiteOnMove(currentMove)) return FALSE;
6335         break;
6336       case MachinePlaysWhite:
6337       case IcsPlayingBlack:
6338         if(WhiteOnMove(currentMove)) return FALSE;
6339         break;
6340       case EditGame:
6341         break;
6342       default:
6343         return FALSE;
6344     }
6345     cl.pieceIn = EmptySquare;
6346     cl.rfIn = *y;
6347     cl.ffIn = *x;
6348     cl.rtIn = -1;
6349     cl.ftIn = -1;
6350     cl.promoCharIn = NULLCHAR;
6351     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6352     if( cl.kind == NormalMove ||
6353         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6354         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6355         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6356       fromX = cl.ff;
6357       fromY = cl.rf;
6358       *x = cl.ft;
6359       *y = cl.rt;
6360       return TRUE;
6361     }
6362     if(cl.kind != ImpossibleMove) return FALSE;
6363     cl.pieceIn = EmptySquare;
6364     cl.rfIn = -1;
6365     cl.ffIn = -1;
6366     cl.rtIn = *y;
6367     cl.ftIn = *x;
6368     cl.promoCharIn = NULLCHAR;
6369     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6370     if( cl.kind == NormalMove ||
6371         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6372         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6373         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6374       fromX = cl.ff;
6375       fromY = cl.rf;
6376       *x = cl.ft;
6377       *y = cl.rt;
6378       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6379       return TRUE;
6380     }
6381     return FALSE;
6382 }
6383
6384 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6385 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6386 int lastLoadGameUseList = FALSE;
6387 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6388 ChessMove lastLoadGameStart = EndOfFile;
6389
6390 void
6391 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6392 {
6393     ChessMove moveType;
6394     ChessSquare pdown, pup;
6395
6396     /* Check if the user is playing in turn.  This is complicated because we
6397        let the user "pick up" a piece before it is his turn.  So the piece he
6398        tried to pick up may have been captured by the time he puts it down!
6399        Therefore we use the color the user is supposed to be playing in this
6400        test, not the color of the piece that is currently on the starting
6401        square---except in EditGame mode, where the user is playing both
6402        sides; fortunately there the capture race can't happen.  (It can
6403        now happen in IcsExamining mode, but that's just too bad.  The user
6404        will get a somewhat confusing message in that case.)
6405        */
6406
6407     switch (gameMode) {
6408       case AnalyzeFile:
6409       case TwoMachinesPlay:
6410       case EndOfGame:
6411       case IcsObserving:
6412       case IcsIdle:
6413         /* We switched into a game mode where moves are not accepted,
6414            perhaps while the mouse button was down. */
6415         return;
6416
6417       case MachinePlaysWhite:
6418         /* User is moving for Black */
6419         if (WhiteOnMove(currentMove)) {
6420             DisplayMoveError(_("It is White's turn"));
6421             return;
6422         }
6423         break;
6424
6425       case MachinePlaysBlack:
6426         /* User is moving for White */
6427         if (!WhiteOnMove(currentMove)) {
6428             DisplayMoveError(_("It is Black's turn"));
6429             return;
6430         }
6431         break;
6432
6433       case PlayFromGameFile:
6434             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6435       case EditGame:
6436       case IcsExamining:
6437       case BeginningOfGame:
6438       case AnalyzeMode:
6439       case Training:
6440         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6441         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6442             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6443             /* User is moving for Black */
6444             if (WhiteOnMove(currentMove)) {
6445                 DisplayMoveError(_("It is White's turn"));
6446                 return;
6447             }
6448         } else {
6449             /* User is moving for White */
6450             if (!WhiteOnMove(currentMove)) {
6451                 DisplayMoveError(_("It is Black's turn"));
6452                 return;
6453             }
6454         }
6455         break;
6456
6457       case IcsPlayingBlack:
6458         /* User is moving for Black */
6459         if (WhiteOnMove(currentMove)) {
6460             if (!appData.premove) {
6461                 DisplayMoveError(_("It is White's turn"));
6462             } else if (toX >= 0 && toY >= 0) {
6463                 premoveToX = toX;
6464                 premoveToY = toY;
6465                 premoveFromX = fromX;
6466                 premoveFromY = fromY;
6467                 premovePromoChar = promoChar;
6468                 gotPremove = 1;
6469                 if (appData.debugMode)
6470                     fprintf(debugFP, "Got premove: fromX %d,"
6471                             "fromY %d, toX %d, toY %d\n",
6472                             fromX, fromY, toX, toY);
6473             }
6474             return;
6475         }
6476         break;
6477
6478       case IcsPlayingWhite:
6479         /* User is moving for White */
6480         if (!WhiteOnMove(currentMove)) {
6481             if (!appData.premove) {
6482                 DisplayMoveError(_("It is Black's turn"));
6483             } else if (toX >= 0 && toY >= 0) {
6484                 premoveToX = toX;
6485                 premoveToY = toY;
6486                 premoveFromX = fromX;
6487                 premoveFromY = fromY;
6488                 premovePromoChar = promoChar;
6489                 gotPremove = 1;
6490                 if (appData.debugMode)
6491                     fprintf(debugFP, "Got premove: fromX %d,"
6492                             "fromY %d, toX %d, toY %d\n",
6493                             fromX, fromY, toX, toY);
6494             }
6495             return;
6496         }
6497         break;
6498
6499       default:
6500         break;
6501
6502       case EditPosition:
6503         /* EditPosition, empty square, or different color piece;
6504            click-click move is possible */
6505         if (toX == -2 || toY == -2) {
6506             boards[0][fromY][fromX] = EmptySquare;
6507             DrawPosition(FALSE, boards[currentMove]);
6508             return;
6509         } else if (toX >= 0 && toY >= 0) {
6510             boards[0][toY][toX] = boards[0][fromY][fromX];
6511             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6512                 if(boards[0][fromY][0] != EmptySquare) {
6513                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6514                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6515                 }
6516             } else
6517             if(fromX == BOARD_RGHT+1) {
6518                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6519                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6520                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6521                 }
6522             } else
6523             boards[0][fromY][fromX] = EmptySquare;
6524             DrawPosition(FALSE, boards[currentMove]);
6525             return;
6526         }
6527         return;
6528     }
6529
6530     if(toX < 0 || toY < 0) return;
6531     pdown = boards[currentMove][fromY][fromX];
6532     pup = boards[currentMove][toY][toX];
6533
6534     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6535     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6536          if( pup != EmptySquare ) return;
6537          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6538            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6539                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6540            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6541            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6542            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6543            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6544          fromY = DROP_RANK;
6545     }
6546
6547     /* [HGM] always test for legality, to get promotion info */
6548     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6549                                          fromY, fromX, toY, toX, promoChar);
6550
6551     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6552
6553     /* [HGM] but possibly ignore an IllegalMove result */
6554     if (appData.testLegality) {
6555         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6556             DisplayMoveError(_("Illegal move"));
6557             return;
6558         }
6559     }
6560
6561     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6562 }
6563
6564 /* Common tail of UserMoveEvent and DropMenuEvent */
6565 int
6566 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6567 {
6568     char *bookHit = 0;
6569
6570     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6571         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6572         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6573         if(WhiteOnMove(currentMove)) {
6574             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6575         } else {
6576             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6577         }
6578     }
6579
6580     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6581        move type in caller when we know the move is a legal promotion */
6582     if(moveType == NormalMove && promoChar)
6583         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6584
6585     /* [HGM] <popupFix> The following if has been moved here from
6586        UserMoveEvent(). Because it seemed to belong here (why not allow
6587        piece drops in training games?), and because it can only be
6588        performed after it is known to what we promote. */
6589     if (gameMode == Training) {
6590       /* compare the move played on the board to the next move in the
6591        * game. If they match, display the move and the opponent's response.
6592        * If they don't match, display an error message.
6593        */
6594       int saveAnimate;
6595       Board testBoard;
6596       CopyBoard(testBoard, boards[currentMove]);
6597       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6598
6599       if (CompareBoards(testBoard, boards[currentMove+1])) {
6600         ForwardInner(currentMove+1);
6601
6602         /* Autoplay the opponent's response.
6603          * if appData.animate was TRUE when Training mode was entered,
6604          * the response will be animated.
6605          */
6606         saveAnimate = appData.animate;
6607         appData.animate = animateTraining;
6608         ForwardInner(currentMove+1);
6609         appData.animate = saveAnimate;
6610
6611         /* check for the end of the game */
6612         if (currentMove >= forwardMostMove) {
6613           gameMode = PlayFromGameFile;
6614           ModeHighlight();
6615           SetTrainingModeOff();
6616           DisplayInformation(_("End of game"));
6617         }
6618       } else {
6619         DisplayError(_("Incorrect move"), 0);
6620       }
6621       return 1;
6622     }
6623
6624   /* Ok, now we know that the move is good, so we can kill
6625      the previous line in Analysis Mode */
6626   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6627                                 && currentMove < forwardMostMove) {
6628     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6629     else forwardMostMove = currentMove;
6630   }
6631
6632   /* If we need the chess program but it's dead, restart it */
6633   ResurrectChessProgram();
6634
6635   /* A user move restarts a paused game*/
6636   if (pausing)
6637     PauseEvent();
6638
6639   thinkOutput[0] = NULLCHAR;
6640
6641   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6642
6643   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6644     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6645     return 1;
6646   }
6647
6648   if (gameMode == BeginningOfGame) {
6649     if (appData.noChessProgram) {
6650       gameMode = EditGame;
6651       SetGameInfo();
6652     } else {
6653       char buf[MSG_SIZ];
6654       gameMode = MachinePlaysBlack;
6655       StartClocks();
6656       SetGameInfo();
6657       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6658       DisplayTitle(buf);
6659       if (first.sendName) {
6660         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6661         SendToProgram(buf, &first);
6662       }
6663       StartClocks();
6664     }
6665     ModeHighlight();
6666   }
6667
6668   /* Relay move to ICS or chess engine */
6669   if (appData.icsActive) {
6670     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6671         gameMode == IcsExamining) {
6672       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6673         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6674         SendToICS("draw ");
6675         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6676       }
6677       // also send plain move, in case ICS does not understand atomic claims
6678       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6679       ics_user_moved = 1;
6680     }
6681   } else {
6682     if (first.sendTime && (gameMode == BeginningOfGame ||
6683                            gameMode == MachinePlaysWhite ||
6684                            gameMode == MachinePlaysBlack)) {
6685       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6686     }
6687     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6688          // [HGM] book: if program might be playing, let it use book
6689         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6690         first.maybeThinking = TRUE;
6691     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6692         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6693         SendBoard(&first, currentMove+1);
6694     } else SendMoveToProgram(forwardMostMove-1, &first);
6695     if (currentMove == cmailOldMove + 1) {
6696       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6697     }
6698   }
6699
6700   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6701
6702   switch (gameMode) {
6703   case EditGame:
6704     if(appData.testLegality)
6705     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6706     case MT_NONE:
6707     case MT_CHECK:
6708       break;
6709     case MT_CHECKMATE:
6710     case MT_STAINMATE:
6711       if (WhiteOnMove(currentMove)) {
6712         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6713       } else {
6714         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6715       }
6716       break;
6717     case MT_STALEMATE:
6718       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6719       break;
6720     }
6721     break;
6722
6723   case MachinePlaysBlack:
6724   case MachinePlaysWhite:
6725     /* disable certain menu options while machine is thinking */
6726     SetMachineThinkingEnables();
6727     break;
6728
6729   default:
6730     break;
6731   }
6732
6733   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6734   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6735
6736   if(bookHit) { // [HGM] book: simulate book reply
6737         static char bookMove[MSG_SIZ]; // a bit generous?
6738
6739         programStats.nodes = programStats.depth = programStats.time =
6740         programStats.score = programStats.got_only_move = 0;
6741         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6742
6743         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6744         strcat(bookMove, bookHit);
6745         HandleMachineMove(bookMove, &first);
6746   }
6747   return 1;
6748 }
6749
6750 void
6751 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6752 {
6753     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6754     Markers *m = (Markers *) closure;
6755     if(rf == fromY && ff == fromX)
6756         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6757                          || kind == WhiteCapturesEnPassant
6758                          || kind == BlackCapturesEnPassant);
6759     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6760 }
6761
6762 void
6763 MarkTargetSquares (int clear)
6764 {
6765   int x, y;
6766   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6767      !appData.testLegality || gameMode == EditPosition) return;
6768   if(clear) {
6769     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6770   } else {
6771     int capt = 0;
6772     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6773     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6774       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6775       if(capt)
6776       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6777     }
6778   }
6779   DrawPosition(TRUE, NULL);
6780 }
6781
6782 int
6783 Explode (Board board, int fromX, int fromY, int toX, int toY)
6784 {
6785     if(gameInfo.variant == VariantAtomic &&
6786        (board[toY][toX] != EmptySquare ||                     // capture?
6787         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6788                          board[fromY][fromX] == BlackPawn   )
6789       )) {
6790         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6791         return TRUE;
6792     }
6793     return FALSE;
6794 }
6795
6796 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6797
6798 int
6799 CanPromote (ChessSquare piece, int y)
6800 {
6801         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6802         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6803         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6804            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6805            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6806                                                   gameInfo.variant == VariantMakruk) return FALSE;
6807         return (piece == BlackPawn && y == 1 ||
6808                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6809                 piece == BlackLance && y == 1 ||
6810                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6811 }
6812
6813 void
6814 LeftClick (ClickType clickType, int xPix, int yPix)
6815 {
6816     int x, y;
6817     Boolean saveAnimate;
6818     static int second = 0, promotionChoice = 0, clearFlag = 0;
6819     char promoChoice = NULLCHAR;
6820     ChessSquare piece;
6821
6822     if(appData.seekGraph && appData.icsActive && loggedOn &&
6823         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6824         SeekGraphClick(clickType, xPix, yPix, 0);
6825         return;
6826     }
6827
6828     if (clickType == Press) ErrorPopDown();
6829
6830     x = EventToSquare(xPix, BOARD_WIDTH);
6831     y = EventToSquare(yPix, BOARD_HEIGHT);
6832     if (!flipView && y >= 0) {
6833         y = BOARD_HEIGHT - 1 - y;
6834     }
6835     if (flipView && x >= 0) {
6836         x = BOARD_WIDTH - 1 - x;
6837     }
6838
6839     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6840         defaultPromoChoice = promoSweep;
6841         promoSweep = EmptySquare;   // terminate sweep
6842         promoDefaultAltered = TRUE;
6843         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6844     }
6845
6846     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6847         if(clickType == Release) return; // ignore upclick of click-click destination
6848         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6849         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6850         if(gameInfo.holdingsWidth &&
6851                 (WhiteOnMove(currentMove)
6852                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6853                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6854             // click in right holdings, for determining promotion piece
6855             ChessSquare p = boards[currentMove][y][x];
6856             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6857             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6858             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6859                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6860                 fromX = fromY = -1;
6861                 return;
6862             }
6863         }
6864         DrawPosition(FALSE, boards[currentMove]);
6865         return;
6866     }
6867
6868     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6869     if(clickType == Press
6870             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6871               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6872               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6873         return;
6874
6875     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6876         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6877
6878     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6879         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6880                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6881         defaultPromoChoice = DefaultPromoChoice(side);
6882     }
6883
6884     autoQueen = appData.alwaysPromoteToQueen;
6885
6886     if (fromX == -1) {
6887       int originalY = y;
6888       gatingPiece = EmptySquare;
6889       if (clickType != Press) {
6890         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6891             DragPieceEnd(xPix, yPix); dragging = 0;
6892             DrawPosition(FALSE, NULL);
6893         }
6894         return;
6895       }
6896       fromX = x; fromY = y; toX = toY = -1;
6897       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6898          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6899          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6900             /* First square */
6901             if (OKToStartUserMove(fromX, fromY)) {
6902                 second = 0;
6903                 MarkTargetSquares(0);
6904                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6905                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6906                     promoSweep = defaultPromoChoice;
6907                     selectFlag = 0; lastX = xPix; lastY = yPix;
6908                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6909                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6910                 }
6911                 if (appData.highlightDragging) {
6912                     SetHighlights(fromX, fromY, -1, -1);
6913                 }
6914             } else fromX = fromY = -1;
6915             return;
6916         }
6917     }
6918
6919     /* fromX != -1 */
6920     if (clickType == Press && gameMode != EditPosition) {
6921         ChessSquare fromP;
6922         ChessSquare toP;
6923         int frc;
6924
6925         // ignore off-board to clicks
6926         if(y < 0 || x < 0) return;
6927
6928         /* Check if clicking again on the same color piece */
6929         fromP = boards[currentMove][fromY][fromX];
6930         toP = boards[currentMove][y][x];
6931         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6932         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6933              WhitePawn <= toP && toP <= WhiteKing &&
6934              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6935              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6936             (BlackPawn <= fromP && fromP <= BlackKing &&
6937              BlackPawn <= toP && toP <= BlackKing &&
6938              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6939              !(fromP == BlackKing && toP == BlackRook && frc))) {
6940             /* Clicked again on same color piece -- changed his mind */
6941             second = (x == fromX && y == fromY);
6942             promoDefaultAltered = FALSE;
6943             MarkTargetSquares(1);
6944            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6945             if (appData.highlightDragging) {
6946                 SetHighlights(x, y, -1, -1);
6947             } else {
6948                 ClearHighlights();
6949             }
6950             if (OKToStartUserMove(x, y)) {
6951                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6952                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6953                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6954                  gatingPiece = boards[currentMove][fromY][fromX];
6955                 else gatingPiece = EmptySquare;
6956                 fromX = x;
6957                 fromY = y; dragging = 1;
6958                 MarkTargetSquares(0);
6959                 DragPieceBegin(xPix, yPix, FALSE);
6960                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6961                     promoSweep = defaultPromoChoice;
6962                     selectFlag = 0; lastX = xPix; lastY = yPix;
6963                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6964                 }
6965             }
6966            }
6967            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6968            second = FALSE; 
6969         }
6970         // ignore clicks on holdings
6971         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6972     }
6973
6974     if (clickType == Release && x == fromX && y == fromY) {
6975         DragPieceEnd(xPix, yPix); dragging = 0;
6976         if(clearFlag) {
6977             // a deferred attempt to click-click move an empty square on top of a piece
6978             boards[currentMove][y][x] = EmptySquare;
6979             ClearHighlights();
6980             DrawPosition(FALSE, boards[currentMove]);
6981             fromX = fromY = -1; clearFlag = 0;
6982             return;
6983         }
6984         if (appData.animateDragging) {
6985             /* Undo animation damage if any */
6986             DrawPosition(FALSE, NULL);
6987         }
6988         if (second) {
6989             /* Second up/down in same square; just abort move */
6990             second = 0;
6991             fromX = fromY = -1;
6992             gatingPiece = EmptySquare;
6993             ClearHighlights();
6994             gotPremove = 0;
6995             ClearPremoveHighlights();
6996         } else {
6997             /* First upclick in same square; start click-click mode */
6998             SetHighlights(x, y, -1, -1);
6999         }
7000         return;
7001     }
7002
7003     clearFlag = 0;
7004
7005     /* we now have a different from- and (possibly off-board) to-square */
7006     /* Completed move */
7007     toX = x;
7008     toY = y;
7009     saveAnimate = appData.animate;
7010     MarkTargetSquares(1);
7011     if (clickType == Press) {
7012         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7013             // must be Edit Position mode with empty-square selected
7014             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7015             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7016             return;
7017         }
7018         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7019             ChessSquare piece = boards[currentMove][fromY][fromX];
7020             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7021             promoSweep = defaultPromoChoice;
7022             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7023             selectFlag = 0; lastX = xPix; lastY = yPix;
7024             Sweep(0); // Pawn that is going to promote: preview promotion piece
7025             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7026             DrawPosition(FALSE, boards[currentMove]);
7027             return;
7028         }
7029         /* Finish clickclick move */
7030         if (appData.animate || appData.highlightLastMove) {
7031             SetHighlights(fromX, fromY, toX, toY);
7032         } else {
7033             ClearHighlights();
7034         }
7035     } else {
7036         /* Finish drag move */
7037         if (appData.highlightLastMove) {
7038             SetHighlights(fromX, fromY, toX, toY);
7039         } else {
7040             ClearHighlights();
7041         }
7042         DragPieceEnd(xPix, yPix); dragging = 0;
7043         /* Don't animate move and drag both */
7044         appData.animate = FALSE;
7045     }
7046
7047     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7048     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7049         ChessSquare piece = boards[currentMove][fromY][fromX];
7050         if(gameMode == EditPosition && piece != EmptySquare &&
7051            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7052             int n;
7053
7054             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7055                 n = PieceToNumber(piece - (int)BlackPawn);
7056                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7057                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7058                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7059             } else
7060             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7061                 n = PieceToNumber(piece);
7062                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7063                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7064                 boards[currentMove][n][BOARD_WIDTH-2]++;
7065             }
7066             boards[currentMove][fromY][fromX] = EmptySquare;
7067         }
7068         ClearHighlights();
7069         fromX = fromY = -1;
7070         DrawPosition(TRUE, boards[currentMove]);
7071         return;
7072     }
7073
7074     // off-board moves should not be highlighted
7075     if(x < 0 || y < 0) ClearHighlights();
7076
7077     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7078
7079     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7080         SetHighlights(fromX, fromY, toX, toY);
7081         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7082             // [HGM] super: promotion to captured piece selected from holdings
7083             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7084             promotionChoice = TRUE;
7085             // kludge follows to temporarily execute move on display, without promoting yet
7086             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7087             boards[currentMove][toY][toX] = p;
7088             DrawPosition(FALSE, boards[currentMove]);
7089             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7090             boards[currentMove][toY][toX] = q;
7091             DisplayMessage("Click in holdings to choose piece", "");
7092             return;
7093         }
7094         PromotionPopUp();
7095     } else {
7096         int oldMove = currentMove;
7097         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7098         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7099         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7100         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7101            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7102             DrawPosition(TRUE, boards[currentMove]);
7103         fromX = fromY = -1;
7104     }
7105     appData.animate = saveAnimate;
7106     if (appData.animate || appData.animateDragging) {
7107         /* Undo animation damage if needed */
7108         DrawPosition(FALSE, NULL);
7109     }
7110 }
7111
7112 int
7113 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7114 {   // front-end-free part taken out of PieceMenuPopup
7115     int whichMenu; int xSqr, ySqr;
7116
7117     if(seekGraphUp) { // [HGM] seekgraph
7118         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7119         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7120         return -2;
7121     }
7122
7123     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7124          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7125         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7126         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7127         if(action == Press)   {
7128             originalFlip = flipView;
7129             flipView = !flipView; // temporarily flip board to see game from partners perspective
7130             DrawPosition(TRUE, partnerBoard);
7131             DisplayMessage(partnerStatus, "");
7132             partnerUp = TRUE;
7133         } else if(action == Release) {
7134             flipView = originalFlip;
7135             DrawPosition(TRUE, boards[currentMove]);
7136             partnerUp = FALSE;
7137         }
7138         return -2;
7139     }
7140
7141     xSqr = EventToSquare(x, BOARD_WIDTH);
7142     ySqr = EventToSquare(y, BOARD_HEIGHT);
7143     if (action == Release) {
7144         if(pieceSweep != EmptySquare) {
7145             EditPositionMenuEvent(pieceSweep, toX, toY);
7146             pieceSweep = EmptySquare;
7147         } else UnLoadPV(); // [HGM] pv
7148     }
7149     if (action != Press) return -2; // return code to be ignored
7150     switch (gameMode) {
7151       case IcsExamining:
7152         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7153       case EditPosition:
7154         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7155         if (xSqr < 0 || ySqr < 0) return -1;
7156         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7157         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7158         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7159         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7160         NextPiece(0);
7161         return 2; // grab
7162       case IcsObserving:
7163         if(!appData.icsEngineAnalyze) return -1;
7164       case IcsPlayingWhite:
7165       case IcsPlayingBlack:
7166         if(!appData.zippyPlay) goto noZip;
7167       case AnalyzeMode:
7168       case AnalyzeFile:
7169       case MachinePlaysWhite:
7170       case MachinePlaysBlack:
7171       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7172         if (!appData.dropMenu) {
7173           LoadPV(x, y);
7174           return 2; // flag front-end to grab mouse events
7175         }
7176         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7177            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7178       case EditGame:
7179       noZip:
7180         if (xSqr < 0 || ySqr < 0) return -1;
7181         if (!appData.dropMenu || appData.testLegality &&
7182             gameInfo.variant != VariantBughouse &&
7183             gameInfo.variant != VariantCrazyhouse) return -1;
7184         whichMenu = 1; // drop menu
7185         break;
7186       default:
7187         return -1;
7188     }
7189
7190     if (((*fromX = xSqr) < 0) ||
7191         ((*fromY = ySqr) < 0)) {
7192         *fromX = *fromY = -1;
7193         return -1;
7194     }
7195     if (flipView)
7196       *fromX = BOARD_WIDTH - 1 - *fromX;
7197     else
7198       *fromY = BOARD_HEIGHT - 1 - *fromY;
7199
7200     return whichMenu;
7201 }
7202
7203 void
7204 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7205 {
7206 //    char * hint = lastHint;
7207     FrontEndProgramStats stats;
7208
7209     stats.which = cps == &first ? 0 : 1;
7210     stats.depth = cpstats->depth;
7211     stats.nodes = cpstats->nodes;
7212     stats.score = cpstats->score;
7213     stats.time = cpstats->time;
7214     stats.pv = cpstats->movelist;
7215     stats.hint = lastHint;
7216     stats.an_move_index = 0;
7217     stats.an_move_count = 0;
7218
7219     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7220         stats.hint = cpstats->move_name;
7221         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7222         stats.an_move_count = cpstats->nr_moves;
7223     }
7224
7225     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
7226
7227     SetProgramStats( &stats );
7228 }
7229
7230 void
7231 ClearEngineOutputPane (int which)
7232 {
7233     static FrontEndProgramStats dummyStats;
7234     dummyStats.which = which;
7235     dummyStats.pv = "#";
7236     SetProgramStats( &dummyStats );
7237 }
7238
7239 #define MAXPLAYERS 500
7240
7241 char *
7242 TourneyStandings (int display)
7243 {
7244     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7245     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7246     char result, *p, *names[MAXPLAYERS];
7247
7248     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7249         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7250     names[0] = p = strdup(appData.participants);
7251     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7252
7253     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7254
7255     while(result = appData.results[nr]) {
7256         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7257         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7258         wScore = bScore = 0;
7259         switch(result) {
7260           case '+': wScore = 2; break;
7261           case '-': bScore = 2; break;
7262           case '=': wScore = bScore = 1; break;
7263           case ' ':
7264           case '*': return strdup("busy"); // tourney not finished
7265         }
7266         score[w] += wScore;
7267         score[b] += bScore;
7268         games[w]++;
7269         games[b]++;
7270         nr++;
7271     }
7272     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7273     for(w=0; w<nPlayers; w++) {
7274         bScore = -1;
7275         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7276         ranking[w] = b; points[w] = bScore; score[b] = -2;
7277     }
7278     p = malloc(nPlayers*34+1);
7279     for(w=0; w<nPlayers && w<display; w++)
7280         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7281     free(names[0]);
7282     return p;
7283 }
7284
7285 void
7286 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7287 {       // count all piece types
7288         int p, f, r;
7289         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7290         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7291         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7292                 p = board[r][f];
7293                 pCnt[p]++;
7294                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7295                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7296                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7297                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7298                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7299                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7300         }
7301 }
7302
7303 int
7304 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7305 {
7306         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7307         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7308
7309         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7310         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7311         if(myPawns == 2 && nMine == 3) // KPP
7312             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7313         if(myPawns == 1 && nMine == 2) // KP
7314             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7315         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7316             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7317         if(myPawns) return FALSE;
7318         if(pCnt[WhiteRook+side])
7319             return pCnt[BlackRook-side] ||
7320                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7321                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7322                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7323         if(pCnt[WhiteCannon+side]) {
7324             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7325             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7326         }
7327         if(pCnt[WhiteKnight+side])
7328             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7329         return FALSE;
7330 }
7331
7332 int
7333 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7334 {
7335         VariantClass v = gameInfo.variant;
7336
7337         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7338         if(v == VariantShatranj) return TRUE; // always winnable through baring
7339         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7340         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7341
7342         if(v == VariantXiangqi) {
7343                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7344
7345                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7346                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7347                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7348                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7349                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7350                 if(stale) // we have at least one last-rank P plus perhaps C
7351                     return majors // KPKX
7352                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7353                 else // KCA*E*
7354                     return pCnt[WhiteFerz+side] // KCAK
7355                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7356                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7357                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7358
7359         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7360                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7361
7362                 if(nMine == 1) return FALSE; // bare King
7363                 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
7364                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7365                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7366                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7367                 if(pCnt[WhiteKnight+side])
7368                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7369                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7370                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7371                 if(nBishops)
7372                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7373                 if(pCnt[WhiteAlfil+side])
7374                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7375                 if(pCnt[WhiteWazir+side])
7376                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7377         }
7378
7379         return TRUE;
7380 }
7381
7382 int
7383 CompareWithRights (Board b1, Board b2)
7384 {
7385     int rights = 0;
7386     if(!CompareBoards(b1, b2)) return FALSE;
7387     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7388     /* compare castling rights */
7389     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7390            rights++; /* King lost rights, while rook still had them */
7391     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7392         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7393            rights++; /* but at least one rook lost them */
7394     }
7395     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7396            rights++;
7397     if( b1[CASTLING][5] != NoRights ) {
7398         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7399            rights++;
7400     }
7401     return rights == 0;
7402 }
7403
7404 int
7405 Adjudicate (ChessProgramState *cps)
7406 {       // [HGM] some adjudications useful with buggy engines
7407         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7408         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7409         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7410         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7411         int k, count = 0; static int bare = 1;
7412         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7413         Boolean canAdjudicate = !appData.icsActive;
7414
7415         // most tests only when we understand the game, i.e. legality-checking on
7416             if( appData.testLegality )
7417             {   /* [HGM] Some more adjudications for obstinate engines */
7418                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7419                 static int moveCount = 6;
7420                 ChessMove result;
7421                 char *reason = NULL;
7422
7423                 /* Count what is on board. */
7424                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7425
7426                 /* Some material-based adjudications that have to be made before stalemate test */
7427                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7428                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7429                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7430                      if(canAdjudicate && appData.checkMates) {
7431                          if(engineOpponent)
7432                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7433                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7434                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7435                          return 1;
7436                      }
7437                 }
7438
7439                 /* Bare King in Shatranj (loses) or Losers (wins) */
7440                 if( nrW == 1 || nrB == 1) {
7441                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7442                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7443                      if(canAdjudicate && appData.checkMates) {
7444                          if(engineOpponent)
7445                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7446                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7447                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7448                          return 1;
7449                      }
7450                   } else
7451                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7452                   {    /* bare King */
7453                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7454                         if(canAdjudicate && appData.checkMates) {
7455                             /* but only adjudicate if adjudication enabled */
7456                             if(engineOpponent)
7457                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7458                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7459                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7460                             return 1;
7461                         }
7462                   }
7463                 } else bare = 1;
7464
7465
7466             // don't wait for engine to announce game end if we can judge ourselves
7467             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7468               case MT_CHECK:
7469                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7470                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7471                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7472                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7473                             checkCnt++;
7474                         if(checkCnt >= 2) {
7475                             reason = "Xboard adjudication: 3rd check";
7476                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7477                             break;
7478                         }
7479                     }
7480                 }
7481               case MT_NONE:
7482               default:
7483                 break;
7484               case MT_STALEMATE:
7485               case MT_STAINMATE:
7486                 reason = "Xboard adjudication: Stalemate";
7487                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7488                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7489                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7490                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7491                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7492                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7493                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7494                                                                         EP_CHECKMATE : EP_WINS);
7495                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7496                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7497                 }
7498                 break;
7499               case MT_CHECKMATE:
7500                 reason = "Xboard adjudication: Checkmate";
7501                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7502                 break;
7503             }
7504
7505                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7506                     case EP_STALEMATE:
7507                         result = GameIsDrawn; break;
7508                     case EP_CHECKMATE:
7509                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7510                     case EP_WINS:
7511                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7512                     default:
7513                         result = EndOfFile;
7514                 }
7515                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7516                     if(engineOpponent)
7517                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7518                     GameEnds( result, reason, GE_XBOARD );
7519                     return 1;
7520                 }
7521
7522                 /* Next absolutely insufficient mating material. */
7523                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7524                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7525                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7526
7527                      /* always flag draws, for judging claims */
7528                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7529
7530                      if(canAdjudicate && appData.materialDraws) {
7531                          /* but only adjudicate them if adjudication enabled */
7532                          if(engineOpponent) {
7533                            SendToProgram("force\n", engineOpponent); // suppress reply
7534                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7535                          }
7536                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7537                          return 1;
7538                      }
7539                 }
7540
7541                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7542                 if(gameInfo.variant == VariantXiangqi ?
7543                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7544                  : nrW + nrB == 4 &&
7545                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7546                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7547                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7548                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7549                    ) ) {
7550                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7551                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7552                           if(engineOpponent) {
7553                             SendToProgram("force\n", engineOpponent); // suppress reply
7554                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7555                           }
7556                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7557                           return 1;
7558                      }
7559                 } else moveCount = 6;
7560             }
7561
7562         // Repetition draws and 50-move rule can be applied independently of legality testing
7563
7564                 /* Check for rep-draws */
7565                 count = 0;
7566                 for(k = forwardMostMove-2;
7567                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7568                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7569                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7570                     k-=2)
7571                 {   int rights=0;
7572                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7573                         /* compare castling rights */
7574                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7575                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7576                                 rights++; /* King lost rights, while rook still had them */
7577                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7578                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7579                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7580                                    rights++; /* but at least one rook lost them */
7581                         }
7582                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7583                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7584                                 rights++;
7585                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7586                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7587                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7588                                    rights++;
7589                         }
7590                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7591                             && appData.drawRepeats > 1) {
7592                              /* adjudicate after user-specified nr of repeats */
7593                              int result = GameIsDrawn;
7594                              char *details = "XBoard adjudication: repetition draw";
7595                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7596                                 // [HGM] xiangqi: check for forbidden perpetuals
7597                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7598                                 for(m=forwardMostMove; m>k; m-=2) {
7599                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7600                                         ourPerpetual = 0; // the current mover did not always check
7601                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7602                                         hisPerpetual = 0; // the opponent did not always check
7603                                 }
7604                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7605                                                                         ourPerpetual, hisPerpetual);
7606                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7607                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7608                                     details = "Xboard adjudication: perpetual checking";
7609                                 } else
7610                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7611                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7612                                 } else
7613                                 // Now check for perpetual chases
7614                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7615                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7616                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7617                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7618                                         static char resdet[MSG_SIZ];
7619                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7620                                         details = resdet;
7621                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7622                                     } else
7623                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7624                                         break; // Abort repetition-checking loop.
7625                                 }
7626                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7627                              }
7628                              if(engineOpponent) {
7629                                SendToProgram("force\n", engineOpponent); // suppress reply
7630                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7631                              }
7632                              GameEnds( result, details, GE_XBOARD );
7633                              return 1;
7634                         }
7635                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7636                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7637                     }
7638                 }
7639
7640                 /* Now we test for 50-move draws. Determine ply count */
7641                 count = forwardMostMove;
7642                 /* look for last irreversble move */
7643                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7644                     count--;
7645                 /* if we hit starting position, add initial plies */
7646                 if( count == backwardMostMove )
7647                     count -= initialRulePlies;
7648                 count = forwardMostMove - count;
7649                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7650                         // adjust reversible move counter for checks in Xiangqi
7651                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7652                         if(i < backwardMostMove) i = backwardMostMove;
7653                         while(i <= forwardMostMove) {
7654                                 lastCheck = inCheck; // check evasion does not count
7655                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7656                                 if(inCheck || lastCheck) count--; // check does not count
7657                                 i++;
7658                         }
7659                 }
7660                 if( count >= 100)
7661                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7662                          /* this is used to judge if draw claims are legal */
7663                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7664                          if(engineOpponent) {
7665                            SendToProgram("force\n", engineOpponent); // suppress reply
7666                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7667                          }
7668                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7669                          return 1;
7670                 }
7671
7672                 /* if draw offer is pending, treat it as a draw claim
7673                  * when draw condition present, to allow engines a way to
7674                  * claim draws before making their move to avoid a race
7675                  * condition occurring after their move
7676                  */
7677                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7678                          char *p = NULL;
7679                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7680                              p = "Draw claim: 50-move rule";
7681                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7682                              p = "Draw claim: 3-fold repetition";
7683                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7684                              p = "Draw claim: insufficient mating material";
7685                          if( p != NULL && canAdjudicate) {
7686                              if(engineOpponent) {
7687                                SendToProgram("force\n", engineOpponent); // suppress reply
7688                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7689                              }
7690                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7691                              return 1;
7692                          }
7693                 }
7694
7695                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7696                     if(engineOpponent) {
7697                       SendToProgram("force\n", engineOpponent); // suppress reply
7698                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7699                     }
7700                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7701                     return 1;
7702                 }
7703         return 0;
7704 }
7705
7706 char *
7707 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7708 {   // [HGM] book: this routine intercepts moves to simulate book replies
7709     char *bookHit = NULL;
7710
7711     //first determine if the incoming move brings opponent into his book
7712     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7713         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7714     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7715     if(bookHit != NULL && !cps->bookSuspend) {
7716         // make sure opponent is not going to reply after receiving move to book position
7717         SendToProgram("force\n", cps);
7718         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7719     }
7720     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7721     // now arrange restart after book miss
7722     if(bookHit) {
7723         // after a book hit we never send 'go', and the code after the call to this routine
7724         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7725         char buf[MSG_SIZ], *move = bookHit;
7726         if(cps->useSAN) {
7727             int fromX, fromY, toX, toY;
7728             char promoChar;
7729             ChessMove moveType;
7730             move = buf + 30;
7731             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7732                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7733                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7734                                     PosFlags(forwardMostMove),
7735                                     fromY, fromX, toY, toX, promoChar, move);
7736             } else {
7737                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7738                 bookHit = NULL;
7739             }
7740         }
7741         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7742         SendToProgram(buf, cps);
7743         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7744     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7745         SendToProgram("go\n", cps);
7746         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7747     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7748         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7749             SendToProgram("go\n", cps);
7750         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7751     }
7752     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7753 }
7754
7755 char *savedMessage;
7756 ChessProgramState *savedState;
7757 void
7758 DeferredBookMove (void)
7759 {
7760         if(savedState->lastPing != savedState->lastPong)
7761                     ScheduleDelayedEvent(DeferredBookMove, 10);
7762         else
7763         HandleMachineMove(savedMessage, savedState);
7764 }
7765
7766 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7767
7768 void
7769 HandleMachineMove (char *message, ChessProgramState *cps)
7770 {
7771     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7772     char realname[MSG_SIZ];
7773     int fromX, fromY, toX, toY;
7774     ChessMove moveType;
7775     char promoChar;
7776     char *p, *pv=buf1;
7777     int machineWhite;
7778     char *bookHit;
7779
7780     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7781         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7782         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7783             DisplayError(_("Invalid pairing from pairing engine"), 0);
7784             return;
7785         }
7786         pairingReceived = 1;
7787         NextMatchGame();
7788         return; // Skim the pairing messages here.
7789     }
7790
7791     cps->userError = 0;
7792
7793 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7794     /*
7795      * Kludge to ignore BEL characters
7796      */
7797     while (*message == '\007') message++;
7798
7799     /*
7800      * [HGM] engine debug message: ignore lines starting with '#' character
7801      */
7802     if(cps->debug && *message == '#') return;
7803
7804     /*
7805      * Look for book output
7806      */
7807     if (cps == &first && bookRequested) {
7808         if (message[0] == '\t' || message[0] == ' ') {
7809             /* Part of the book output is here; append it */
7810             strcat(bookOutput, message);
7811             strcat(bookOutput, "  \n");
7812             return;
7813         } else if (bookOutput[0] != NULLCHAR) {
7814             /* All of book output has arrived; display it */
7815             char *p = bookOutput;
7816             while (*p != NULLCHAR) {
7817                 if (*p == '\t') *p = ' ';
7818                 p++;
7819             }
7820             DisplayInformation(bookOutput);
7821             bookRequested = FALSE;
7822             /* Fall through to parse the current output */
7823         }
7824     }
7825
7826     /*
7827      * Look for machine move.
7828      */
7829     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7830         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7831     {
7832         /* This method is only useful on engines that support ping */
7833         if (cps->lastPing != cps->lastPong) {
7834           if (gameMode == BeginningOfGame) {
7835             /* Extra move from before last new; ignore */
7836             if (appData.debugMode) {
7837                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7838             }
7839           } else {
7840             if (appData.debugMode) {
7841                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7842                         cps->which, gameMode);
7843             }
7844
7845             SendToProgram("undo\n", cps);
7846           }
7847           return;
7848         }
7849
7850         switch (gameMode) {
7851           case BeginningOfGame:
7852             /* Extra move from before last reset; ignore */
7853             if (appData.debugMode) {
7854                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7855             }
7856             return;
7857
7858           case EndOfGame:
7859           case IcsIdle:
7860           default:
7861             /* Extra move after we tried to stop.  The mode test is
7862                not a reliable way of detecting this problem, but it's
7863                the best we can do on engines that don't support ping.
7864             */
7865             if (appData.debugMode) {
7866                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7867                         cps->which, gameMode);
7868             }
7869             SendToProgram("undo\n", cps);
7870             return;
7871
7872           case MachinePlaysWhite:
7873           case IcsPlayingWhite:
7874             machineWhite = TRUE;
7875             break;
7876
7877           case MachinePlaysBlack:
7878           case IcsPlayingBlack:
7879             machineWhite = FALSE;
7880             break;
7881
7882           case TwoMachinesPlay:
7883             machineWhite = (cps->twoMachinesColor[0] == 'w');
7884             break;
7885         }
7886         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7887             if (appData.debugMode) {
7888                 fprintf(debugFP,
7889                         "Ignoring move out of turn by %s, gameMode %d"
7890                         ", forwardMost %d\n",
7891                         cps->which, gameMode, forwardMostMove);
7892             }
7893             return;
7894         }
7895
7896         if(cps->alphaRank) AlphaRank(machineMove, 4);
7897         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7898                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7899             /* Machine move could not be parsed; ignore it. */
7900           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7901                     machineMove, _(cps->which));
7902             DisplayError(buf1, 0);
7903             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7904                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7905             if (gameMode == TwoMachinesPlay) {
7906               GameEnds(machineWhite ? BlackWins : WhiteWins,
7907                        buf1, GE_XBOARD);
7908             }
7909             return;
7910         }
7911
7912         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7913         /* So we have to redo legality test with true e.p. status here,  */
7914         /* to make sure an illegal e.p. capture does not slip through,   */
7915         /* to cause a forfeit on a justified illegal-move complaint      */
7916         /* of the opponent.                                              */
7917         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7918            ChessMove moveType;
7919            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7920                              fromY, fromX, toY, toX, promoChar);
7921             if(moveType == IllegalMove) {
7922               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7923                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7924                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7925                            buf1, GE_XBOARD);
7926                 return;
7927            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7928            /* [HGM] Kludge to handle engines that send FRC-style castling
7929               when they shouldn't (like TSCP-Gothic) */
7930            switch(moveType) {
7931              case WhiteASideCastleFR:
7932              case BlackASideCastleFR:
7933                toX+=2;
7934                currentMoveString[2]++;
7935                break;
7936              case WhiteHSideCastleFR:
7937              case BlackHSideCastleFR:
7938                toX--;
7939                currentMoveString[2]--;
7940                break;
7941              default: ; // nothing to do, but suppresses warning of pedantic compilers
7942            }
7943         }
7944         hintRequested = FALSE;
7945         lastHint[0] = NULLCHAR;
7946         bookRequested = FALSE;
7947         /* Program may be pondering now */
7948         cps->maybeThinking = TRUE;
7949         if (cps->sendTime == 2) cps->sendTime = 1;
7950         if (cps->offeredDraw) cps->offeredDraw--;
7951
7952         /* [AS] Save move info*/
7953         pvInfoList[ forwardMostMove ].score = programStats.score;
7954         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7955         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7956
7957         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7958
7959         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7960         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7961             int count = 0;
7962
7963             while( count < adjudicateLossPlies ) {
7964                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7965
7966                 if( count & 1 ) {
7967                     score = -score; /* Flip score for winning side */
7968                 }
7969
7970                 if( score > adjudicateLossThreshold ) {
7971                     break;
7972                 }
7973
7974                 count++;
7975             }
7976
7977             if( count >= adjudicateLossPlies ) {
7978                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7979
7980                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7981                     "Xboard adjudication",
7982                     GE_XBOARD );
7983
7984                 return;
7985             }
7986         }
7987
7988         if(Adjudicate(cps)) {
7989             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7990             return; // [HGM] adjudicate: for all automatic game ends
7991         }
7992
7993 #if ZIPPY
7994         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7995             first.initDone) {
7996           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7997                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7998                 SendToICS("draw ");
7999                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8000           }
8001           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8002           ics_user_moved = 1;
8003           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8004                 char buf[3*MSG_SIZ];
8005
8006                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8007                         programStats.score / 100.,
8008                         programStats.depth,
8009                         programStats.time / 100.,
8010                         (unsigned int)programStats.nodes,
8011                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8012                         programStats.movelist);
8013                 SendToICS(buf);
8014 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8015           }
8016         }
8017 #endif
8018
8019         /* [AS] Clear stats for next move */
8020         ClearProgramStats();
8021         thinkOutput[0] = NULLCHAR;
8022         hiddenThinkOutputState = 0;
8023
8024         bookHit = NULL;
8025         if (gameMode == TwoMachinesPlay) {
8026             /* [HGM] relaying draw offers moved to after reception of move */
8027             /* and interpreting offer as claim if it brings draw condition */
8028             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8029                 SendToProgram("draw\n", cps->other);
8030             }
8031             if (cps->other->sendTime) {
8032                 SendTimeRemaining(cps->other,
8033                                   cps->other->twoMachinesColor[0] == 'w');
8034             }
8035             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8036             if (firstMove && !bookHit) {
8037                 firstMove = FALSE;
8038                 if (cps->other->useColors) {
8039                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8040                 }
8041                 SendToProgram("go\n", cps->other);
8042             }
8043             cps->other->maybeThinking = TRUE;
8044         }
8045
8046         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8047
8048         if (!pausing && appData.ringBellAfterMoves) {
8049             RingBell();
8050         }
8051
8052         /*
8053          * Reenable menu items that were disabled while
8054          * machine was thinking
8055          */
8056         if (gameMode != TwoMachinesPlay)
8057             SetUserThinkingEnables();
8058
8059         // [HGM] book: after book hit opponent has received move and is now in force mode
8060         // force the book reply into it, and then fake that it outputted this move by jumping
8061         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8062         if(bookHit) {
8063                 static char bookMove[MSG_SIZ]; // a bit generous?
8064
8065                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8066                 strcat(bookMove, bookHit);
8067                 message = bookMove;
8068                 cps = cps->other;
8069                 programStats.nodes = programStats.depth = programStats.time =
8070                 programStats.score = programStats.got_only_move = 0;
8071                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8072
8073                 if(cps->lastPing != cps->lastPong) {
8074                     savedMessage = message; // args for deferred call
8075                     savedState = cps;
8076                     ScheduleDelayedEvent(DeferredBookMove, 10);
8077                     return;
8078                 }
8079                 goto FakeBookMove;
8080         }
8081
8082         return;
8083     }
8084
8085     /* Set special modes for chess engines.  Later something general
8086      *  could be added here; for now there is just one kludge feature,
8087      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8088      *  when "xboard" is given as an interactive command.
8089      */
8090     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8091         cps->useSigint = FALSE;
8092         cps->useSigterm = FALSE;
8093     }
8094     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8095       ParseFeatures(message+8, cps);
8096       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8097     }
8098
8099     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8100                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8101       int dummy, s=6; char buf[MSG_SIZ];
8102       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8103       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8104       if(startedFromSetupPosition) return;
8105       ParseFEN(boards[0], &dummy, message+s);
8106       DrawPosition(TRUE, boards[0]);
8107       startedFromSetupPosition = TRUE;
8108       return;
8109     }
8110     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8111      * want this, I was asked to put it in, and obliged.
8112      */
8113     if (!strncmp(message, "setboard ", 9)) {
8114         Board initial_position;
8115
8116         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8117
8118         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8119             DisplayError(_("Bad FEN received from engine"), 0);
8120             return ;
8121         } else {
8122            Reset(TRUE, FALSE);
8123            CopyBoard(boards[0], initial_position);
8124            initialRulePlies = FENrulePlies;
8125            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8126            else gameMode = MachinePlaysBlack;
8127            DrawPosition(FALSE, boards[currentMove]);
8128         }
8129         return;
8130     }
8131
8132     /*
8133      * Look for communication commands
8134      */
8135     if (!strncmp(message, "telluser ", 9)) {
8136         if(message[9] == '\\' && message[10] == '\\')
8137             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8138         PlayTellSound();
8139         DisplayNote(message + 9);
8140         return;
8141     }
8142     if (!strncmp(message, "tellusererror ", 14)) {
8143         cps->userError = 1;
8144         if(message[14] == '\\' && message[15] == '\\')
8145             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8146         PlayTellSound();
8147         DisplayError(message + 14, 0);
8148         return;
8149     }
8150     if (!strncmp(message, "tellopponent ", 13)) {
8151       if (appData.icsActive) {
8152         if (loggedOn) {
8153           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8154           SendToICS(buf1);
8155         }
8156       } else {
8157         DisplayNote(message + 13);
8158       }
8159       return;
8160     }
8161     if (!strncmp(message, "tellothers ", 11)) {
8162       if (appData.icsActive) {
8163         if (loggedOn) {
8164           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8165           SendToICS(buf1);
8166         }
8167       }
8168       return;
8169     }
8170     if (!strncmp(message, "tellall ", 8)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8174           SendToICS(buf1);
8175         }
8176       } else {
8177         DisplayNote(message + 8);
8178       }
8179       return;
8180     }
8181     if (strncmp(message, "warning", 7) == 0) {
8182         /* Undocumented feature, use tellusererror in new code */
8183         DisplayError(message, 0);
8184         return;
8185     }
8186     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8187         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8188         strcat(realname, " query");
8189         AskQuestion(realname, buf2, buf1, cps->pr);
8190         return;
8191     }
8192     /* Commands from the engine directly to ICS.  We don't allow these to be
8193      *  sent until we are logged on. Crafty kibitzes have been known to
8194      *  interfere with the login process.
8195      */
8196     if (loggedOn) {
8197         if (!strncmp(message, "tellics ", 8)) {
8198             SendToICS(message + 8);
8199             SendToICS("\n");
8200             return;
8201         }
8202         if (!strncmp(message, "tellicsnoalias ", 15)) {
8203             SendToICS(ics_prefix);
8204             SendToICS(message + 15);
8205             SendToICS("\n");
8206             return;
8207         }
8208         /* The following are for backward compatibility only */
8209         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8210             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8211             SendToICS(ics_prefix);
8212             SendToICS(message);
8213             SendToICS("\n");
8214             return;
8215         }
8216     }
8217     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8218         return;
8219     }
8220     /*
8221      * If the move is illegal, cancel it and redraw the board.
8222      * Also deal with other error cases.  Matching is rather loose
8223      * here to accommodate engines written before the spec.
8224      */
8225     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8226         strncmp(message, "Error", 5) == 0) {
8227         if (StrStr(message, "name") ||
8228             StrStr(message, "rating") || StrStr(message, "?") ||
8229             StrStr(message, "result") || StrStr(message, "board") ||
8230             StrStr(message, "bk") || StrStr(message, "computer") ||
8231             StrStr(message, "variant") || StrStr(message, "hint") ||
8232             StrStr(message, "random") || StrStr(message, "depth") ||
8233             StrStr(message, "accepted")) {
8234             return;
8235         }
8236         if (StrStr(message, "protover")) {
8237           /* Program is responding to input, so it's apparently done
8238              initializing, and this error message indicates it is
8239              protocol version 1.  So we don't need to wait any longer
8240              for it to initialize and send feature commands. */
8241           FeatureDone(cps, 1);
8242           cps->protocolVersion = 1;
8243           return;
8244         }
8245         cps->maybeThinking = FALSE;
8246
8247         if (StrStr(message, "draw")) {
8248             /* Program doesn't have "draw" command */
8249             cps->sendDrawOffers = 0;
8250             return;
8251         }
8252         if (cps->sendTime != 1 &&
8253             (StrStr(message, "time") || StrStr(message, "otim"))) {
8254           /* Program apparently doesn't have "time" or "otim" command */
8255           cps->sendTime = 0;
8256           return;
8257         }
8258         if (StrStr(message, "analyze")) {
8259             cps->analysisSupport = FALSE;
8260             cps->analyzing = FALSE;
8261 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8262             EditGameEvent(); // [HGM] try to preserve loaded game
8263             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8264             DisplayError(buf2, 0);
8265             return;
8266         }
8267         if (StrStr(message, "(no matching move)st")) {
8268           /* Special kludge for GNU Chess 4 only */
8269           cps->stKludge = TRUE;
8270           SendTimeControl(cps, movesPerSession, timeControl,
8271                           timeIncrement, appData.searchDepth,
8272                           searchTime);
8273           return;
8274         }
8275         if (StrStr(message, "(no matching move)sd")) {
8276           /* Special kludge for GNU Chess 4 only */
8277           cps->sdKludge = TRUE;
8278           SendTimeControl(cps, movesPerSession, timeControl,
8279                           timeIncrement, appData.searchDepth,
8280                           searchTime);
8281           return;
8282         }
8283         if (!StrStr(message, "llegal")) {
8284             return;
8285         }
8286         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8287             gameMode == IcsIdle) return;
8288         if (forwardMostMove <= backwardMostMove) return;
8289         if (pausing) PauseEvent();
8290       if(appData.forceIllegal) {
8291             // [HGM] illegal: machine refused move; force position after move into it
8292           SendToProgram("force\n", cps);
8293           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8294                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8295                 // when black is to move, while there might be nothing on a2 or black
8296                 // might already have the move. So send the board as if white has the move.
8297                 // But first we must change the stm of the engine, as it refused the last move
8298                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8299                 if(WhiteOnMove(forwardMostMove)) {
8300                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8301                     SendBoard(cps, forwardMostMove); // kludgeless board
8302                 } else {
8303                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8304                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8305                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8306                 }
8307           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8308             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8309                  gameMode == TwoMachinesPlay)
8310               SendToProgram("go\n", cps);
8311             return;
8312       } else
8313         if (gameMode == PlayFromGameFile) {
8314             /* Stop reading this game file */
8315             gameMode = EditGame;
8316             ModeHighlight();
8317         }
8318         /* [HGM] illegal-move claim should forfeit game when Xboard */
8319         /* only passes fully legal moves                            */
8320         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8321             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8322                                 "False illegal-move claim", GE_XBOARD );
8323             return; // do not take back move we tested as valid
8324         }
8325         currentMove = forwardMostMove-1;
8326         DisplayMove(currentMove-1); /* before DisplayMoveError */
8327         SwitchClocks(forwardMostMove-1); // [HGM] race
8328         DisplayBothClocks();
8329         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8330                 parseList[currentMove], _(cps->which));
8331         DisplayMoveError(buf1);
8332         DrawPosition(FALSE, boards[currentMove]);
8333
8334         SetUserThinkingEnables();
8335         return;
8336     }
8337     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8338         /* Program has a broken "time" command that
8339            outputs a string not ending in newline.
8340            Don't use it. */
8341         cps->sendTime = 0;
8342     }
8343
8344     /*
8345      * If chess program startup fails, exit with an error message.
8346      * Attempts to recover here are futile.
8347      */
8348     if ((StrStr(message, "unknown host") != NULL)
8349         || (StrStr(message, "No remote directory") != NULL)
8350         || (StrStr(message, "not found") != NULL)
8351         || (StrStr(message, "No such file") != NULL)
8352         || (StrStr(message, "can't alloc") != NULL)
8353         || (StrStr(message, "Permission denied") != NULL)) {
8354
8355         cps->maybeThinking = FALSE;
8356         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8357                 _(cps->which), cps->program, cps->host, message);
8358         RemoveInputSource(cps->isr);
8359         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8360             if(cps == &first) appData.noChessProgram = TRUE;
8361             DisplayError(buf1, 0);
8362         }
8363         return;
8364     }
8365
8366     /*
8367      * Look for hint output
8368      */
8369     if (sscanf(message, "Hint: %s", buf1) == 1) {
8370         if (cps == &first && hintRequested) {
8371             hintRequested = FALSE;
8372             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8373                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8374                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8375                                     PosFlags(forwardMostMove),
8376                                     fromY, fromX, toY, toX, promoChar, buf1);
8377                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8378                 DisplayInformation(buf2);
8379             } else {
8380                 /* Hint move could not be parsed!? */
8381               snprintf(buf2, sizeof(buf2),
8382                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8383                         buf1, _(cps->which));
8384                 DisplayError(buf2, 0);
8385             }
8386         } else {
8387           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8388         }
8389         return;
8390     }
8391
8392     /*
8393      * Ignore other messages if game is not in progress
8394      */
8395     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8396         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8397
8398     /*
8399      * look for win, lose, draw, or draw offer
8400      */
8401     if (strncmp(message, "1-0", 3) == 0) {
8402         char *p, *q, *r = "";
8403         p = strchr(message, '{');
8404         if (p) {
8405             q = strchr(p, '}');
8406             if (q) {
8407                 *q = NULLCHAR;
8408                 r = p + 1;
8409             }
8410         }
8411         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8412         return;
8413     } else if (strncmp(message, "0-1", 3) == 0) {
8414         char *p, *q, *r = "";
8415         p = strchr(message, '{');
8416         if (p) {
8417             q = strchr(p, '}');
8418             if (q) {
8419                 *q = NULLCHAR;
8420                 r = p + 1;
8421             }
8422         }
8423         /* Kludge for Arasan 4.1 bug */
8424         if (strcmp(r, "Black resigns") == 0) {
8425             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8426             return;
8427         }
8428         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8429         return;
8430     } else if (strncmp(message, "1/2", 3) == 0) {
8431         char *p, *q, *r = "";
8432         p = strchr(message, '{');
8433         if (p) {
8434             q = strchr(p, '}');
8435             if (q) {
8436                 *q = NULLCHAR;
8437                 r = p + 1;
8438             }
8439         }
8440
8441         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8442         return;
8443
8444     } else if (strncmp(message, "White resign", 12) == 0) {
8445         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8446         return;
8447     } else if (strncmp(message, "Black resign", 12) == 0) {
8448         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8449         return;
8450     } else if (strncmp(message, "White matches", 13) == 0 ||
8451                strncmp(message, "Black matches", 13) == 0   ) {
8452         /* [HGM] ignore GNUShogi noises */
8453         return;
8454     } else if (strncmp(message, "White", 5) == 0 &&
8455                message[5] != '(' &&
8456                StrStr(message, "Black") == NULL) {
8457         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8458         return;
8459     } else if (strncmp(message, "Black", 5) == 0 &&
8460                message[5] != '(') {
8461         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8462         return;
8463     } else if (strcmp(message, "resign") == 0 ||
8464                strcmp(message, "computer resigns") == 0) {
8465         switch (gameMode) {
8466           case MachinePlaysBlack:
8467           case IcsPlayingBlack:
8468             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8469             break;
8470           case MachinePlaysWhite:
8471           case IcsPlayingWhite:
8472             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8473             break;
8474           case TwoMachinesPlay:
8475             if (cps->twoMachinesColor[0] == 'w')
8476               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8477             else
8478               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8479             break;
8480           default:
8481             /* can't happen */
8482             break;
8483         }
8484         return;
8485     } else if (strncmp(message, "opponent mates", 14) == 0) {
8486         switch (gameMode) {
8487           case MachinePlaysBlack:
8488           case IcsPlayingBlack:
8489             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8490             break;
8491           case MachinePlaysWhite:
8492           case IcsPlayingWhite:
8493             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8494             break;
8495           case TwoMachinesPlay:
8496             if (cps->twoMachinesColor[0] == 'w')
8497               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8498             else
8499               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8500             break;
8501           default:
8502             /* can't happen */
8503             break;
8504         }
8505         return;
8506     } else if (strncmp(message, "computer mates", 14) == 0) {
8507         switch (gameMode) {
8508           case MachinePlaysBlack:
8509           case IcsPlayingBlack:
8510             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8511             break;
8512           case MachinePlaysWhite:
8513           case IcsPlayingWhite:
8514             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8515             break;
8516           case TwoMachinesPlay:
8517             if (cps->twoMachinesColor[0] == 'w')
8518               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8519             else
8520               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8521             break;
8522           default:
8523             /* can't happen */
8524             break;
8525         }
8526         return;
8527     } else if (strncmp(message, "checkmate", 9) == 0) {
8528         if (WhiteOnMove(forwardMostMove)) {
8529             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8530         } else {
8531             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8532         }
8533         return;
8534     } else if (strstr(message, "Draw") != NULL ||
8535                strstr(message, "game is a draw") != NULL) {
8536         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8537         return;
8538     } else if (strstr(message, "offer") != NULL &&
8539                strstr(message, "draw") != NULL) {
8540 #if ZIPPY
8541         if (appData.zippyPlay && first.initDone) {
8542             /* Relay offer to ICS */
8543             SendToICS(ics_prefix);
8544             SendToICS("draw\n");
8545         }
8546 #endif
8547         cps->offeredDraw = 2; /* valid until this engine moves twice */
8548         if (gameMode == TwoMachinesPlay) {
8549             if (cps->other->offeredDraw) {
8550                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8551             /* [HGM] in two-machine mode we delay relaying draw offer      */
8552             /* until after we also have move, to see if it is really claim */
8553             }
8554         } else if (gameMode == MachinePlaysWhite ||
8555                    gameMode == MachinePlaysBlack) {
8556           if (userOfferedDraw) {
8557             DisplayInformation(_("Machine accepts your draw offer"));
8558             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8559           } else {
8560             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8561           }
8562         }
8563     }
8564
8565
8566     /*
8567      * Look for thinking output
8568      */
8569     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8570           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8571                                 ) {
8572         int plylev, mvleft, mvtot, curscore, time;
8573         char mvname[MOVE_LEN];
8574         u64 nodes; // [DM]
8575         char plyext;
8576         int ignore = FALSE;
8577         int prefixHint = FALSE;
8578         mvname[0] = NULLCHAR;
8579
8580         switch (gameMode) {
8581           case MachinePlaysBlack:
8582           case IcsPlayingBlack:
8583             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8584             break;
8585           case MachinePlaysWhite:
8586           case IcsPlayingWhite:
8587             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8588             break;
8589           case AnalyzeMode:
8590           case AnalyzeFile:
8591             break;
8592           case IcsObserving: /* [DM] icsEngineAnalyze */
8593             if (!appData.icsEngineAnalyze) ignore = TRUE;
8594             break;
8595           case TwoMachinesPlay:
8596             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8597                 ignore = TRUE;
8598             }
8599             break;
8600           default:
8601             ignore = TRUE;
8602             break;
8603         }
8604
8605         if (!ignore) {
8606             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8607             buf1[0] = NULLCHAR;
8608             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8609                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8610
8611                 if (plyext != ' ' && plyext != '\t') {
8612                     time *= 100;
8613                 }
8614
8615                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8616                 if( cps->scoreIsAbsolute &&
8617                     ( gameMode == MachinePlaysBlack ||
8618                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8619                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8620                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8621                      !WhiteOnMove(currentMove)
8622                     ) )
8623                 {
8624                     curscore = -curscore;
8625                 }
8626
8627                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8628
8629                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8630                         char buf[MSG_SIZ];
8631                         FILE *f;
8632                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8633                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8634                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8635                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8636                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8637                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8638                                 fclose(f);
8639                         } else DisplayError(_("failed writing PV"), 0);
8640                 }
8641
8642                 tempStats.depth = plylev;
8643                 tempStats.nodes = nodes;
8644                 tempStats.time = time;
8645                 tempStats.score = curscore;
8646                 tempStats.got_only_move = 0;
8647
8648                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8649                         int ticklen;
8650
8651                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8652                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8653                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8654                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8655                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8656                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8657                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8658                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8659                 }
8660
8661                 /* Buffer overflow protection */
8662                 if (pv[0] != NULLCHAR) {
8663                     if (strlen(pv) >= sizeof(tempStats.movelist)
8664                         && appData.debugMode) {
8665                         fprintf(debugFP,
8666                                 "PV is too long; using the first %u bytes.\n",
8667                                 (unsigned) sizeof(tempStats.movelist) - 1);
8668                     }
8669
8670                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8671                 } else {
8672                     sprintf(tempStats.movelist, " no PV\n");
8673                 }
8674
8675                 if (tempStats.seen_stat) {
8676                     tempStats.ok_to_send = 1;
8677                 }
8678
8679                 if (strchr(tempStats.movelist, '(') != NULL) {
8680                     tempStats.line_is_book = 1;
8681                     tempStats.nr_moves = 0;
8682                     tempStats.moves_left = 0;
8683                 } else {
8684                     tempStats.line_is_book = 0;
8685                 }
8686
8687                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8688                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8689
8690                 SendProgramStatsToFrontend( cps, &tempStats );
8691
8692                 /*
8693                     [AS] Protect the thinkOutput buffer from overflow... this
8694                     is only useful if buf1 hasn't overflowed first!
8695                 */
8696                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8697                          plylev,
8698                          (gameMode == TwoMachinesPlay ?
8699                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8700                          ((double) curscore) / 100.0,
8701                          prefixHint ? lastHint : "",
8702                          prefixHint ? " " : "" );
8703
8704                 if( buf1[0] != NULLCHAR ) {
8705                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8706
8707                     if( strlen(pv) > max_len ) {
8708                         if( appData.debugMode) {
8709                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8710                         }
8711                         pv[max_len+1] = '\0';
8712                     }
8713
8714                     strcat( thinkOutput, pv);
8715                 }
8716
8717                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8718                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8719                     DisplayMove(currentMove - 1);
8720                 }
8721                 return;
8722
8723             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8724                 /* crafty (9.25+) says "(only move) <move>"
8725                  * if there is only 1 legal move
8726                  */
8727                 sscanf(p, "(only move) %s", buf1);
8728                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8729                 sprintf(programStats.movelist, "%s (only move)", buf1);
8730                 programStats.depth = 1;
8731                 programStats.nr_moves = 1;
8732                 programStats.moves_left = 1;
8733                 programStats.nodes = 1;
8734                 programStats.time = 1;
8735                 programStats.got_only_move = 1;
8736
8737                 /* Not really, but we also use this member to
8738                    mean "line isn't going to change" (Crafty
8739                    isn't searching, so stats won't change) */
8740                 programStats.line_is_book = 1;
8741
8742                 SendProgramStatsToFrontend( cps, &programStats );
8743
8744                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8745                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8746                     DisplayMove(currentMove - 1);
8747                 }
8748                 return;
8749             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8750                               &time, &nodes, &plylev, &mvleft,
8751                               &mvtot, mvname) >= 5) {
8752                 /* The stat01: line is from Crafty (9.29+) in response
8753                    to the "." command */
8754                 programStats.seen_stat = 1;
8755                 cps->maybeThinking = TRUE;
8756
8757                 if (programStats.got_only_move || !appData.periodicUpdates)
8758                   return;
8759
8760                 programStats.depth = plylev;
8761                 programStats.time = time;
8762                 programStats.nodes = nodes;
8763                 programStats.moves_left = mvleft;
8764                 programStats.nr_moves = mvtot;
8765                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8766                 programStats.ok_to_send = 1;
8767                 programStats.movelist[0] = '\0';
8768
8769                 SendProgramStatsToFrontend( cps, &programStats );
8770
8771                 return;
8772
8773             } else if (strncmp(message,"++",2) == 0) {
8774                 /* Crafty 9.29+ outputs this */
8775                 programStats.got_fail = 2;
8776                 return;
8777
8778             } else if (strncmp(message,"--",2) == 0) {
8779                 /* Crafty 9.29+ outputs this */
8780                 programStats.got_fail = 1;
8781                 return;
8782
8783             } else if (thinkOutput[0] != NULLCHAR &&
8784                        strncmp(message, "    ", 4) == 0) {
8785                 unsigned message_len;
8786
8787                 p = message;
8788                 while (*p && *p == ' ') p++;
8789
8790                 message_len = strlen( p );
8791
8792                 /* [AS] Avoid buffer overflow */
8793                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8794                     strcat(thinkOutput, " ");
8795                     strcat(thinkOutput, p);
8796                 }
8797
8798                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8799                     strcat(programStats.movelist, " ");
8800                     strcat(programStats.movelist, p);
8801                 }
8802
8803                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8804                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8805                     DisplayMove(currentMove - 1);
8806                 }
8807                 return;
8808             }
8809         }
8810         else {
8811             buf1[0] = NULLCHAR;
8812
8813             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8814                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8815             {
8816                 ChessProgramStats cpstats;
8817
8818                 if (plyext != ' ' && plyext != '\t') {
8819                     time *= 100;
8820                 }
8821
8822                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8823                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8824                     curscore = -curscore;
8825                 }
8826
8827                 cpstats.depth = plylev;
8828                 cpstats.nodes = nodes;
8829                 cpstats.time = time;
8830                 cpstats.score = curscore;
8831                 cpstats.got_only_move = 0;
8832                 cpstats.movelist[0] = '\0';
8833
8834                 if (buf1[0] != NULLCHAR) {
8835                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8836                 }
8837
8838                 cpstats.ok_to_send = 0;
8839                 cpstats.line_is_book = 0;
8840                 cpstats.nr_moves = 0;
8841                 cpstats.moves_left = 0;
8842
8843                 SendProgramStatsToFrontend( cps, &cpstats );
8844             }
8845         }
8846     }
8847 }
8848
8849
8850 /* Parse a game score from the character string "game", and
8851    record it as the history of the current game.  The game
8852    score is NOT assumed to start from the standard position.
8853    The display is not updated in any way.
8854    */
8855 void
8856 ParseGameHistory (char *game)
8857 {
8858     ChessMove moveType;
8859     int fromX, fromY, toX, toY, boardIndex;
8860     char promoChar;
8861     char *p, *q;
8862     char buf[MSG_SIZ];
8863
8864     if (appData.debugMode)
8865       fprintf(debugFP, "Parsing game history: %s\n", game);
8866
8867     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8868     gameInfo.site = StrSave(appData.icsHost);
8869     gameInfo.date = PGNDate();
8870     gameInfo.round = StrSave("-");
8871
8872     /* Parse out names of players */
8873     while (*game == ' ') game++;
8874     p = buf;
8875     while (*game != ' ') *p++ = *game++;
8876     *p = NULLCHAR;
8877     gameInfo.white = StrSave(buf);
8878     while (*game == ' ') game++;
8879     p = buf;
8880     while (*game != ' ' && *game != '\n') *p++ = *game++;
8881     *p = NULLCHAR;
8882     gameInfo.black = StrSave(buf);
8883
8884     /* Parse moves */
8885     boardIndex = blackPlaysFirst ? 1 : 0;
8886     yynewstr(game);
8887     for (;;) {
8888         yyboardindex = boardIndex;
8889         moveType = (ChessMove) Myylex();
8890         switch (moveType) {
8891           case IllegalMove:             /* maybe suicide chess, etc. */
8892   if (appData.debugMode) {
8893     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8894     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8895     setbuf(debugFP, NULL);
8896   }
8897           case WhitePromotion:
8898           case BlackPromotion:
8899           case WhiteNonPromotion:
8900           case BlackNonPromotion:
8901           case NormalMove:
8902           case WhiteCapturesEnPassant:
8903           case BlackCapturesEnPassant:
8904           case WhiteKingSideCastle:
8905           case WhiteQueenSideCastle:
8906           case BlackKingSideCastle:
8907           case BlackQueenSideCastle:
8908           case WhiteKingSideCastleWild:
8909           case WhiteQueenSideCastleWild:
8910           case BlackKingSideCastleWild:
8911           case BlackQueenSideCastleWild:
8912           /* PUSH Fabien */
8913           case WhiteHSideCastleFR:
8914           case WhiteASideCastleFR:
8915           case BlackHSideCastleFR:
8916           case BlackASideCastleFR:
8917           /* POP Fabien */
8918             fromX = currentMoveString[0] - AAA;
8919             fromY = currentMoveString[1] - ONE;
8920             toX = currentMoveString[2] - AAA;
8921             toY = currentMoveString[3] - ONE;
8922             promoChar = currentMoveString[4];
8923             break;
8924           case WhiteDrop:
8925           case BlackDrop:
8926             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8927             fromX = moveType == WhiteDrop ?
8928               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8929             (int) CharToPiece(ToLower(currentMoveString[0]));
8930             fromY = DROP_RANK;
8931             toX = currentMoveString[2] - AAA;
8932             toY = currentMoveString[3] - ONE;
8933             promoChar = NULLCHAR;
8934             break;
8935           case AmbiguousMove:
8936             /* bug? */
8937             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8938   if (appData.debugMode) {
8939     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8940     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8941     setbuf(debugFP, NULL);
8942   }
8943             DisplayError(buf, 0);
8944             return;
8945           case ImpossibleMove:
8946             /* bug? */
8947             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8948   if (appData.debugMode) {
8949     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8950     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8951     setbuf(debugFP, NULL);
8952   }
8953             DisplayError(buf, 0);
8954             return;
8955           case EndOfFile:
8956             if (boardIndex < backwardMostMove) {
8957                 /* Oops, gap.  How did that happen? */
8958                 DisplayError(_("Gap in move list"), 0);
8959                 return;
8960             }
8961             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8962             if (boardIndex > forwardMostMove) {
8963                 forwardMostMove = boardIndex;
8964             }
8965             return;
8966           case ElapsedTime:
8967             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8968                 strcat(parseList[boardIndex-1], " ");
8969                 strcat(parseList[boardIndex-1], yy_text);
8970             }
8971             continue;
8972           case Comment:
8973           case PGNTag:
8974           case NAG:
8975           default:
8976             /* ignore */
8977             continue;
8978           case WhiteWins:
8979           case BlackWins:
8980           case GameIsDrawn:
8981           case GameUnfinished:
8982             if (gameMode == IcsExamining) {
8983                 if (boardIndex < backwardMostMove) {
8984                     /* Oops, gap.  How did that happen? */
8985                     return;
8986                 }
8987                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8988                 return;
8989             }
8990             gameInfo.result = moveType;
8991             p = strchr(yy_text, '{');
8992             if (p == NULL) p = strchr(yy_text, '(');
8993             if (p == NULL) {
8994                 p = yy_text;
8995                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8996             } else {
8997                 q = strchr(p, *p == '{' ? '}' : ')');
8998                 if (q != NULL) *q = NULLCHAR;
8999                 p++;
9000             }
9001             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9002             gameInfo.resultDetails = StrSave(p);
9003             continue;
9004         }
9005         if (boardIndex >= forwardMostMove &&
9006             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9007             backwardMostMove = blackPlaysFirst ? 1 : 0;
9008             return;
9009         }
9010         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9011                                  fromY, fromX, toY, toX, promoChar,
9012                                  parseList[boardIndex]);
9013         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9014         /* currentMoveString is set as a side-effect of yylex */
9015         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9016         strcat(moveList[boardIndex], "\n");
9017         boardIndex++;
9018         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9019         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9020           case MT_NONE:
9021           case MT_STALEMATE:
9022           default:
9023             break;
9024           case MT_CHECK:
9025             if(gameInfo.variant != VariantShogi)
9026                 strcat(parseList[boardIndex - 1], "+");
9027             break;
9028           case MT_CHECKMATE:
9029           case MT_STAINMATE:
9030             strcat(parseList[boardIndex - 1], "#");
9031             break;
9032         }
9033     }
9034 }
9035
9036
9037 /* Apply a move to the given board  */
9038 void
9039 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9040 {
9041   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9042   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9043
9044     /* [HGM] compute & store e.p. status and castling rights for new position */
9045     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9046
9047       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9048       oldEP = (signed char)board[EP_STATUS];
9049       board[EP_STATUS] = EP_NONE;
9050
9051   if (fromY == DROP_RANK) {
9052         /* must be first */
9053         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9054             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9055             return;
9056         }
9057         piece = board[toY][toX] = (ChessSquare) fromX;
9058   } else {
9059       int i;
9060
9061       if( board[toY][toX] != EmptySquare )
9062            board[EP_STATUS] = EP_CAPTURE;
9063
9064       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9065            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9066                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9067       } else
9068       if( board[fromY][fromX] == WhitePawn ) {
9069            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9070                board[EP_STATUS] = EP_PAWN_MOVE;
9071            if( toY-fromY==2) {
9072                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9073                         gameInfo.variant != VariantBerolina || toX < fromX)
9074                       board[EP_STATUS] = toX | berolina;
9075                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9076                         gameInfo.variant != VariantBerolina || toX > fromX)
9077                       board[EP_STATUS] = toX;
9078            }
9079       } else
9080       if( board[fromY][fromX] == BlackPawn ) {
9081            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9082                board[EP_STATUS] = EP_PAWN_MOVE;
9083            if( toY-fromY== -2) {
9084                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9085                         gameInfo.variant != VariantBerolina || toX < fromX)
9086                       board[EP_STATUS] = toX | berolina;
9087                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9088                         gameInfo.variant != VariantBerolina || toX > fromX)
9089                       board[EP_STATUS] = toX;
9090            }
9091        }
9092
9093        for(i=0; i<nrCastlingRights; i++) {
9094            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9095               board[CASTLING][i] == toX   && castlingRank[i] == toY
9096              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9097        }
9098
9099      if (fromX == toX && fromY == toY) return;
9100
9101      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9102      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9103      if(gameInfo.variant == VariantKnightmate)
9104          king += (int) WhiteUnicorn - (int) WhiteKing;
9105
9106     /* Code added by Tord: */
9107     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9108     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9109         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9110       board[fromY][fromX] = EmptySquare;
9111       board[toY][toX] = EmptySquare;
9112       if((toX > fromX) != (piece == WhiteRook)) {
9113         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9114       } else {
9115         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9116       }
9117     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9118                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9119       board[fromY][fromX] = EmptySquare;
9120       board[toY][toX] = EmptySquare;
9121       if((toX > fromX) != (piece == BlackRook)) {
9122         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9123       } else {
9124         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9125       }
9126     /* End of code added by Tord */
9127
9128     } else if (board[fromY][fromX] == king
9129         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9130         && toY == fromY && toX > fromX+1) {
9131         board[fromY][fromX] = EmptySquare;
9132         board[toY][toX] = king;
9133         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9134         board[fromY][BOARD_RGHT-1] = EmptySquare;
9135     } else if (board[fromY][fromX] == king
9136         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9137                && toY == fromY && toX < fromX-1) {
9138         board[fromY][fromX] = EmptySquare;
9139         board[toY][toX] = king;
9140         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9141         board[fromY][BOARD_LEFT] = EmptySquare;
9142     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9143                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9144                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9145                ) {
9146         /* white pawn promotion */
9147         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9148         if(gameInfo.variant==VariantBughouse ||
9149            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9150             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9151         board[fromY][fromX] = EmptySquare;
9152     } else if ((fromY >= BOARD_HEIGHT>>1)
9153                && (toX != fromX)
9154                && gameInfo.variant != VariantXiangqi
9155                && gameInfo.variant != VariantBerolina
9156                && (board[fromY][fromX] == WhitePawn)
9157                && (board[toY][toX] == EmptySquare)) {
9158         board[fromY][fromX] = EmptySquare;
9159         board[toY][toX] = WhitePawn;
9160         captured = board[toY - 1][toX];
9161         board[toY - 1][toX] = EmptySquare;
9162     } else if ((fromY == BOARD_HEIGHT-4)
9163                && (toX == fromX)
9164                && gameInfo.variant == VariantBerolina
9165                && (board[fromY][fromX] == WhitePawn)
9166                && (board[toY][toX] == EmptySquare)) {
9167         board[fromY][fromX] = EmptySquare;
9168         board[toY][toX] = WhitePawn;
9169         if(oldEP & EP_BEROLIN_A) {
9170                 captured = board[fromY][fromX-1];
9171                 board[fromY][fromX-1] = EmptySquare;
9172         }else{  captured = board[fromY][fromX+1];
9173                 board[fromY][fromX+1] = EmptySquare;
9174         }
9175     } else if (board[fromY][fromX] == king
9176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9177                && toY == fromY && toX > fromX+1) {
9178         board[fromY][fromX] = EmptySquare;
9179         board[toY][toX] = king;
9180         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9181         board[fromY][BOARD_RGHT-1] = EmptySquare;
9182     } else if (board[fromY][fromX] == king
9183         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9184                && toY == fromY && toX < fromX-1) {
9185         board[fromY][fromX] = EmptySquare;
9186         board[toY][toX] = king;
9187         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9188         board[fromY][BOARD_LEFT] = EmptySquare;
9189     } else if (fromY == 7 && fromX == 3
9190                && board[fromY][fromX] == BlackKing
9191                && toY == 7 && toX == 5) {
9192         board[fromY][fromX] = EmptySquare;
9193         board[toY][toX] = BlackKing;
9194         board[fromY][7] = EmptySquare;
9195         board[toY][4] = BlackRook;
9196     } else if (fromY == 7 && fromX == 3
9197                && board[fromY][fromX] == BlackKing
9198                && toY == 7 && toX == 1) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = BlackKing;
9201         board[fromY][0] = EmptySquare;
9202         board[toY][2] = BlackRook;
9203     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9204                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9205                && toY < promoRank && promoChar
9206                ) {
9207         /* black pawn promotion */
9208         board[toY][toX] = CharToPiece(ToLower(promoChar));
9209         if(gameInfo.variant==VariantBughouse ||
9210            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9211             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9212         board[fromY][fromX] = EmptySquare;
9213     } else if ((fromY < BOARD_HEIGHT>>1)
9214                && (toX != fromX)
9215                && gameInfo.variant != VariantXiangqi
9216                && gameInfo.variant != VariantBerolina
9217                && (board[fromY][fromX] == BlackPawn)
9218                && (board[toY][toX] == EmptySquare)) {
9219         board[fromY][fromX] = EmptySquare;
9220         board[toY][toX] = BlackPawn;
9221         captured = board[toY + 1][toX];
9222         board[toY + 1][toX] = EmptySquare;
9223     } else if ((fromY == 3)
9224                && (toX == fromX)
9225                && gameInfo.variant == VariantBerolina
9226                && (board[fromY][fromX] == BlackPawn)
9227                && (board[toY][toX] == EmptySquare)) {
9228         board[fromY][fromX] = EmptySquare;
9229         board[toY][toX] = BlackPawn;
9230         if(oldEP & EP_BEROLIN_A) {
9231                 captured = board[fromY][fromX-1];
9232                 board[fromY][fromX-1] = EmptySquare;
9233         }else{  captured = board[fromY][fromX+1];
9234                 board[fromY][fromX+1] = EmptySquare;
9235         }
9236     } else {
9237         board[toY][toX] = board[fromY][fromX];
9238         board[fromY][fromX] = EmptySquare;
9239     }
9240   }
9241
9242     if (gameInfo.holdingsWidth != 0) {
9243
9244       /* !!A lot more code needs to be written to support holdings  */
9245       /* [HGM] OK, so I have written it. Holdings are stored in the */
9246       /* penultimate board files, so they are automaticlly stored   */
9247       /* in the game history.                                       */
9248       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9249                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9250         /* Delete from holdings, by decreasing count */
9251         /* and erasing image if necessary            */
9252         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9253         if(p < (int) BlackPawn) { /* white drop */
9254              p -= (int)WhitePawn;
9255                  p = PieceToNumber((ChessSquare)p);
9256              if(p >= gameInfo.holdingsSize) p = 0;
9257              if(--board[p][BOARD_WIDTH-2] <= 0)
9258                   board[p][BOARD_WIDTH-1] = EmptySquare;
9259              if((int)board[p][BOARD_WIDTH-2] < 0)
9260                         board[p][BOARD_WIDTH-2] = 0;
9261         } else {                  /* black drop */
9262              p -= (int)BlackPawn;
9263                  p = PieceToNumber((ChessSquare)p);
9264              if(p >= gameInfo.holdingsSize) p = 0;
9265              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9266                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9267              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9268                         board[BOARD_HEIGHT-1-p][1] = 0;
9269         }
9270       }
9271       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9272           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9273         /* [HGM] holdings: Add to holdings, if holdings exist */
9274         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9275                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9276                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9277         }
9278         p = (int) captured;
9279         if (p >= (int) BlackPawn) {
9280           p -= (int)BlackPawn;
9281           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9282                   /* in Shogi restore piece to its original  first */
9283                   captured = (ChessSquare) (DEMOTED captured);
9284                   p = DEMOTED p;
9285           }
9286           p = PieceToNumber((ChessSquare)p);
9287           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9288           board[p][BOARD_WIDTH-2]++;
9289           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9290         } else {
9291           p -= (int)WhitePawn;
9292           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9293                   captured = (ChessSquare) (DEMOTED captured);
9294                   p = DEMOTED p;
9295           }
9296           p = PieceToNumber((ChessSquare)p);
9297           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9298           board[BOARD_HEIGHT-1-p][1]++;
9299           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9300         }
9301       }
9302     } else if (gameInfo.variant == VariantAtomic) {
9303       if (captured != EmptySquare) {
9304         int y, x;
9305         for (y = toY-1; y <= toY+1; y++) {
9306           for (x = toX-1; x <= toX+1; x++) {
9307             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9308                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9309               board[y][x] = EmptySquare;
9310             }
9311           }
9312         }
9313         board[toY][toX] = EmptySquare;
9314       }
9315     }
9316     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9317         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9318     } else
9319     if(promoChar == '+') {
9320         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9321         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9322     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9323         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9324     }
9325     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9326                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9327         // [HGM] superchess: take promotion piece out of holdings
9328         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9329         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9330             if(!--board[k][BOARD_WIDTH-2])
9331                 board[k][BOARD_WIDTH-1] = EmptySquare;
9332         } else {
9333             if(!--board[BOARD_HEIGHT-1-k][1])
9334                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9335         }
9336     }
9337
9338 }
9339
9340 /* Updates forwardMostMove */
9341 void
9342 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9343 {
9344 //    forwardMostMove++; // [HGM] bare: moved downstream
9345
9346     (void) CoordsToAlgebraic(boards[forwardMostMove],
9347                              PosFlags(forwardMostMove),
9348                              fromY, fromX, toY, toX, promoChar,
9349                              parseList[forwardMostMove]);
9350
9351     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9352         int timeLeft; static int lastLoadFlag=0; int king, piece;
9353         piece = boards[forwardMostMove][fromY][fromX];
9354         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9355         if(gameInfo.variant == VariantKnightmate)
9356             king += (int) WhiteUnicorn - (int) WhiteKing;
9357         if(forwardMostMove == 0) {
9358             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9359                 fprintf(serverMoves, "%s;", UserName());
9360             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9361                 fprintf(serverMoves, "%s;", second.tidy);
9362             fprintf(serverMoves, "%s;", first.tidy);
9363             if(gameMode == MachinePlaysWhite)
9364                 fprintf(serverMoves, "%s;", UserName());
9365             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9366                 fprintf(serverMoves, "%s;", second.tidy);
9367         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9368         lastLoadFlag = loadFlag;
9369         // print base move
9370         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9371         // print castling suffix
9372         if( toY == fromY && piece == king ) {
9373             if(toX-fromX > 1)
9374                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9375             if(fromX-toX >1)
9376                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9377         }
9378         // e.p. suffix
9379         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9380              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9381              boards[forwardMostMove][toY][toX] == EmptySquare
9382              && fromX != toX && fromY != toY)
9383                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9384         // promotion suffix
9385         if(promoChar != NULLCHAR)
9386                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9387         if(!loadFlag) {
9388                 char buf[MOVE_LEN*2], *p; int len;
9389             fprintf(serverMoves, "/%d/%d",
9390                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9391             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9392             else                      timeLeft = blackTimeRemaining/1000;
9393             fprintf(serverMoves, "/%d", timeLeft);
9394                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9395                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9396                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9397             fprintf(serverMoves, "/%s", buf);
9398         }
9399         fflush(serverMoves);
9400     }
9401
9402     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9403         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9404       return;
9405     }
9406     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9407     if (commentList[forwardMostMove+1] != NULL) {
9408         free(commentList[forwardMostMove+1]);
9409         commentList[forwardMostMove+1] = NULL;
9410     }
9411     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9412     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9413     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9414     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9415     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9416     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9417     adjustedClock = FALSE;
9418     gameInfo.result = GameUnfinished;
9419     if (gameInfo.resultDetails != NULL) {
9420         free(gameInfo.resultDetails);
9421         gameInfo.resultDetails = NULL;
9422     }
9423     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9424                               moveList[forwardMostMove - 1]);
9425     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9426       case MT_NONE:
9427       case MT_STALEMATE:
9428       default:
9429         break;
9430       case MT_CHECK:
9431         if(gameInfo.variant != VariantShogi)
9432             strcat(parseList[forwardMostMove - 1], "+");
9433         break;
9434       case MT_CHECKMATE:
9435       case MT_STAINMATE:
9436         strcat(parseList[forwardMostMove - 1], "#");
9437         break;
9438     }
9439
9440 }
9441
9442 /* Updates currentMove if not pausing */
9443 void
9444 ShowMove (int fromX, int fromY, int toX, int toY)
9445 {
9446     int instant = (gameMode == PlayFromGameFile) ?
9447         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9448     if(appData.noGUI) return;
9449     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9450         if (!instant) {
9451             if (forwardMostMove == currentMove + 1) {
9452                 AnimateMove(boards[forwardMostMove - 1],
9453                             fromX, fromY, toX, toY);
9454             }
9455             if (appData.highlightLastMove) {
9456                 SetHighlights(fromX, fromY, toX, toY);
9457             }
9458         }
9459         currentMove = forwardMostMove;
9460     }
9461
9462     if (instant) return;
9463
9464     DisplayMove(currentMove - 1);
9465     DrawPosition(FALSE, boards[currentMove]);
9466     DisplayBothClocks();
9467     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9468 }
9469
9470 void
9471 SendEgtPath (ChessProgramState *cps)
9472 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9473         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9474
9475         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9476
9477         while(*p) {
9478             char c, *q = name+1, *r, *s;
9479
9480             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9481             while(*p && *p != ',') *q++ = *p++;
9482             *q++ = ':'; *q = 0;
9483             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9484                 strcmp(name, ",nalimov:") == 0 ) {
9485                 // take nalimov path from the menu-changeable option first, if it is defined
9486               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9487                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9488             } else
9489             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9490                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9491                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9492                 s = r = StrStr(s, ":") + 1; // beginning of path info
9493                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9494                 c = *r; *r = 0;             // temporarily null-terminate path info
9495                     *--q = 0;               // strip of trailig ':' from name
9496                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9497                 *r = c;
9498                 SendToProgram(buf,cps);     // send egtbpath command for this format
9499             }
9500             if(*p == ',') p++; // read away comma to position for next format name
9501         }
9502 }
9503
9504 void
9505 InitChessProgram (ChessProgramState *cps, int setup)
9506 /* setup needed to setup FRC opening position */
9507 {
9508     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9509     if (appData.noChessProgram) return;
9510     hintRequested = FALSE;
9511     bookRequested = FALSE;
9512
9513     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9514     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9515     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9516     if(cps->memSize) { /* [HGM] memory */
9517       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9518         SendToProgram(buf, cps);
9519     }
9520     SendEgtPath(cps); /* [HGM] EGT */
9521     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9522       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9523         SendToProgram(buf, cps);
9524     }
9525
9526     SendToProgram(cps->initString, cps);
9527     if (gameInfo.variant != VariantNormal &&
9528         gameInfo.variant != VariantLoadable
9529         /* [HGM] also send variant if board size non-standard */
9530         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9531                                             ) {
9532       char *v = VariantName(gameInfo.variant);
9533       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9534         /* [HGM] in protocol 1 we have to assume all variants valid */
9535         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9536         DisplayFatalError(buf, 0, 1);
9537         return;
9538       }
9539
9540       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9541       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9542       if( gameInfo.variant == VariantXiangqi )
9543            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9544       if( gameInfo.variant == VariantShogi )
9545            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9546       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9547            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9548       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9549           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9550            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9551       if( gameInfo.variant == VariantCourier )
9552            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9553       if( gameInfo.variant == VariantSuper )
9554            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9555       if( gameInfo.variant == VariantGreat )
9556            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9557       if( gameInfo.variant == VariantSChess )
9558            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9559       if( gameInfo.variant == VariantGrand )
9560            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9561
9562       if(overruled) {
9563         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9564                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9565            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9566            if(StrStr(cps->variants, b) == NULL) {
9567                // specific sized variant not known, check if general sizing allowed
9568                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9569                    if(StrStr(cps->variants, "boardsize") == NULL) {
9570                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9571                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9572                        DisplayFatalError(buf, 0, 1);
9573                        return;
9574                    }
9575                    /* [HGM] here we really should compare with the maximum supported board size */
9576                }
9577            }
9578       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9579       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9580       SendToProgram(buf, cps);
9581     }
9582     currentlyInitializedVariant = gameInfo.variant;
9583
9584     /* [HGM] send opening position in FRC to first engine */
9585     if(setup) {
9586           SendToProgram("force\n", cps);
9587           SendBoard(cps, 0);
9588           /* engine is now in force mode! Set flag to wake it up after first move. */
9589           setboardSpoiledMachineBlack = 1;
9590     }
9591
9592     if (cps->sendICS) {
9593       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9594       SendToProgram(buf, cps);
9595     }
9596     cps->maybeThinking = FALSE;
9597     cps->offeredDraw = 0;
9598     if (!appData.icsActive) {
9599         SendTimeControl(cps, movesPerSession, timeControl,
9600                         timeIncrement, appData.searchDepth,
9601                         searchTime);
9602     }
9603     if (appData.showThinking
9604         // [HGM] thinking: four options require thinking output to be sent
9605         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9606                                 ) {
9607         SendToProgram("post\n", cps);
9608     }
9609     SendToProgram("hard\n", cps);
9610     if (!appData.ponderNextMove) {
9611         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9612            it without being sure what state we are in first.  "hard"
9613            is not a toggle, so that one is OK.
9614          */
9615         SendToProgram("easy\n", cps);
9616     }
9617     if (cps->usePing) {
9618       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9619       SendToProgram(buf, cps);
9620     }
9621     cps->initDone = TRUE;
9622     ClearEngineOutputPane(cps == &second);
9623 }
9624
9625
9626 void
9627 StartChessProgram (ChessProgramState *cps)
9628 {
9629     char buf[MSG_SIZ];
9630     int err;
9631
9632     if (appData.noChessProgram) return;
9633     cps->initDone = FALSE;
9634
9635     if (strcmp(cps->host, "localhost") == 0) {
9636         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9637     } else if (*appData.remoteShell == NULLCHAR) {
9638         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9639     } else {
9640         if (*appData.remoteUser == NULLCHAR) {
9641           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9642                     cps->program);
9643         } else {
9644           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9645                     cps->host, appData.remoteUser, cps->program);
9646         }
9647         err = StartChildProcess(buf, "", &cps->pr);
9648     }
9649
9650     if (err != 0) {
9651       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9652         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9653         if(cps != &first) return;
9654         appData.noChessProgram = TRUE;
9655         ThawUI();
9656         SetNCPMode();
9657 //      DisplayFatalError(buf, err, 1);
9658 //      cps->pr = NoProc;
9659 //      cps->isr = NULL;
9660         return;
9661     }
9662
9663     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9664     if (cps->protocolVersion > 1) {
9665       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9666       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9667       cps->comboCnt = 0;  //                and values of combo boxes
9668       SendToProgram(buf, cps);
9669     } else {
9670       SendToProgram("xboard\n", cps);
9671     }
9672 }
9673
9674 void
9675 TwoMachinesEventIfReady P((void))
9676 {
9677   static int curMess = 0;
9678   if (first.lastPing != first.lastPong) {
9679     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9680     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9681     return;
9682   }
9683   if (second.lastPing != second.lastPong) {
9684     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9685     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9686     return;
9687   }
9688   DisplayMessage("", ""); curMess = 0;
9689   ThawUI();
9690   TwoMachinesEvent();
9691 }
9692
9693 char *
9694 MakeName (char *template)
9695 {
9696     time_t clock;
9697     struct tm *tm;
9698     static char buf[MSG_SIZ];
9699     char *p = buf;
9700     int i;
9701
9702     clock = time((time_t *)NULL);
9703     tm = localtime(&clock);
9704
9705     while(*p++ = *template++) if(p[-1] == '%') {
9706         switch(*template++) {
9707           case 0:   *p = 0; return buf;
9708           case 'Y': i = tm->tm_year+1900; break;
9709           case 'y': i = tm->tm_year-100; break;
9710           case 'M': i = tm->tm_mon+1; break;
9711           case 'd': i = tm->tm_mday; break;
9712           case 'h': i = tm->tm_hour; break;
9713           case 'm': i = tm->tm_min; break;
9714           case 's': i = tm->tm_sec; break;
9715           default:  i = 0;
9716         }
9717         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9718     }
9719     return buf;
9720 }
9721
9722 int
9723 CountPlayers (char *p)
9724 {
9725     int n = 0;
9726     while(p = strchr(p, '\n')) p++, n++; // count participants
9727     return n;
9728 }
9729
9730 FILE *
9731 WriteTourneyFile (char *results, FILE *f)
9732 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9733     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9734     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9735         // create a file with tournament description
9736         fprintf(f, "-participants {%s}\n", appData.participants);
9737         fprintf(f, "-seedBase %d\n", appData.seedBase);
9738         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9739         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9740         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9741         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9742         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9743         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9744         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9745         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9746         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9747         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9748         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9749         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9750         if(searchTime > 0)
9751                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9752         else {
9753                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9754                 fprintf(f, "-tc %s\n", appData.timeControl);
9755                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9756         }
9757         fprintf(f, "-results \"%s\"\n", results);
9758     }
9759     return f;
9760 }
9761
9762 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9763
9764 void
9765 Substitute (char *participants, int expunge)
9766 {
9767     int i, changed, changes=0, nPlayers=0;
9768     char *p, *q, *r, buf[MSG_SIZ];
9769     if(participants == NULL) return;
9770     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9771     r = p = participants; q = appData.participants;
9772     while(*p && *p == *q) {
9773         if(*p == '\n') r = p+1, nPlayers++;
9774         p++; q++;
9775     }
9776     if(*p) { // difference
9777         while(*p && *p++ != '\n');
9778         while(*q && *q++ != '\n');
9779       changed = nPlayers;
9780         changes = 1 + (strcmp(p, q) != 0);
9781     }
9782     if(changes == 1) { // a single engine mnemonic was changed
9783         q = r; while(*q) nPlayers += (*q++ == '\n');
9784         p = buf; while(*r && (*p = *r++) != '\n') p++;
9785         *p = NULLCHAR;
9786         NamesToList(firstChessProgramNames, command, mnemonic, "all");
9787         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9788         if(mnemonic[i]) { // The substitute is valid
9789             FILE *f;
9790             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9791                 flock(fileno(f), LOCK_EX);
9792                 ParseArgsFromFile(f);
9793                 fseek(f, 0, SEEK_SET);
9794                 FREE(appData.participants); appData.participants = participants;
9795                 if(expunge) { // erase results of replaced engine
9796                     int len = strlen(appData.results), w, b, dummy;
9797                     for(i=0; i<len; i++) {
9798                         Pairing(i, nPlayers, &w, &b, &dummy);
9799                         if((w == changed || b == changed) && appData.results[i] == '*') {
9800                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9801                             fclose(f);
9802                             return;
9803                         }
9804                     }
9805                     for(i=0; i<len; i++) {
9806                         Pairing(i, nPlayers, &w, &b, &dummy);
9807                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9808                     }
9809                 }
9810                 WriteTourneyFile(appData.results, f);
9811                 fclose(f); // release lock
9812                 return;
9813             }
9814         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9815     }
9816     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9817     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9818     free(participants);
9819     return;
9820 }
9821
9822 int
9823 CreateTourney (char *name)
9824 {
9825         FILE *f;
9826         if(matchMode && strcmp(name, appData.tourneyFile)) {
9827              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9828         }
9829         if(name[0] == NULLCHAR) {
9830             if(appData.participants[0])
9831                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9832             return 0;
9833         }
9834         f = fopen(name, "r");
9835         if(f) { // file exists
9836             ASSIGN(appData.tourneyFile, name);
9837             ParseArgsFromFile(f); // parse it
9838         } else {
9839             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9840             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9841                 DisplayError(_("Not enough participants"), 0);
9842                 return 0;
9843             }
9844             ASSIGN(appData.tourneyFile, name);
9845             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9846             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9847         }
9848         fclose(f);
9849         appData.noChessProgram = FALSE;
9850         appData.clockMode = TRUE;
9851         SetGNUMode();
9852         return 1;
9853 }
9854
9855 int
9856 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
9857 {
9858     char buf[MSG_SIZ], *p, *q;
9859     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
9860     skip = !all && group[0]; // if group requested, we start in skip mode
9861     for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) {
9862         p = names; q = buf; header = 0;
9863         while(*p && *p != '\n') *q++ = *p++;
9864         *q = 0;
9865         if(*p == '\n') p++;
9866         if(buf[0] == '#') {
9867             if(strstr(buf, "# end") == buf) { depth--; continue; } // leave group, and suppress printing label
9868             depth++; // we must be entering a new group
9869             if(all) continue; // suppress printing group headers when complete list requested
9870             header = 1;
9871             if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group
9872         }
9873         if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header)
9874         if(engineList[i]) free(engineList[i]);
9875         engineList[i] = strdup(buf);
9876         if(buf[0] != '#') TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied
9877         if(engineMnemonic[i]) free(engineMnemonic[i]);
9878         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9879             strcat(buf, " (");
9880             sscanf(q + 8, "%s", buf + strlen(buf));
9881             strcat(buf, ")");
9882         }
9883         engineMnemonic[i] = strdup(buf);
9884         i++;
9885     }
9886     engineList[i] = engineMnemonic[i] = NULL;
9887     return i;
9888 }
9889
9890 // following implemented as macro to avoid type limitations
9891 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9892
9893 void
9894 SwapEngines (int n)
9895 {   // swap settings for first engine and other engine (so far only some selected options)
9896     int h;
9897     char *p;
9898     if(n == 0) return;
9899     SWAP(directory, p)
9900     SWAP(chessProgram, p)
9901     SWAP(isUCI, h)
9902     SWAP(hasOwnBookUCI, h)
9903     SWAP(protocolVersion, h)
9904     SWAP(reuse, h)
9905     SWAP(scoreIsAbsolute, h)
9906     SWAP(timeOdds, h)
9907     SWAP(logo, p)
9908     SWAP(pgnName, p)
9909     SWAP(pvSAN, h)
9910     SWAP(engOptions, p)
9911 }
9912
9913 int
9914 SetPlayer (int player, char *p)
9915 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9916     int i;
9917     char buf[MSG_SIZ], *engineName;
9918     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9919     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9920     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9921     if(mnemonic[i]) {
9922         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9923         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9924         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9925         ParseArgsFromString(buf);
9926     }
9927     free(engineName);
9928     return i;
9929 }
9930
9931 char *recentEngines;
9932
9933 void
9934 RecentEngineEvent (int nr)
9935 {
9936     int n;
9937 //    SwapEngines(1); // bump first to second
9938 //    ReplaceEngine(&second, 1); // and load it there
9939     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
9940     n = SetPlayer(nr, recentEngines); // select new (using original menu order!)
9941     if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip
9942         ReplaceEngine(&first, 0);
9943         FloatToFront(&appData.recentEngineList, command[n]);
9944     }
9945 }
9946
9947 int
9948 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9949 {   // determine players from game number
9950     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9951
9952     if(appData.tourneyType == 0) {
9953         roundsPerCycle = (nPlayers - 1) | 1;
9954         pairingsPerRound = nPlayers / 2;
9955     } else if(appData.tourneyType > 0) {
9956         roundsPerCycle = nPlayers - appData.tourneyType;
9957         pairingsPerRound = appData.tourneyType;
9958     }
9959     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9960     gamesPerCycle = gamesPerRound * roundsPerCycle;
9961     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9962     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9963     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9964     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9965     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9966     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9967
9968     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9969     if(appData.roundSync) *syncInterval = gamesPerRound;
9970
9971     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9972
9973     if(appData.tourneyType == 0) {
9974         if(curPairing == (nPlayers-1)/2 ) {
9975             *whitePlayer = curRound;
9976             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9977         } else {
9978             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9979             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9980             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9981             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9982         }
9983     } else if(appData.tourneyType > 1) {
9984         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9985         *whitePlayer = curRound + appData.tourneyType;
9986     } else if(appData.tourneyType > 0) {
9987         *whitePlayer = curPairing;
9988         *blackPlayer = curRound + appData.tourneyType;
9989     }
9990
9991     // take care of white/black alternation per round. 
9992     // For cycles and games this is already taken care of by default, derived from matchGame!
9993     return curRound & 1;
9994 }
9995
9996 int
9997 NextTourneyGame (int nr, int *swapColors)
9998 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9999     char *p, *q;
10000     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10001     FILE *tf;
10002     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10003     tf = fopen(appData.tourneyFile, "r");
10004     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10005     ParseArgsFromFile(tf); fclose(tf);
10006     InitTimeControls(); // TC might be altered from tourney file
10007
10008     nPlayers = CountPlayers(appData.participants); // count participants
10009     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10010     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10011
10012     if(syncInterval) {
10013         p = q = appData.results;
10014         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10015         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10016             DisplayMessage(_("Waiting for other game(s)"),"");
10017             waitingForGame = TRUE;
10018             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10019             return 0;
10020         }
10021         waitingForGame = FALSE;
10022     }
10023
10024     if(appData.tourneyType < 0) {
10025         if(nr>=0 && !pairingReceived) {
10026             char buf[1<<16];
10027             if(pairing.pr == NoProc) {
10028                 if(!appData.pairingEngine[0]) {
10029                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10030                     return 0;
10031                 }
10032                 StartChessProgram(&pairing); // starts the pairing engine
10033             }
10034             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10035             SendToProgram(buf, &pairing);
10036             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10037             SendToProgram(buf, &pairing);
10038             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10039         }
10040         pairingReceived = 0;                              // ... so we continue here 
10041         *swapColors = 0;
10042         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10043         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10044         matchGame = 1; roundNr = nr / syncInterval + 1;
10045     }
10046
10047     if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded
10048
10049     // redefine engines, engine dir, etc.
10050     NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines
10051     if(first.pr == NoProc) {
10052       SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line
10053       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10054     }
10055     if(second.pr == NoProc) {
10056       SwapEngines(1);
10057       SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line
10058       SwapEngines(1);         // and make that valid for second engine by swapping
10059       InitEngine(&second, 1);
10060     }
10061     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10062     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10063     return 1;
10064 }
10065
10066 void
10067 NextMatchGame ()
10068 {   // performs game initialization that does not invoke engines, and then tries to start the game
10069     int res, firstWhite, swapColors = 0;
10070     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10071     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10072     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10073     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10074     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10075     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10076     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10077     Reset(FALSE, first.pr != NoProc);
10078     res = LoadGameOrPosition(matchGame); // setup game
10079     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10080     if(!res) return; // abort when bad game/pos file
10081     TwoMachinesEvent();
10082 }
10083
10084 void
10085 UserAdjudicationEvent (int result)
10086 {
10087     ChessMove gameResult = GameIsDrawn;
10088
10089     if( result > 0 ) {
10090         gameResult = WhiteWins;
10091     }
10092     else if( result < 0 ) {
10093         gameResult = BlackWins;
10094     }
10095
10096     if( gameMode == TwoMachinesPlay ) {
10097         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10098     }
10099 }
10100
10101
10102 // [HGM] save: calculate checksum of game to make games easily identifiable
10103 int
10104 StringCheckSum (char *s)
10105 {
10106         int i = 0;
10107         if(s==NULL) return 0;
10108         while(*s) i = i*259 + *s++;
10109         return i;
10110 }
10111
10112 int
10113 GameCheckSum ()
10114 {
10115         int i, sum=0;
10116         for(i=backwardMostMove; i<forwardMostMove; i++) {
10117                 sum += pvInfoList[i].depth;
10118                 sum += StringCheckSum(parseList[i]);
10119                 sum += StringCheckSum(commentList[i]);
10120                 sum *= 261;
10121         }
10122         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10123         return sum + StringCheckSum(commentList[i]);
10124 } // end of save patch
10125
10126 void
10127 GameEnds (ChessMove result, char *resultDetails, int whosays)
10128 {
10129     GameMode nextGameMode;
10130     int isIcsGame;
10131     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10132
10133     if(endingGame) return; /* [HGM] crash: forbid recursion */
10134     endingGame = 1;
10135     if(twoBoards) { // [HGM] dual: switch back to one board
10136         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10137         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10138     }
10139     if (appData.debugMode) {
10140       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10141               result, resultDetails ? resultDetails : "(null)", whosays);
10142     }
10143
10144     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10145
10146     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10147         /* If we are playing on ICS, the server decides when the
10148            game is over, but the engine can offer to draw, claim
10149            a draw, or resign.
10150          */
10151 #if ZIPPY
10152         if (appData.zippyPlay && first.initDone) {
10153             if (result == GameIsDrawn) {
10154                 /* In case draw still needs to be claimed */
10155                 SendToICS(ics_prefix);
10156                 SendToICS("draw\n");
10157             } else if (StrCaseStr(resultDetails, "resign")) {
10158                 SendToICS(ics_prefix);
10159                 SendToICS("resign\n");
10160             }
10161         }
10162 #endif
10163         endingGame = 0; /* [HGM] crash */
10164         return;
10165     }
10166
10167     /* If we're loading the game from a file, stop */
10168     if (whosays == GE_FILE) {
10169       (void) StopLoadGameTimer();
10170       gameFileFP = NULL;
10171     }
10172
10173     /* Cancel draw offers */
10174     first.offeredDraw = second.offeredDraw = 0;
10175
10176     /* If this is an ICS game, only ICS can really say it's done;
10177        if not, anyone can. */
10178     isIcsGame = (gameMode == IcsPlayingWhite ||
10179                  gameMode == IcsPlayingBlack ||
10180                  gameMode == IcsObserving    ||
10181                  gameMode == IcsExamining);
10182
10183     if (!isIcsGame || whosays == GE_ICS) {
10184         /* OK -- not an ICS game, or ICS said it was done */
10185         StopClocks();
10186         if (!isIcsGame && !appData.noChessProgram)
10187           SetUserThinkingEnables();
10188
10189         /* [HGM] if a machine claims the game end we verify this claim */
10190         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10191             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10192                 char claimer;
10193                 ChessMove trueResult = (ChessMove) -1;
10194
10195                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10196                                             first.twoMachinesColor[0] :
10197                                             second.twoMachinesColor[0] ;
10198
10199                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10200                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10201                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10202                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10203                 } else
10204                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10205                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10206                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10207                 } else
10208                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10209                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10210                 }
10211
10212                 // now verify win claims, but not in drop games, as we don't understand those yet
10213                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10214                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10215                     (result == WhiteWins && claimer == 'w' ||
10216                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10217                       if (appData.debugMode) {
10218                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10219                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10220                       }
10221                       if(result != trueResult) {
10222                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10223                               result = claimer == 'w' ? BlackWins : WhiteWins;
10224                               resultDetails = buf;
10225                       }
10226                 } else
10227                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10228                     && (forwardMostMove <= backwardMostMove ||
10229                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10230                         (claimer=='b')==(forwardMostMove&1))
10231                                                                                   ) {
10232                       /* [HGM] verify: draws that were not flagged are false claims */
10233                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10234                       result = claimer == 'w' ? BlackWins : WhiteWins;
10235                       resultDetails = buf;
10236                 }
10237                 /* (Claiming a loss is accepted no questions asked!) */
10238             }
10239             /* [HGM] bare: don't allow bare King to win */
10240             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10241                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10242                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10243                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10244                && result != GameIsDrawn)
10245             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10246                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10247                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10248                         if(p >= 0 && p <= (int)WhiteKing) k++;
10249                 }
10250                 if (appData.debugMode) {
10251                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10252                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10253                 }
10254                 if(k <= 1) {
10255                         result = GameIsDrawn;
10256                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10257                         resultDetails = buf;
10258                 }
10259             }
10260         }
10261
10262
10263         if(serverMoves != NULL && !loadFlag) { char c = '=';
10264             if(result==WhiteWins) c = '+';
10265             if(result==BlackWins) c = '-';
10266             if(resultDetails != NULL)
10267                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10268         }
10269         if (resultDetails != NULL) {
10270             gameInfo.result = result;
10271             gameInfo.resultDetails = StrSave(resultDetails);
10272
10273             /* display last move only if game was not loaded from file */
10274             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10275                 DisplayMove(currentMove - 1);
10276
10277             if (forwardMostMove != 0) {
10278                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10279                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10280                                                                 ) {
10281                     if (*appData.saveGameFile != NULLCHAR) {
10282                         SaveGameToFile(appData.saveGameFile, TRUE);
10283                     } else if (appData.autoSaveGames) {
10284                         AutoSaveGame();
10285                     }
10286                     if (*appData.savePositionFile != NULLCHAR) {
10287                         SavePositionToFile(appData.savePositionFile);
10288                     }
10289                 }
10290             }
10291
10292             /* Tell program how game ended in case it is learning */
10293             /* [HGM] Moved this to after saving the PGN, just in case */
10294             /* engine died and we got here through time loss. In that */
10295             /* case we will get a fatal error writing the pipe, which */
10296             /* would otherwise lose us the PGN.                       */
10297             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10298             /* output during GameEnds should never be fatal anymore   */
10299             if (gameMode == MachinePlaysWhite ||
10300                 gameMode == MachinePlaysBlack ||
10301                 gameMode == TwoMachinesPlay ||
10302                 gameMode == IcsPlayingWhite ||
10303                 gameMode == IcsPlayingBlack ||
10304                 gameMode == BeginningOfGame) {
10305                 char buf[MSG_SIZ];
10306                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10307                         resultDetails);
10308                 if (first.pr != NoProc) {
10309                     SendToProgram(buf, &first);
10310                 }
10311                 if (second.pr != NoProc &&
10312                     gameMode == TwoMachinesPlay) {
10313                     SendToProgram(buf, &second);
10314                 }
10315             }
10316         }
10317
10318         if (appData.icsActive) {
10319             if (appData.quietPlay &&
10320                 (gameMode == IcsPlayingWhite ||
10321                  gameMode == IcsPlayingBlack)) {
10322                 SendToICS(ics_prefix);
10323                 SendToICS("set shout 1\n");
10324             }
10325             nextGameMode = IcsIdle;
10326             ics_user_moved = FALSE;
10327             /* clean up premove.  It's ugly when the game has ended and the
10328              * premove highlights are still on the board.
10329              */
10330             if (gotPremove) {
10331               gotPremove = FALSE;
10332               ClearPremoveHighlights();
10333               DrawPosition(FALSE, boards[currentMove]);
10334             }
10335             if (whosays == GE_ICS) {
10336                 switch (result) {
10337                 case WhiteWins:
10338                     if (gameMode == IcsPlayingWhite)
10339                         PlayIcsWinSound();
10340                     else if(gameMode == IcsPlayingBlack)
10341                         PlayIcsLossSound();
10342                     break;
10343                 case BlackWins:
10344                     if (gameMode == IcsPlayingBlack)
10345                         PlayIcsWinSound();
10346                     else if(gameMode == IcsPlayingWhite)
10347                         PlayIcsLossSound();
10348                     break;
10349                 case GameIsDrawn:
10350                     PlayIcsDrawSound();
10351                     break;
10352                 default:
10353                     PlayIcsUnfinishedSound();
10354                 }
10355             }
10356         } else if (gameMode == EditGame ||
10357                    gameMode == PlayFromGameFile ||
10358                    gameMode == AnalyzeMode ||
10359                    gameMode == AnalyzeFile) {
10360             nextGameMode = gameMode;
10361         } else {
10362             nextGameMode = EndOfGame;
10363         }
10364         pausing = FALSE;
10365         ModeHighlight();
10366     } else {
10367         nextGameMode = gameMode;
10368     }
10369
10370     if (appData.noChessProgram) {
10371         gameMode = nextGameMode;
10372         ModeHighlight();
10373         endingGame = 0; /* [HGM] crash */
10374         return;
10375     }
10376
10377     if (first.reuse) {
10378         /* Put first chess program into idle state */
10379         if (first.pr != NoProc &&
10380             (gameMode == MachinePlaysWhite ||
10381              gameMode == MachinePlaysBlack ||
10382              gameMode == TwoMachinesPlay ||
10383              gameMode == IcsPlayingWhite ||
10384              gameMode == IcsPlayingBlack ||
10385              gameMode == BeginningOfGame)) {
10386             SendToProgram("force\n", &first);
10387             if (first.usePing) {
10388               char buf[MSG_SIZ];
10389               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10390               SendToProgram(buf, &first);
10391             }
10392         }
10393     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10394         /* Kill off first chess program */
10395         if (first.isr != NULL)
10396           RemoveInputSource(first.isr);
10397         first.isr = NULL;
10398
10399         if (first.pr != NoProc) {
10400             ExitAnalyzeMode();
10401             DoSleep( appData.delayBeforeQuit );
10402             SendToProgram("quit\n", &first);
10403             DoSleep( appData.delayAfterQuit );
10404             DestroyChildProcess(first.pr, first.useSigterm);
10405         }
10406         first.pr = NoProc;
10407     }
10408     if (second.reuse) {
10409         /* Put second chess program into idle state */
10410         if (second.pr != NoProc &&
10411             gameMode == TwoMachinesPlay) {
10412             SendToProgram("force\n", &second);
10413             if (second.usePing) {
10414               char buf[MSG_SIZ];
10415               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10416               SendToProgram(buf, &second);
10417             }
10418         }
10419     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10420         /* Kill off second chess program */
10421         if (second.isr != NULL)
10422           RemoveInputSource(second.isr);
10423         second.isr = NULL;
10424
10425         if (second.pr != NoProc) {
10426             DoSleep( appData.delayBeforeQuit );
10427             SendToProgram("quit\n", &second);
10428             DoSleep( appData.delayAfterQuit );
10429             DestroyChildProcess(second.pr, second.useSigterm);
10430         }
10431         second.pr = NoProc;
10432     }
10433
10434     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10435         char resChar = '=';
10436         switch (result) {
10437         case WhiteWins:
10438           resChar = '+';
10439           if (first.twoMachinesColor[0] == 'w') {
10440             first.matchWins++;
10441           } else {
10442             second.matchWins++;
10443           }
10444           break;
10445         case BlackWins:
10446           resChar = '-';
10447           if (first.twoMachinesColor[0] == 'b') {
10448             first.matchWins++;
10449           } else {
10450             second.matchWins++;
10451           }
10452           break;
10453         case GameUnfinished:
10454           resChar = ' ';
10455         default:
10456           break;
10457         }
10458
10459         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10460         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10461             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10462             ReserveGame(nextGame, resChar); // sets nextGame
10463             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10464             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10465         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10466
10467         if (nextGame <= appData.matchGames && !abortMatch) {
10468             gameMode = nextGameMode;
10469             matchGame = nextGame; // this will be overruled in tourney mode!
10470             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10471             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10472             endingGame = 0; /* [HGM] crash */
10473             return;
10474         } else {
10475             gameMode = nextGameMode;
10476             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10477                      first.tidy, second.tidy,
10478                      first.matchWins, second.matchWins,
10479                      appData.matchGames - (first.matchWins + second.matchWins));
10480             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10481             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10482             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10483             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10484                 first.twoMachinesColor = "black\n";
10485                 second.twoMachinesColor = "white\n";
10486             } else {
10487                 first.twoMachinesColor = "white\n";
10488                 second.twoMachinesColor = "black\n";
10489             }
10490         }
10491     }
10492     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10493         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10494       ExitAnalyzeMode();
10495     gameMode = nextGameMode;
10496     ModeHighlight();
10497     endingGame = 0;  /* [HGM] crash */
10498     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10499         if(matchMode == TRUE) { // match through command line: exit with or without popup
10500             if(ranking) {
10501                 ToNrEvent(forwardMostMove);
10502                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10503                 else ExitEvent(0);
10504             } else DisplayFatalError(buf, 0, 0);
10505         } else { // match through menu; just stop, with or without popup
10506             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10507             ModeHighlight();
10508             if(ranking){
10509                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10510             } else DisplayNote(buf);
10511       }
10512       if(ranking) free(ranking);
10513     }
10514 }
10515
10516 /* Assumes program was just initialized (initString sent).
10517    Leaves program in force mode. */
10518 void
10519 FeedMovesToProgram (ChessProgramState *cps, int upto)
10520 {
10521     int i;
10522
10523     if (appData.debugMode)
10524       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10525               startedFromSetupPosition ? "position and " : "",
10526               backwardMostMove, upto, cps->which);
10527     if(currentlyInitializedVariant != gameInfo.variant) {
10528       char buf[MSG_SIZ];
10529         // [HGM] variantswitch: make engine aware of new variant
10530         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10531                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10532         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10533         SendToProgram(buf, cps);
10534         currentlyInitializedVariant = gameInfo.variant;
10535     }
10536     SendToProgram("force\n", cps);
10537     if (startedFromSetupPosition) {
10538         SendBoard(cps, backwardMostMove);
10539     if (appData.debugMode) {
10540         fprintf(debugFP, "feedMoves\n");
10541     }
10542     }
10543     for (i = backwardMostMove; i < upto; i++) {
10544         SendMoveToProgram(i, cps);
10545     }
10546 }
10547
10548
10549 int
10550 ResurrectChessProgram ()
10551 {
10552      /* The chess program may have exited.
10553         If so, restart it and feed it all the moves made so far. */
10554     static int doInit = 0;
10555
10556     if (appData.noChessProgram) return 1;
10557
10558     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10559         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10560         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10561         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10562     } else {
10563         if (first.pr != NoProc) return 1;
10564         StartChessProgram(&first);
10565     }
10566     InitChessProgram(&first, FALSE);
10567     FeedMovesToProgram(&first, currentMove);
10568
10569     if (!first.sendTime) {
10570         /* can't tell gnuchess what its clock should read,
10571            so we bow to its notion. */
10572         ResetClocks();
10573         timeRemaining[0][currentMove] = whiteTimeRemaining;
10574         timeRemaining[1][currentMove] = blackTimeRemaining;
10575     }
10576
10577     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10578                 appData.icsEngineAnalyze) && first.analysisSupport) {
10579       SendToProgram("analyze\n", &first);
10580       first.analyzing = TRUE;
10581     }
10582     return 1;
10583 }
10584
10585 /*
10586  * Button procedures
10587  */
10588 void
10589 Reset (int redraw, int init)
10590 {
10591     int i;
10592
10593     if (appData.debugMode) {
10594         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10595                 redraw, init, gameMode);
10596     }
10597     CleanupTail(); // [HGM] vari: delete any stored variations
10598     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10599     pausing = pauseExamInvalid = FALSE;
10600     startedFromSetupPosition = blackPlaysFirst = FALSE;
10601     firstMove = TRUE;
10602     whiteFlag = blackFlag = FALSE;
10603     userOfferedDraw = FALSE;
10604     hintRequested = bookRequested = FALSE;
10605     first.maybeThinking = FALSE;
10606     second.maybeThinking = FALSE;
10607     first.bookSuspend = FALSE; // [HGM] book
10608     second.bookSuspend = FALSE;
10609     thinkOutput[0] = NULLCHAR;
10610     lastHint[0] = NULLCHAR;
10611     ClearGameInfo(&gameInfo);
10612     gameInfo.variant = StringToVariant(appData.variant);
10613     ics_user_moved = ics_clock_paused = FALSE;
10614     ics_getting_history = H_FALSE;
10615     ics_gamenum = -1;
10616     white_holding[0] = black_holding[0] = NULLCHAR;
10617     ClearProgramStats();
10618     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10619
10620     ResetFrontEnd();
10621     ClearHighlights();
10622     flipView = appData.flipView;
10623     ClearPremoveHighlights();
10624     gotPremove = FALSE;
10625     alarmSounded = FALSE;
10626
10627     GameEnds(EndOfFile, NULL, GE_PLAYER);
10628     if(appData.serverMovesName != NULL) {
10629         /* [HGM] prepare to make moves file for broadcasting */
10630         clock_t t = clock();
10631         if(serverMoves != NULL) fclose(serverMoves);
10632         serverMoves = fopen(appData.serverMovesName, "r");
10633         if(serverMoves != NULL) {
10634             fclose(serverMoves);
10635             /* delay 15 sec before overwriting, so all clients can see end */
10636             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10637         }
10638         serverMoves = fopen(appData.serverMovesName, "w");
10639     }
10640
10641     ExitAnalyzeMode();
10642     gameMode = BeginningOfGame;
10643     ModeHighlight();
10644     if(appData.icsActive) gameInfo.variant = VariantNormal;
10645     currentMove = forwardMostMove = backwardMostMove = 0;
10646     MarkTargetSquares(1);
10647     InitPosition(redraw);
10648     for (i = 0; i < MAX_MOVES; i++) {
10649         if (commentList[i] != NULL) {
10650             free(commentList[i]);
10651             commentList[i] = NULL;
10652         }
10653     }
10654     ResetClocks();
10655     timeRemaining[0][0] = whiteTimeRemaining;
10656     timeRemaining[1][0] = blackTimeRemaining;
10657
10658     if (first.pr == NoProc) {
10659         StartChessProgram(&first);
10660     }
10661     if (init) {
10662             InitChessProgram(&first, startedFromSetupPosition);
10663     }
10664     DisplayTitle("");
10665     DisplayMessage("", "");
10666     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10667     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10668 }
10669
10670 void
10671 AutoPlayGameLoop ()
10672 {
10673     for (;;) {
10674         if (!AutoPlayOneMove())
10675           return;
10676         if (matchMode || appData.timeDelay == 0)
10677           continue;
10678         if (appData.timeDelay < 0)
10679           return;
10680         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10681         break;
10682     }
10683 }
10684
10685
10686 int
10687 AutoPlayOneMove ()
10688 {
10689     int fromX, fromY, toX, toY;
10690
10691     if (appData.debugMode) {
10692       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10693     }
10694
10695     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10696       return FALSE;
10697
10698     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10699       pvInfoList[currentMove].depth = programStats.depth;
10700       pvInfoList[currentMove].score = programStats.score;
10701       pvInfoList[currentMove].time  = 0;
10702       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10703     }
10704
10705     if (currentMove >= forwardMostMove) {
10706       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10707 //      gameMode = EndOfGame;
10708 //      ModeHighlight();
10709
10710       /* [AS] Clear current move marker at the end of a game */
10711       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10712
10713       return FALSE;
10714     }
10715
10716     toX = moveList[currentMove][2] - AAA;
10717     toY = moveList[currentMove][3] - ONE;
10718
10719     if (moveList[currentMove][1] == '@') {
10720         if (appData.highlightLastMove) {
10721             SetHighlights(-1, -1, toX, toY);
10722         }
10723     } else {
10724         fromX = moveList[currentMove][0] - AAA;
10725         fromY = moveList[currentMove][1] - ONE;
10726
10727         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10728
10729         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10730
10731         if (appData.highlightLastMove) {
10732             SetHighlights(fromX, fromY, toX, toY);
10733         }
10734     }
10735     DisplayMove(currentMove);
10736     SendMoveToProgram(currentMove++, &first);
10737     DisplayBothClocks();
10738     DrawPosition(FALSE, boards[currentMove]);
10739     // [HGM] PV info: always display, routine tests if empty
10740     DisplayComment(currentMove - 1, commentList[currentMove]);
10741     return TRUE;
10742 }
10743
10744
10745 int
10746 LoadGameOneMove (ChessMove readAhead)
10747 {
10748     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10749     char promoChar = NULLCHAR;
10750     ChessMove moveType;
10751     char move[MSG_SIZ];
10752     char *p, *q;
10753
10754     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10755         gameMode != AnalyzeMode && gameMode != Training) {
10756         gameFileFP = NULL;
10757         return FALSE;
10758     }
10759
10760     yyboardindex = forwardMostMove;
10761     if (readAhead != EndOfFile) {
10762       moveType = readAhead;
10763     } else {
10764       if (gameFileFP == NULL)
10765           return FALSE;
10766       moveType = (ChessMove) Myylex();
10767     }
10768
10769     done = FALSE;
10770     switch (moveType) {
10771       case Comment:
10772         if (appData.debugMode)
10773           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10774         p = yy_text;
10775
10776         /* append the comment but don't display it */
10777         AppendComment(currentMove, p, FALSE);
10778         return TRUE;
10779
10780       case WhiteCapturesEnPassant:
10781       case BlackCapturesEnPassant:
10782       case WhitePromotion:
10783       case BlackPromotion:
10784       case WhiteNonPromotion:
10785       case BlackNonPromotion:
10786       case NormalMove:
10787       case WhiteKingSideCastle:
10788       case WhiteQueenSideCastle:
10789       case BlackKingSideCastle:
10790       case BlackQueenSideCastle:
10791       case WhiteKingSideCastleWild:
10792       case WhiteQueenSideCastleWild:
10793       case BlackKingSideCastleWild:
10794       case BlackQueenSideCastleWild:
10795       /* PUSH Fabien */
10796       case WhiteHSideCastleFR:
10797       case WhiteASideCastleFR:
10798       case BlackHSideCastleFR:
10799       case BlackASideCastleFR:
10800       /* POP Fabien */
10801         if (appData.debugMode)
10802           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10803         fromX = currentMoveString[0] - AAA;
10804         fromY = currentMoveString[1] - ONE;
10805         toX = currentMoveString[2] - AAA;
10806         toY = currentMoveString[3] - ONE;
10807         promoChar = currentMoveString[4];
10808         break;
10809
10810       case WhiteDrop:
10811       case BlackDrop:
10812         if (appData.debugMode)
10813           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10814         fromX = moveType == WhiteDrop ?
10815           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10816         (int) CharToPiece(ToLower(currentMoveString[0]));
10817         fromY = DROP_RANK;
10818         toX = currentMoveString[2] - AAA;
10819         toY = currentMoveString[3] - ONE;
10820         break;
10821
10822       case WhiteWins:
10823       case BlackWins:
10824       case GameIsDrawn:
10825       case GameUnfinished:
10826         if (appData.debugMode)
10827           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10828         p = strchr(yy_text, '{');
10829         if (p == NULL) p = strchr(yy_text, '(');
10830         if (p == NULL) {
10831             p = yy_text;
10832             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10833         } else {
10834             q = strchr(p, *p == '{' ? '}' : ')');
10835             if (q != NULL) *q = NULLCHAR;
10836             p++;
10837         }
10838         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10839         GameEnds(moveType, p, GE_FILE);
10840         done = TRUE;
10841         if (cmailMsgLoaded) {
10842             ClearHighlights();
10843             flipView = WhiteOnMove(currentMove);
10844             if (moveType == GameUnfinished) flipView = !flipView;
10845             if (appData.debugMode)
10846               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10847         }
10848         break;
10849
10850       case EndOfFile:
10851         if (appData.debugMode)
10852           fprintf(debugFP, "Parser hit end of file\n");
10853         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10854           case MT_NONE:
10855           case MT_CHECK:
10856             break;
10857           case MT_CHECKMATE:
10858           case MT_STAINMATE:
10859             if (WhiteOnMove(currentMove)) {
10860                 GameEnds(BlackWins, "Black mates", GE_FILE);
10861             } else {
10862                 GameEnds(WhiteWins, "White mates", GE_FILE);
10863             }
10864             break;
10865           case MT_STALEMATE:
10866             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10867             break;
10868         }
10869         done = TRUE;
10870         break;
10871
10872       case MoveNumberOne:
10873         if (lastLoadGameStart == GNUChessGame) {
10874             /* GNUChessGames have numbers, but they aren't move numbers */
10875             if (appData.debugMode)
10876               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10877                       yy_text, (int) moveType);
10878             return LoadGameOneMove(EndOfFile); /* tail recursion */
10879         }
10880         /* else fall thru */
10881
10882       case XBoardGame:
10883       case GNUChessGame:
10884       case PGNTag:
10885         /* Reached start of next game in file */
10886         if (appData.debugMode)
10887           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10888         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10889           case MT_NONE:
10890           case MT_CHECK:
10891             break;
10892           case MT_CHECKMATE:
10893           case MT_STAINMATE:
10894             if (WhiteOnMove(currentMove)) {
10895                 GameEnds(BlackWins, "Black mates", GE_FILE);
10896             } else {
10897                 GameEnds(WhiteWins, "White mates", GE_FILE);
10898             }
10899             break;
10900           case MT_STALEMATE:
10901             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10902             break;
10903         }
10904         done = TRUE;
10905         break;
10906
10907       case PositionDiagram:     /* should not happen; ignore */
10908       case ElapsedTime:         /* ignore */
10909       case NAG:                 /* ignore */
10910         if (appData.debugMode)
10911           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10912                   yy_text, (int) moveType);
10913         return LoadGameOneMove(EndOfFile); /* tail recursion */
10914
10915       case IllegalMove:
10916         if (appData.testLegality) {
10917             if (appData.debugMode)
10918               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10919             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10920                     (forwardMostMove / 2) + 1,
10921                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10922             DisplayError(move, 0);
10923             done = TRUE;
10924         } else {
10925             if (appData.debugMode)
10926               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10927                       yy_text, currentMoveString);
10928             fromX = currentMoveString[0] - AAA;
10929             fromY = currentMoveString[1] - ONE;
10930             toX = currentMoveString[2] - AAA;
10931             toY = currentMoveString[3] - ONE;
10932             promoChar = currentMoveString[4];
10933         }
10934         break;
10935
10936       case AmbiguousMove:
10937         if (appData.debugMode)
10938           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10939         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10940                 (forwardMostMove / 2) + 1,
10941                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10942         DisplayError(move, 0);
10943         done = TRUE;
10944         break;
10945
10946       default:
10947       case ImpossibleMove:
10948         if (appData.debugMode)
10949           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10950         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10951                 (forwardMostMove / 2) + 1,
10952                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10953         DisplayError(move, 0);
10954         done = TRUE;
10955         break;
10956     }
10957
10958     if (done) {
10959         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10960             DrawPosition(FALSE, boards[currentMove]);
10961             DisplayBothClocks();
10962             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10963               DisplayComment(currentMove - 1, commentList[currentMove]);
10964         }
10965         (void) StopLoadGameTimer();
10966         gameFileFP = NULL;
10967         cmailOldMove = forwardMostMove;
10968         return FALSE;
10969     } else {
10970         /* currentMoveString is set as a side-effect of yylex */
10971
10972         thinkOutput[0] = NULLCHAR;
10973         MakeMove(fromX, fromY, toX, toY, promoChar);
10974         currentMove = forwardMostMove;
10975         return TRUE;
10976     }
10977 }
10978
10979 /* Load the nth game from the given file */
10980 int
10981 LoadGameFromFile (char *filename, int n, char *title, int useList)
10982 {
10983     FILE *f;
10984     char buf[MSG_SIZ];
10985
10986     if (strcmp(filename, "-") == 0) {
10987         f = stdin;
10988         title = "stdin";
10989     } else {
10990         f = fopen(filename, "rb");
10991         if (f == NULL) {
10992           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10993             DisplayError(buf, errno);
10994             return FALSE;
10995         }
10996     }
10997     if (fseek(f, 0, 0) == -1) {
10998         /* f is not seekable; probably a pipe */
10999         useList = FALSE;
11000     }
11001     if (useList && n == 0) {
11002         int error = GameListBuild(f);
11003         if (error) {
11004             DisplayError(_("Cannot build game list"), error);
11005         } else if (!ListEmpty(&gameList) &&
11006                    ((ListGame *) gameList.tailPred)->number > 1) {
11007             GameListPopUp(f, title);
11008             return TRUE;
11009         }
11010         GameListDestroy();
11011         n = 1;
11012     }
11013     if (n == 0) n = 1;
11014     return LoadGame(f, n, title, FALSE);
11015 }
11016
11017
11018 void
11019 MakeRegisteredMove ()
11020 {
11021     int fromX, fromY, toX, toY;
11022     char promoChar;
11023     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11024         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11025           case CMAIL_MOVE:
11026           case CMAIL_DRAW:
11027             if (appData.debugMode)
11028               fprintf(debugFP, "Restoring %s for game %d\n",
11029                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11030
11031             thinkOutput[0] = NULLCHAR;
11032             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11033             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11034             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11035             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11036             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11037             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11038             MakeMove(fromX, fromY, toX, toY, promoChar);
11039             ShowMove(fromX, fromY, toX, toY);
11040
11041             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11042               case MT_NONE:
11043               case MT_CHECK:
11044                 break;
11045
11046               case MT_CHECKMATE:
11047               case MT_STAINMATE:
11048                 if (WhiteOnMove(currentMove)) {
11049                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11050                 } else {
11051                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11052                 }
11053                 break;
11054
11055               case MT_STALEMATE:
11056                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11057                 break;
11058             }
11059
11060             break;
11061
11062           case CMAIL_RESIGN:
11063             if (WhiteOnMove(currentMove)) {
11064                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11065             } else {
11066                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11067             }
11068             break;
11069
11070           case CMAIL_ACCEPT:
11071             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11072             break;
11073
11074           default:
11075             break;
11076         }
11077     }
11078
11079     return;
11080 }
11081
11082 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11083 int
11084 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11085 {
11086     int retVal;
11087
11088     if (gameNumber > nCmailGames) {
11089         DisplayError(_("No more games in this message"), 0);
11090         return FALSE;
11091     }
11092     if (f == lastLoadGameFP) {
11093         int offset = gameNumber - lastLoadGameNumber;
11094         if (offset == 0) {
11095             cmailMsg[0] = NULLCHAR;
11096             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11097                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11098                 nCmailMovesRegistered--;
11099             }
11100             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11101             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11102                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11103             }
11104         } else {
11105             if (! RegisterMove()) return FALSE;
11106         }
11107     }
11108
11109     retVal = LoadGame(f, gameNumber, title, useList);
11110
11111     /* Make move registered during previous look at this game, if any */
11112     MakeRegisteredMove();
11113
11114     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11115         commentList[currentMove]
11116           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11117         DisplayComment(currentMove - 1, commentList[currentMove]);
11118     }
11119
11120     return retVal;
11121 }
11122
11123 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11124 int
11125 ReloadGame (int offset)
11126 {
11127     int gameNumber = lastLoadGameNumber + offset;
11128     if (lastLoadGameFP == NULL) {
11129         DisplayError(_("No game has been loaded yet"), 0);
11130         return FALSE;
11131     }
11132     if (gameNumber <= 0) {
11133         DisplayError(_("Can't back up any further"), 0);
11134         return FALSE;
11135     }
11136     if (cmailMsgLoaded) {
11137         return CmailLoadGame(lastLoadGameFP, gameNumber,
11138                              lastLoadGameTitle, lastLoadGameUseList);
11139     } else {
11140         return LoadGame(lastLoadGameFP, gameNumber,
11141                         lastLoadGameTitle, lastLoadGameUseList);
11142     }
11143 }
11144
11145 int keys[EmptySquare+1];
11146
11147 int
11148 PositionMatches (Board b1, Board b2)
11149 {
11150     int r, f, sum=0;
11151     switch(appData.searchMode) {
11152         case 1: return CompareWithRights(b1, b2);
11153         case 2:
11154             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11155                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11156             }
11157             return TRUE;
11158         case 3:
11159             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11160               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11161                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11162             }
11163             return sum==0;
11164         case 4:
11165             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11166                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11167             }
11168             return sum==0;
11169     }
11170     return TRUE;
11171 }
11172
11173 #define Q_PROMO  4
11174 #define Q_EP     3
11175 #define Q_BCASTL 2
11176 #define Q_WCASTL 1
11177
11178 int pieceList[256], quickBoard[256];
11179 ChessSquare pieceType[256] = { EmptySquare };
11180 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11181 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11182 int soughtTotal, turn;
11183 Boolean epOK, flipSearch;
11184
11185 typedef struct {
11186     unsigned char piece, to;
11187 } Move;
11188
11189 #define DSIZE (250000)
11190
11191 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11192 Move *moveDatabase = initialSpace;
11193 unsigned int movePtr, dataSize = DSIZE;
11194
11195 int
11196 MakePieceList (Board board, int *counts)
11197 {
11198     int r, f, n=Q_PROMO, total=0;
11199     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11200     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11201         int sq = f + (r<<4);
11202         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11203             quickBoard[sq] = ++n;
11204             pieceList[n] = sq;
11205             pieceType[n] = board[r][f];
11206             counts[board[r][f]]++;
11207             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11208             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11209             total++;
11210         }
11211     }
11212     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11213     return total;
11214 }
11215
11216 void
11217 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11218 {
11219     int sq = fromX + (fromY<<4);
11220     int piece = quickBoard[sq];
11221     quickBoard[sq] = 0;
11222     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11223     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11224         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11225         moveDatabase[movePtr++].piece = Q_WCASTL;
11226         quickBoard[sq] = piece;
11227         piece = quickBoard[from]; quickBoard[from] = 0;
11228         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11229     } else
11230     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11231         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11232         moveDatabase[movePtr++].piece = Q_BCASTL;
11233         quickBoard[sq] = piece;
11234         piece = quickBoard[from]; quickBoard[from] = 0;
11235         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11236     } else
11237     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11238         quickBoard[(fromY<<4)+toX] = 0;
11239         moveDatabase[movePtr].piece = Q_EP;
11240         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11241         moveDatabase[movePtr].to = sq;
11242     } else
11243     if(promoPiece != pieceType[piece]) {
11244         moveDatabase[movePtr++].piece = Q_PROMO;
11245         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11246     }
11247     moveDatabase[movePtr].piece = piece;
11248     quickBoard[sq] = piece;
11249     movePtr++;
11250 }
11251
11252 int
11253 PackGame (Board board)
11254 {
11255     Move *newSpace = NULL;
11256     moveDatabase[movePtr].piece = 0; // terminate previous game
11257     if(movePtr > dataSize) {
11258         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11259         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11260         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11261         if(newSpace) {
11262             int i;
11263             Move *p = moveDatabase, *q = newSpace;
11264             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11265             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11266             moveDatabase = newSpace;
11267         } else { // calloc failed, we must be out of memory. Too bad...
11268             dataSize = 0; // prevent calloc events for all subsequent games
11269             return 0;     // and signal this one isn't cached
11270         }
11271     }
11272     movePtr++;
11273     MakePieceList(board, counts);
11274     return movePtr;
11275 }
11276
11277 int
11278 QuickCompare (Board board, int *minCounts, int *maxCounts)
11279 {   // compare according to search mode
11280     int r, f;
11281     switch(appData.searchMode)
11282     {
11283       case 1: // exact position match
11284         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11285         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11286             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11287         }
11288         break;
11289       case 2: // can have extra material on empty squares
11290         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11291             if(board[r][f] == EmptySquare) continue;
11292             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11293         }
11294         break;
11295       case 3: // material with exact Pawn structure
11296         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11297             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11298             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11299         } // fall through to material comparison
11300       case 4: // exact material
11301         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11302         break;
11303       case 6: // material range with given imbalance
11304         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11305         // fall through to range comparison
11306       case 5: // material range
11307         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11308     }
11309     return TRUE;
11310 }
11311
11312 int
11313 QuickScan (Board board, Move *move)
11314 {   // reconstruct game,and compare all positions in it
11315     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11316     do {
11317         int piece = move->piece;
11318         int to = move->to, from = pieceList[piece];
11319         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11320           if(!piece) return -1;
11321           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11322             piece = (++move)->piece;
11323             from = pieceList[piece];
11324             counts[pieceType[piece]]--;
11325             pieceType[piece] = (ChessSquare) move->to;
11326             counts[move->to]++;
11327           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11328             counts[pieceType[quickBoard[to]]]--;
11329             quickBoard[to] = 0; total--;
11330             move++;
11331             continue;
11332           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11333             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11334             from  = pieceList[piece]; // so this must be King
11335             quickBoard[from] = 0;
11336             quickBoard[to] = piece;
11337             pieceList[piece] = to;
11338             move++;
11339             continue;
11340           }
11341         }
11342         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11343         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11344         quickBoard[from] = 0;
11345         quickBoard[to] = piece;
11346         pieceList[piece] = to;
11347         cnt++; turn ^= 3;
11348         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11349            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11350            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11351                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11352           ) {
11353             static int lastCounts[EmptySquare+1];
11354             int i;
11355             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11356             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11357         } else stretch = 0;
11358         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11359         move++;
11360     } while(1);
11361 }
11362
11363 void
11364 InitSearch ()
11365 {
11366     int r, f;
11367     flipSearch = FALSE;
11368     CopyBoard(soughtBoard, boards[currentMove]);
11369     soughtTotal = MakePieceList(soughtBoard, maxSought);
11370     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11371     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11372     CopyBoard(reverseBoard, boards[currentMove]);
11373     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11374         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11375         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11376         reverseBoard[r][f] = piece;
11377     }
11378     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11379     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11380     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11381                  || (boards[currentMove][CASTLING][2] == NoRights || 
11382                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11383                  && (boards[currentMove][CASTLING][5] == NoRights || 
11384                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11385       ) {
11386         flipSearch = TRUE;
11387         CopyBoard(flipBoard, soughtBoard);
11388         CopyBoard(rotateBoard, reverseBoard);
11389         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11390             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11391             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11392         }
11393     }
11394     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11395     if(appData.searchMode >= 5) {
11396         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11397         MakePieceList(soughtBoard, minSought);
11398         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11399     }
11400     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11401         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11402 }
11403
11404 GameInfo dummyInfo;
11405
11406 int
11407 GameContainsPosition (FILE *f, ListGame *lg)
11408 {
11409     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11410     int fromX, fromY, toX, toY;
11411     char promoChar;
11412     static int initDone=FALSE;
11413
11414     // weed out games based on numerical tag comparison
11415     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11416     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11417     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11418     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11419     if(!initDone) {
11420         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11421         initDone = TRUE;
11422     }
11423     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11424     else CopyBoard(boards[scratch], initialPosition); // default start position
11425     if(lg->moves) {
11426         turn = btm + 1;
11427         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11428         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11429     }
11430     if(btm) plyNr++;
11431     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11432     fseek(f, lg->offset, 0);
11433     yynewfile(f);
11434     while(1) {
11435         yyboardindex = scratch;
11436         quickFlag = plyNr+1;
11437         next = Myylex();
11438         quickFlag = 0;
11439         switch(next) {
11440             case PGNTag:
11441                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11442             default:
11443                 continue;
11444
11445             case XBoardGame:
11446             case GNUChessGame:
11447                 if(plyNr) return -1; // after we have seen moves, this is for new game
11448               continue;
11449
11450             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11451             case ImpossibleMove:
11452             case WhiteWins: // game ends here with these four
11453             case BlackWins:
11454             case GameIsDrawn:
11455             case GameUnfinished:
11456                 return -1;
11457
11458             case IllegalMove:
11459                 if(appData.testLegality) return -1;
11460             case WhiteCapturesEnPassant:
11461             case BlackCapturesEnPassant:
11462             case WhitePromotion:
11463             case BlackPromotion:
11464             case WhiteNonPromotion:
11465             case BlackNonPromotion:
11466             case NormalMove:
11467             case WhiteKingSideCastle:
11468             case WhiteQueenSideCastle:
11469             case BlackKingSideCastle:
11470             case BlackQueenSideCastle:
11471             case WhiteKingSideCastleWild:
11472             case WhiteQueenSideCastleWild:
11473             case BlackKingSideCastleWild:
11474             case BlackQueenSideCastleWild:
11475             case WhiteHSideCastleFR:
11476             case WhiteASideCastleFR:
11477             case BlackHSideCastleFR:
11478             case BlackASideCastleFR:
11479                 fromX = currentMoveString[0] - AAA;
11480                 fromY = currentMoveString[1] - ONE;
11481                 toX = currentMoveString[2] - AAA;
11482                 toY = currentMoveString[3] - ONE;
11483                 promoChar = currentMoveString[4];
11484                 break;
11485             case WhiteDrop:
11486             case BlackDrop:
11487                 fromX = next == WhiteDrop ?
11488                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11489                   (int) CharToPiece(ToLower(currentMoveString[0]));
11490                 fromY = DROP_RANK;
11491                 toX = currentMoveString[2] - AAA;
11492                 toY = currentMoveString[3] - ONE;
11493                 promoChar = 0;
11494                 break;
11495         }
11496         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11497         plyNr++;
11498         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11499         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11500         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11501         if(appData.findMirror) {
11502             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11503             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11504         }
11505     }
11506 }
11507
11508 /* Load the nth game from open file f */
11509 int
11510 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11511 {
11512     ChessMove cm;
11513     char buf[MSG_SIZ];
11514     int gn = gameNumber;
11515     ListGame *lg = NULL;
11516     int numPGNTags = 0;
11517     int err, pos = -1;
11518     GameMode oldGameMode;
11519     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11520
11521     if (appData.debugMode)
11522         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11523
11524     if (gameMode == Training )
11525         SetTrainingModeOff();
11526
11527     oldGameMode = gameMode;
11528     if (gameMode != BeginningOfGame) {
11529       Reset(FALSE, TRUE);
11530     }
11531
11532     gameFileFP = f;
11533     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11534         fclose(lastLoadGameFP);
11535     }
11536
11537     if (useList) {
11538         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11539
11540         if (lg) {
11541             fseek(f, lg->offset, 0);
11542             GameListHighlight(gameNumber);
11543             pos = lg->position;
11544             gn = 1;
11545         }
11546         else {
11547             DisplayError(_("Game number out of range"), 0);
11548             return FALSE;
11549         }
11550     } else {
11551         GameListDestroy();
11552         if (fseek(f, 0, 0) == -1) {
11553             if (f == lastLoadGameFP ?
11554                 gameNumber == lastLoadGameNumber + 1 :
11555                 gameNumber == 1) {
11556                 gn = 1;
11557             } else {
11558                 DisplayError(_("Can't seek on game file"), 0);
11559                 return FALSE;
11560             }
11561         }
11562     }
11563     lastLoadGameFP = f;
11564     lastLoadGameNumber = gameNumber;
11565     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11566     lastLoadGameUseList = useList;
11567
11568     yynewfile(f);
11569
11570     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11571       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11572                 lg->gameInfo.black);
11573             DisplayTitle(buf);
11574     } else if (*title != NULLCHAR) {
11575         if (gameNumber > 1) {
11576           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11577             DisplayTitle(buf);
11578         } else {
11579             DisplayTitle(title);
11580         }
11581     }
11582
11583     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11584         gameMode = PlayFromGameFile;
11585         ModeHighlight();
11586     }
11587
11588     currentMove = forwardMostMove = backwardMostMove = 0;
11589     CopyBoard(boards[0], initialPosition);
11590     StopClocks();
11591
11592     /*
11593      * Skip the first gn-1 games in the file.
11594      * Also skip over anything that precedes an identifiable
11595      * start of game marker, to avoid being confused by
11596      * garbage at the start of the file.  Currently
11597      * recognized start of game markers are the move number "1",
11598      * the pattern "gnuchess .* game", the pattern
11599      * "^[#;%] [^ ]* game file", and a PGN tag block.
11600      * A game that starts with one of the latter two patterns
11601      * will also have a move number 1, possibly
11602      * following a position diagram.
11603      * 5-4-02: Let's try being more lenient and allowing a game to
11604      * start with an unnumbered move.  Does that break anything?
11605      */
11606     cm = lastLoadGameStart = EndOfFile;
11607     while (gn > 0) {
11608         yyboardindex = forwardMostMove;
11609         cm = (ChessMove) Myylex();
11610         switch (cm) {
11611           case EndOfFile:
11612             if (cmailMsgLoaded) {
11613                 nCmailGames = CMAIL_MAX_GAMES - gn;
11614             } else {
11615                 Reset(TRUE, TRUE);
11616                 DisplayError(_("Game not found in file"), 0);
11617             }
11618             return FALSE;
11619
11620           case GNUChessGame:
11621           case XBoardGame:
11622             gn--;
11623             lastLoadGameStart = cm;
11624             break;
11625
11626           case MoveNumberOne:
11627             switch (lastLoadGameStart) {
11628               case GNUChessGame:
11629               case XBoardGame:
11630               case PGNTag:
11631                 break;
11632               case MoveNumberOne:
11633               case EndOfFile:
11634                 gn--;           /* count this game */
11635                 lastLoadGameStart = cm;
11636                 break;
11637               default:
11638                 /* impossible */
11639                 break;
11640             }
11641             break;
11642
11643           case PGNTag:
11644             switch (lastLoadGameStart) {
11645               case GNUChessGame:
11646               case PGNTag:
11647               case MoveNumberOne:
11648               case EndOfFile:
11649                 gn--;           /* count this game */
11650                 lastLoadGameStart = cm;
11651                 break;
11652               case XBoardGame:
11653                 lastLoadGameStart = cm; /* game counted already */
11654                 break;
11655               default:
11656                 /* impossible */
11657                 break;
11658             }
11659             if (gn > 0) {
11660                 do {
11661                     yyboardindex = forwardMostMove;
11662                     cm = (ChessMove) Myylex();
11663                 } while (cm == PGNTag || cm == Comment);
11664             }
11665             break;
11666
11667           case WhiteWins:
11668           case BlackWins:
11669           case GameIsDrawn:
11670             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11671                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11672                     != CMAIL_OLD_RESULT) {
11673                     nCmailResults ++ ;
11674                     cmailResult[  CMAIL_MAX_GAMES
11675                                 - gn - 1] = CMAIL_OLD_RESULT;
11676                 }
11677             }
11678             break;
11679
11680           case NormalMove:
11681             /* Only a NormalMove can be at the start of a game
11682              * without a position diagram. */
11683             if (lastLoadGameStart == EndOfFile ) {
11684               gn--;
11685               lastLoadGameStart = MoveNumberOne;
11686             }
11687             break;
11688
11689           default:
11690             break;
11691         }
11692     }
11693
11694     if (appData.debugMode)
11695       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11696
11697     if (cm == XBoardGame) {
11698         /* Skip any header junk before position diagram and/or move 1 */
11699         for (;;) {
11700             yyboardindex = forwardMostMove;
11701             cm = (ChessMove) Myylex();
11702
11703             if (cm == EndOfFile ||
11704                 cm == GNUChessGame || cm == XBoardGame) {
11705                 /* Empty game; pretend end-of-file and handle later */
11706                 cm = EndOfFile;
11707                 break;
11708             }
11709
11710             if (cm == MoveNumberOne || cm == PositionDiagram ||
11711                 cm == PGNTag || cm == Comment)
11712               break;
11713         }
11714     } else if (cm == GNUChessGame) {
11715         if (gameInfo.event != NULL) {
11716             free(gameInfo.event);
11717         }
11718         gameInfo.event = StrSave(yy_text);
11719     }
11720
11721     startedFromSetupPosition = FALSE;
11722     while (cm == PGNTag) {
11723         if (appData.debugMode)
11724           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11725         err = ParsePGNTag(yy_text, &gameInfo);
11726         if (!err) numPGNTags++;
11727
11728         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11729         if(gameInfo.variant != oldVariant) {
11730             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11731             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11732             InitPosition(TRUE);
11733             oldVariant = gameInfo.variant;
11734             if (appData.debugMode)
11735               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11736         }
11737
11738
11739         if (gameInfo.fen != NULL) {
11740           Board initial_position;
11741           startedFromSetupPosition = TRUE;
11742           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11743             Reset(TRUE, TRUE);
11744             DisplayError(_("Bad FEN position in file"), 0);
11745             return FALSE;
11746           }
11747           CopyBoard(boards[0], initial_position);
11748           if (blackPlaysFirst) {
11749             currentMove = forwardMostMove = backwardMostMove = 1;
11750             CopyBoard(boards[1], initial_position);
11751             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11752             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11753             timeRemaining[0][1] = whiteTimeRemaining;
11754             timeRemaining[1][1] = blackTimeRemaining;
11755             if (commentList[0] != NULL) {
11756               commentList[1] = commentList[0];
11757               commentList[0] = NULL;
11758             }
11759           } else {
11760             currentMove = forwardMostMove = backwardMostMove = 0;
11761           }
11762           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11763           {   int i;
11764               initialRulePlies = FENrulePlies;
11765               for( i=0; i< nrCastlingRights; i++ )
11766                   initialRights[i] = initial_position[CASTLING][i];
11767           }
11768           yyboardindex = forwardMostMove;
11769           free(gameInfo.fen);
11770           gameInfo.fen = NULL;
11771         }
11772
11773         yyboardindex = forwardMostMove;
11774         cm = (ChessMove) Myylex();
11775
11776         /* Handle comments interspersed among the tags */
11777         while (cm == Comment) {
11778             char *p;
11779             if (appData.debugMode)
11780               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11781             p = yy_text;
11782             AppendComment(currentMove, p, FALSE);
11783             yyboardindex = forwardMostMove;
11784             cm = (ChessMove) Myylex();
11785         }
11786     }
11787
11788     /* don't rely on existence of Event tag since if game was
11789      * pasted from clipboard the Event tag may not exist
11790      */
11791     if (numPGNTags > 0){
11792         char *tags;
11793         if (gameInfo.variant == VariantNormal) {
11794           VariantClass v = StringToVariant(gameInfo.event);
11795           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11796           if(v < VariantShogi) gameInfo.variant = v;
11797         }
11798         if (!matchMode) {
11799           if( appData.autoDisplayTags ) {
11800             tags = PGNTags(&gameInfo);
11801             TagsPopUp(tags, CmailMsg());
11802             free(tags);
11803           }
11804         }
11805     } else {
11806         /* Make something up, but don't display it now */
11807         SetGameInfo();
11808         TagsPopDown();
11809     }
11810
11811     if (cm == PositionDiagram) {
11812         int i, j;
11813         char *p;
11814         Board initial_position;
11815
11816         if (appData.debugMode)
11817           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11818
11819         if (!startedFromSetupPosition) {
11820             p = yy_text;
11821             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11822               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11823                 switch (*p) {
11824                   case '{':
11825                   case '[':
11826                   case '-':
11827                   case ' ':
11828                   case '\t':
11829                   case '\n':
11830                   case '\r':
11831                     break;
11832                   default:
11833                     initial_position[i][j++] = CharToPiece(*p);
11834                     break;
11835                 }
11836             while (*p == ' ' || *p == '\t' ||
11837                    *p == '\n' || *p == '\r') p++;
11838
11839             if (strncmp(p, "black", strlen("black"))==0)
11840               blackPlaysFirst = TRUE;
11841             else
11842               blackPlaysFirst = FALSE;
11843             startedFromSetupPosition = TRUE;
11844
11845             CopyBoard(boards[0], initial_position);
11846             if (blackPlaysFirst) {
11847                 currentMove = forwardMostMove = backwardMostMove = 1;
11848                 CopyBoard(boards[1], initial_position);
11849                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11850                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11851                 timeRemaining[0][1] = whiteTimeRemaining;
11852                 timeRemaining[1][1] = blackTimeRemaining;
11853                 if (commentList[0] != NULL) {
11854                     commentList[1] = commentList[0];
11855                     commentList[0] = NULL;
11856                 }
11857             } else {
11858                 currentMove = forwardMostMove = backwardMostMove = 0;
11859             }
11860         }
11861         yyboardindex = forwardMostMove;
11862         cm = (ChessMove) Myylex();
11863     }
11864
11865     if (first.pr == NoProc) {
11866         StartChessProgram(&first);
11867     }
11868     InitChessProgram(&first, FALSE);
11869     SendToProgram("force\n", &first);
11870     if (startedFromSetupPosition) {
11871         SendBoard(&first, forwardMostMove);
11872     if (appData.debugMode) {
11873         fprintf(debugFP, "Load Game\n");
11874     }
11875         DisplayBothClocks();
11876     }
11877
11878     /* [HGM] server: flag to write setup moves in broadcast file as one */
11879     loadFlag = appData.suppressLoadMoves;
11880
11881     while (cm == Comment) {
11882         char *p;
11883         if (appData.debugMode)
11884           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11885         p = yy_text;
11886         AppendComment(currentMove, p, FALSE);
11887         yyboardindex = forwardMostMove;
11888         cm = (ChessMove) Myylex();
11889     }
11890
11891     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11892         cm == WhiteWins || cm == BlackWins ||
11893         cm == GameIsDrawn || cm == GameUnfinished) {
11894         DisplayMessage("", _("No moves in game"));
11895         if (cmailMsgLoaded) {
11896             if (appData.debugMode)
11897               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11898             ClearHighlights();
11899             flipView = FALSE;
11900         }
11901         DrawPosition(FALSE, boards[currentMove]);
11902         DisplayBothClocks();
11903         gameMode = EditGame;
11904         ModeHighlight();
11905         gameFileFP = NULL;
11906         cmailOldMove = 0;
11907         return TRUE;
11908     }
11909
11910     // [HGM] PV info: routine tests if comment empty
11911     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11912         DisplayComment(currentMove - 1, commentList[currentMove]);
11913     }
11914     if (!matchMode && appData.timeDelay != 0)
11915       DrawPosition(FALSE, boards[currentMove]);
11916
11917     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11918       programStats.ok_to_send = 1;
11919     }
11920
11921     /* if the first token after the PGN tags is a move
11922      * and not move number 1, retrieve it from the parser
11923      */
11924     if (cm != MoveNumberOne)
11925         LoadGameOneMove(cm);
11926
11927     /* load the remaining moves from the file */
11928     while (LoadGameOneMove(EndOfFile)) {
11929       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11930       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11931     }
11932
11933     /* rewind to the start of the game */
11934     currentMove = backwardMostMove;
11935
11936     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11937
11938     if (oldGameMode == AnalyzeFile ||
11939         oldGameMode == AnalyzeMode) {
11940       AnalyzeFileEvent();
11941     }
11942
11943     if (!matchMode && pos >= 0) {
11944         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11945     } else
11946     if (matchMode || appData.timeDelay == 0) {
11947       ToEndEvent();
11948     } else if (appData.timeDelay > 0) {
11949       AutoPlayGameLoop();
11950     }
11951
11952     if (appData.debugMode)
11953         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11954
11955     loadFlag = 0; /* [HGM] true game starts */
11956     return TRUE;
11957 }
11958
11959 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11960 int
11961 ReloadPosition (int offset)
11962 {
11963     int positionNumber = lastLoadPositionNumber + offset;
11964     if (lastLoadPositionFP == NULL) {
11965         DisplayError(_("No position has been loaded yet"), 0);
11966         return FALSE;
11967     }
11968     if (positionNumber <= 0) {
11969         DisplayError(_("Can't back up any further"), 0);
11970         return FALSE;
11971     }
11972     return LoadPosition(lastLoadPositionFP, positionNumber,
11973                         lastLoadPositionTitle);
11974 }
11975
11976 /* Load the nth position from the given file */
11977 int
11978 LoadPositionFromFile (char *filename, int n, char *title)
11979 {
11980     FILE *f;
11981     char buf[MSG_SIZ];
11982
11983     if (strcmp(filename, "-") == 0) {
11984         return LoadPosition(stdin, n, "stdin");
11985     } else {
11986         f = fopen(filename, "rb");
11987         if (f == NULL) {
11988             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11989             DisplayError(buf, errno);
11990             return FALSE;
11991         } else {
11992             return LoadPosition(f, n, title);
11993         }
11994     }
11995 }
11996
11997 /* Load the nth position from the given open file, and close it */
11998 int
11999 LoadPosition (FILE *f, int positionNumber, char *title)
12000 {
12001     char *p, line[MSG_SIZ];
12002     Board initial_position;
12003     int i, j, fenMode, pn;
12004
12005     if (gameMode == Training )
12006         SetTrainingModeOff();
12007
12008     if (gameMode != BeginningOfGame) {
12009         Reset(FALSE, TRUE);
12010     }
12011     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12012         fclose(lastLoadPositionFP);
12013     }
12014     if (positionNumber == 0) positionNumber = 1;
12015     lastLoadPositionFP = f;
12016     lastLoadPositionNumber = positionNumber;
12017     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12018     if (first.pr == NoProc && !appData.noChessProgram) {
12019       StartChessProgram(&first);
12020       InitChessProgram(&first, FALSE);
12021     }
12022     pn = positionNumber;
12023     if (positionNumber < 0) {
12024         /* Negative position number means to seek to that byte offset */
12025         if (fseek(f, -positionNumber, 0) == -1) {
12026             DisplayError(_("Can't seek on position file"), 0);
12027             return FALSE;
12028         };
12029         pn = 1;
12030     } else {
12031         if (fseek(f, 0, 0) == -1) {
12032             if (f == lastLoadPositionFP ?
12033                 positionNumber == lastLoadPositionNumber + 1 :
12034                 positionNumber == 1) {
12035                 pn = 1;
12036             } else {
12037                 DisplayError(_("Can't seek on position file"), 0);
12038                 return FALSE;
12039             }
12040         }
12041     }
12042     /* See if this file is FEN or old-style xboard */
12043     if (fgets(line, MSG_SIZ, f) == NULL) {
12044         DisplayError(_("Position not found in file"), 0);
12045         return FALSE;
12046     }
12047     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12048     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12049
12050     if (pn >= 2) {
12051         if (fenMode || line[0] == '#') pn--;
12052         while (pn > 0) {
12053             /* skip positions before number pn */
12054             if (fgets(line, MSG_SIZ, f) == NULL) {
12055                 Reset(TRUE, TRUE);
12056                 DisplayError(_("Position not found in file"), 0);
12057                 return FALSE;
12058             }
12059             if (fenMode || line[0] == '#') pn--;
12060         }
12061     }
12062
12063     if (fenMode) {
12064         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12065             DisplayError(_("Bad FEN position in file"), 0);
12066             return FALSE;
12067         }
12068     } else {
12069         (void) fgets(line, MSG_SIZ, f);
12070         (void) fgets(line, MSG_SIZ, f);
12071
12072         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12073             (void) fgets(line, MSG_SIZ, f);
12074             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12075                 if (*p == ' ')
12076                   continue;
12077                 initial_position[i][j++] = CharToPiece(*p);
12078             }
12079         }
12080
12081         blackPlaysFirst = FALSE;
12082         if (!feof(f)) {
12083             (void) fgets(line, MSG_SIZ, f);
12084             if (strncmp(line, "black", strlen("black"))==0)
12085               blackPlaysFirst = TRUE;
12086         }
12087     }
12088     startedFromSetupPosition = TRUE;
12089
12090     CopyBoard(boards[0], initial_position);
12091     if (blackPlaysFirst) {
12092         currentMove = forwardMostMove = backwardMostMove = 1;
12093         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12094         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12095         CopyBoard(boards[1], initial_position);
12096         DisplayMessage("", _("Black to play"));
12097     } else {
12098         currentMove = forwardMostMove = backwardMostMove = 0;
12099         DisplayMessage("", _("White to play"));
12100     }
12101     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12102     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12103         SendToProgram("force\n", &first);
12104         SendBoard(&first, forwardMostMove);
12105     }
12106     if (appData.debugMode) {
12107 int i, j;
12108   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12109   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12110         fprintf(debugFP, "Load Position\n");
12111     }
12112
12113     if (positionNumber > 1) {
12114       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12115         DisplayTitle(line);
12116     } else {
12117         DisplayTitle(title);
12118     }
12119     gameMode = EditGame;
12120     ModeHighlight();
12121     ResetClocks();
12122     timeRemaining[0][1] = whiteTimeRemaining;
12123     timeRemaining[1][1] = blackTimeRemaining;
12124     DrawPosition(FALSE, boards[currentMove]);
12125
12126     return TRUE;
12127 }
12128
12129
12130 void
12131 CopyPlayerNameIntoFileName (char **dest, char *src)
12132 {
12133     while (*src != NULLCHAR && *src != ',') {
12134         if (*src == ' ') {
12135             *(*dest)++ = '_';
12136             src++;
12137         } else {
12138             *(*dest)++ = *src++;
12139         }
12140     }
12141 }
12142
12143 char *
12144 DefaultFileName (char *ext)
12145 {
12146     static char def[MSG_SIZ];
12147     char *p;
12148
12149     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12150         p = def;
12151         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12152         *p++ = '-';
12153         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12154         *p++ = '.';
12155         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12156     } else {
12157         def[0] = NULLCHAR;
12158     }
12159     return def;
12160 }
12161
12162 /* Save the current game to the given file */
12163 int
12164 SaveGameToFile (char *filename, int append)
12165 {
12166     FILE *f;
12167     char buf[MSG_SIZ];
12168     int result, i, t,tot=0;
12169
12170     if (strcmp(filename, "-") == 0) {
12171         return SaveGame(stdout, 0, NULL);
12172     } else {
12173         for(i=0; i<10; i++) { // upto 10 tries
12174              f = fopen(filename, append ? "a" : "w");
12175              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12176              if(f || errno != 13) break;
12177              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12178              tot += t;
12179         }
12180         if (f == NULL) {
12181             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12182             DisplayError(buf, errno);
12183             return FALSE;
12184         } else {
12185             safeStrCpy(buf, lastMsg, MSG_SIZ);
12186             DisplayMessage(_("Waiting for access to save file"), "");
12187             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12188             DisplayMessage(_("Saving game"), "");
12189             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12190             result = SaveGame(f, 0, NULL);
12191             DisplayMessage(buf, "");
12192             return result;
12193         }
12194     }
12195 }
12196
12197 char *
12198 SavePart (char *str)
12199 {
12200     static char buf[MSG_SIZ];
12201     char *p;
12202
12203     p = strchr(str, ' ');
12204     if (p == NULL) return str;
12205     strncpy(buf, str, p - str);
12206     buf[p - str] = NULLCHAR;
12207     return buf;
12208 }
12209
12210 #define PGN_MAX_LINE 75
12211
12212 #define PGN_SIDE_WHITE  0
12213 #define PGN_SIDE_BLACK  1
12214
12215 static int
12216 FindFirstMoveOutOfBook (int side)
12217 {
12218     int result = -1;
12219
12220     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12221         int index = backwardMostMove;
12222         int has_book_hit = 0;
12223
12224         if( (index % 2) != side ) {
12225             index++;
12226         }
12227
12228         while( index < forwardMostMove ) {
12229             /* Check to see if engine is in book */
12230             int depth = pvInfoList[index].depth;
12231             int score = pvInfoList[index].score;
12232             int in_book = 0;
12233
12234             if( depth <= 2 ) {
12235                 in_book = 1;
12236             }
12237             else if( score == 0 && depth == 63 ) {
12238                 in_book = 1; /* Zappa */
12239             }
12240             else if( score == 2 && depth == 99 ) {
12241                 in_book = 1; /* Abrok */
12242             }
12243
12244             has_book_hit += in_book;
12245
12246             if( ! in_book ) {
12247                 result = index;
12248
12249                 break;
12250             }
12251
12252             index += 2;
12253         }
12254     }
12255
12256     return result;
12257 }
12258
12259 void
12260 GetOutOfBookInfo (char * buf)
12261 {
12262     int oob[2];
12263     int i;
12264     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12265
12266     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12267     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12268
12269     *buf = '\0';
12270
12271     if( oob[0] >= 0 || oob[1] >= 0 ) {
12272         for( i=0; i<2; i++ ) {
12273             int idx = oob[i];
12274
12275             if( idx >= 0 ) {
12276                 if( i > 0 && oob[0] >= 0 ) {
12277                     strcat( buf, "   " );
12278                 }
12279
12280                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12281                 sprintf( buf+strlen(buf), "%s%.2f",
12282                     pvInfoList[idx].score >= 0 ? "+" : "",
12283                     pvInfoList[idx].score / 100.0 );
12284             }
12285         }
12286     }
12287 }
12288
12289 /* Save game in PGN style and close the file */
12290 int
12291 SaveGamePGN (FILE *f)
12292 {
12293     int i, offset, linelen, newblock;
12294     time_t tm;
12295 //    char *movetext;
12296     char numtext[32];
12297     int movelen, numlen, blank;
12298     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12299
12300     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12301
12302     tm = time((time_t *) NULL);
12303
12304     PrintPGNTags(f, &gameInfo);
12305
12306     if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag
12307
12308     if (backwardMostMove > 0 || startedFromSetupPosition) {
12309         char *fen = PositionToFEN(backwardMostMove, NULL);
12310         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12311         fprintf(f, "\n{--------------\n");
12312         PrintPosition(f, backwardMostMove);
12313         fprintf(f, "--------------}\n");
12314         free(fen);
12315     }
12316     else {
12317         /* [AS] Out of book annotation */
12318         if( appData.saveOutOfBookInfo ) {
12319             char buf[64];
12320
12321             GetOutOfBookInfo( buf );
12322
12323             if( buf[0] != '\0' ) {
12324                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12325             }
12326         }
12327
12328         fprintf(f, "\n");
12329     }
12330
12331     i = backwardMostMove;
12332     linelen = 0;
12333     newblock = TRUE;
12334
12335     while (i < forwardMostMove) {
12336         /* Print comments preceding this move */
12337         if (commentList[i] != NULL) {
12338             if (linelen > 0) fprintf(f, "\n");
12339             fprintf(f, "%s", commentList[i]);
12340             linelen = 0;
12341             newblock = TRUE;
12342         }
12343
12344         /* Format move number */
12345         if ((i % 2) == 0)
12346           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12347         else
12348           if (newblock)
12349             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12350           else
12351             numtext[0] = NULLCHAR;
12352
12353         numlen = strlen(numtext);
12354         newblock = FALSE;
12355
12356         /* Print move number */
12357         blank = linelen > 0 && numlen > 0;
12358         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12359             fprintf(f, "\n");
12360             linelen = 0;
12361             blank = 0;
12362         }
12363         if (blank) {
12364             fprintf(f, " ");
12365             linelen++;
12366         }
12367         fprintf(f, "%s", numtext);
12368         linelen += numlen;
12369
12370         /* Get move */
12371         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12372         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12373
12374         /* Print move */
12375         blank = linelen > 0 && movelen > 0;
12376         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12377             fprintf(f, "\n");
12378             linelen = 0;
12379             blank = 0;
12380         }
12381         if (blank) {
12382             fprintf(f, " ");
12383             linelen++;
12384         }
12385         fprintf(f, "%s", move_buffer);
12386         linelen += movelen;
12387
12388         /* [AS] Add PV info if present */
12389         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12390             /* [HGM] add time */
12391             char buf[MSG_SIZ]; int seconds;
12392
12393             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12394
12395             if( seconds <= 0)
12396               buf[0] = 0;
12397             else
12398               if( seconds < 30 )
12399                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12400               else
12401                 {
12402                   seconds = (seconds + 4)/10; // round to full seconds
12403                   if( seconds < 60 )
12404                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12405                   else
12406                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12407                 }
12408
12409             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12410                       pvInfoList[i].score >= 0 ? "+" : "",
12411                       pvInfoList[i].score / 100.0,
12412                       pvInfoList[i].depth,
12413                       buf );
12414
12415             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12416
12417             /* Print score/depth */
12418             blank = linelen > 0 && movelen > 0;
12419             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12420                 fprintf(f, "\n");
12421                 linelen = 0;
12422                 blank = 0;
12423             }
12424             if (blank) {
12425                 fprintf(f, " ");
12426                 linelen++;
12427             }
12428             fprintf(f, "%s", move_buffer);
12429             linelen += movelen;
12430         }
12431
12432         i++;
12433     }
12434
12435     /* Start a new line */
12436     if (linelen > 0) fprintf(f, "\n");
12437
12438     /* Print comments after last move */
12439     if (commentList[i] != NULL) {
12440         fprintf(f, "%s\n", commentList[i]);
12441     }
12442
12443     /* Print result */
12444     if (gameInfo.resultDetails != NULL &&
12445         gameInfo.resultDetails[0] != NULLCHAR) {
12446         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12447                 PGNResult(gameInfo.result));
12448     } else {
12449         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12450     }
12451
12452     fclose(f);
12453     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12454     return TRUE;
12455 }
12456
12457 /* Save game in old style and close the file */
12458 int
12459 SaveGameOldStyle (FILE *f)
12460 {
12461     int i, offset;
12462     time_t tm;
12463
12464     tm = time((time_t *) NULL);
12465
12466     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12467     PrintOpponents(f);
12468
12469     if (backwardMostMove > 0 || startedFromSetupPosition) {
12470         fprintf(f, "\n[--------------\n");
12471         PrintPosition(f, backwardMostMove);
12472         fprintf(f, "--------------]\n");
12473     } else {
12474         fprintf(f, "\n");
12475     }
12476
12477     i = backwardMostMove;
12478     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12479
12480     while (i < forwardMostMove) {
12481         if (commentList[i] != NULL) {
12482             fprintf(f, "[%s]\n", commentList[i]);
12483         }
12484
12485         if ((i % 2) == 1) {
12486             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12487             i++;
12488         } else {
12489             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12490             i++;
12491             if (commentList[i] != NULL) {
12492                 fprintf(f, "\n");
12493                 continue;
12494             }
12495             if (i >= forwardMostMove) {
12496                 fprintf(f, "\n");
12497                 break;
12498             }
12499             fprintf(f, "%s\n", parseList[i]);
12500             i++;
12501         }
12502     }
12503
12504     if (commentList[i] != NULL) {
12505         fprintf(f, "[%s]\n", commentList[i]);
12506     }
12507
12508     /* This isn't really the old style, but it's close enough */
12509     if (gameInfo.resultDetails != NULL &&
12510         gameInfo.resultDetails[0] != NULLCHAR) {
12511         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12512                 gameInfo.resultDetails);
12513     } else {
12514         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12515     }
12516
12517     fclose(f);
12518     return TRUE;
12519 }
12520
12521 /* Save the current game to open file f and close the file */
12522 int
12523 SaveGame (FILE *f, int dummy, char *dummy2)
12524 {
12525     if (gameMode == EditPosition) EditPositionDone(TRUE);
12526     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12527     if (appData.oldSaveStyle)
12528       return SaveGameOldStyle(f);
12529     else
12530       return SaveGamePGN(f);
12531 }
12532
12533 /* Save the current position to the given file */
12534 int
12535 SavePositionToFile (char *filename)
12536 {
12537     FILE *f;
12538     char buf[MSG_SIZ];
12539
12540     if (strcmp(filename, "-") == 0) {
12541         return SavePosition(stdout, 0, NULL);
12542     } else {
12543         f = fopen(filename, "a");
12544         if (f == NULL) {
12545             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12546             DisplayError(buf, errno);
12547             return FALSE;
12548         } else {
12549             safeStrCpy(buf, lastMsg, MSG_SIZ);
12550             DisplayMessage(_("Waiting for access to save file"), "");
12551             flock(fileno(f), LOCK_EX); // [HGM] lock
12552             DisplayMessage(_("Saving position"), "");
12553             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12554             SavePosition(f, 0, NULL);
12555             DisplayMessage(buf, "");
12556             return TRUE;
12557         }
12558     }
12559 }
12560
12561 /* Save the current position to the given open file and close the file */
12562 int
12563 SavePosition (FILE *f, int dummy, char *dummy2)
12564 {
12565     time_t tm;
12566     char *fen;
12567
12568     if (gameMode == EditPosition) EditPositionDone(TRUE);
12569     if (appData.oldSaveStyle) {
12570         tm = time((time_t *) NULL);
12571
12572         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12573         PrintOpponents(f);
12574         fprintf(f, "[--------------\n");
12575         PrintPosition(f, currentMove);
12576         fprintf(f, "--------------]\n");
12577     } else {
12578         fen = PositionToFEN(currentMove, NULL);
12579         fprintf(f, "%s\n", fen);
12580         free(fen);
12581     }
12582     fclose(f);
12583     return TRUE;
12584 }
12585
12586 void
12587 ReloadCmailMsgEvent (int unregister)
12588 {
12589 #if !WIN32
12590     static char *inFilename = NULL;
12591     static char *outFilename;
12592     int i;
12593     struct stat inbuf, outbuf;
12594     int status;
12595
12596     /* Any registered moves are unregistered if unregister is set, */
12597     /* i.e. invoked by the signal handler */
12598     if (unregister) {
12599         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12600             cmailMoveRegistered[i] = FALSE;
12601             if (cmailCommentList[i] != NULL) {
12602                 free(cmailCommentList[i]);
12603                 cmailCommentList[i] = NULL;
12604             }
12605         }
12606         nCmailMovesRegistered = 0;
12607     }
12608
12609     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12610         cmailResult[i] = CMAIL_NOT_RESULT;
12611     }
12612     nCmailResults = 0;
12613
12614     if (inFilename == NULL) {
12615         /* Because the filenames are static they only get malloced once  */
12616         /* and they never get freed                                      */
12617         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12618         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12619
12620         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12621         sprintf(outFilename, "%s.out", appData.cmailGameName);
12622     }
12623
12624     status = stat(outFilename, &outbuf);
12625     if (status < 0) {
12626         cmailMailedMove = FALSE;
12627     } else {
12628         status = stat(inFilename, &inbuf);
12629         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12630     }
12631
12632     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12633        counts the games, notes how each one terminated, etc.
12634
12635        It would be nice to remove this kludge and instead gather all
12636        the information while building the game list.  (And to keep it
12637        in the game list nodes instead of having a bunch of fixed-size
12638        parallel arrays.)  Note this will require getting each game's
12639        termination from the PGN tags, as the game list builder does
12640        not process the game moves.  --mann
12641        */
12642     cmailMsgLoaded = TRUE;
12643     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12644
12645     /* Load first game in the file or popup game menu */
12646     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12647
12648 #endif /* !WIN32 */
12649     return;
12650 }
12651
12652 int
12653 RegisterMove ()
12654 {
12655     FILE *f;
12656     char string[MSG_SIZ];
12657
12658     if (   cmailMailedMove
12659         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12660         return TRUE;            /* Allow free viewing  */
12661     }
12662
12663     /* Unregister move to ensure that we don't leave RegisterMove        */
12664     /* with the move registered when the conditions for registering no   */
12665     /* longer hold                                                       */
12666     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12667         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12668         nCmailMovesRegistered --;
12669
12670         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12671           {
12672               free(cmailCommentList[lastLoadGameNumber - 1]);
12673               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12674           }
12675     }
12676
12677     if (cmailOldMove == -1) {
12678         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12679         return FALSE;
12680     }
12681
12682     if (currentMove > cmailOldMove + 1) {
12683         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12684         return FALSE;
12685     }
12686
12687     if (currentMove < cmailOldMove) {
12688         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12689         return FALSE;
12690     }
12691
12692     if (forwardMostMove > currentMove) {
12693         /* Silently truncate extra moves */
12694         TruncateGame();
12695     }
12696
12697     if (   (currentMove == cmailOldMove + 1)
12698         || (   (currentMove == cmailOldMove)
12699             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12700                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12701         if (gameInfo.result != GameUnfinished) {
12702             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12703         }
12704
12705         if (commentList[currentMove] != NULL) {
12706             cmailCommentList[lastLoadGameNumber - 1]
12707               = StrSave(commentList[currentMove]);
12708         }
12709         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12710
12711         if (appData.debugMode)
12712           fprintf(debugFP, "Saving %s for game %d\n",
12713                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12714
12715         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12716
12717         f = fopen(string, "w");
12718         if (appData.oldSaveStyle) {
12719             SaveGameOldStyle(f); /* also closes the file */
12720
12721             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12722             f = fopen(string, "w");
12723             SavePosition(f, 0, NULL); /* also closes the file */
12724         } else {
12725             fprintf(f, "{--------------\n");
12726             PrintPosition(f, currentMove);
12727             fprintf(f, "--------------}\n\n");
12728
12729             SaveGame(f, 0, NULL); /* also closes the file*/
12730         }
12731
12732         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12733         nCmailMovesRegistered ++;
12734     } else if (nCmailGames == 1) {
12735         DisplayError(_("You have not made a move yet"), 0);
12736         return FALSE;
12737     }
12738
12739     return TRUE;
12740 }
12741
12742 void
12743 MailMoveEvent ()
12744 {
12745 #if !WIN32
12746     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12747     FILE *commandOutput;
12748     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12749     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12750     int nBuffers;
12751     int i;
12752     int archived;
12753     char *arcDir;
12754
12755     if (! cmailMsgLoaded) {
12756         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12757         return;
12758     }
12759
12760     if (nCmailGames == nCmailResults) {
12761         DisplayError(_("No unfinished games"), 0);
12762         return;
12763     }
12764
12765 #if CMAIL_PROHIBIT_REMAIL
12766     if (cmailMailedMove) {
12767       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);
12768         DisplayError(msg, 0);
12769         return;
12770     }
12771 #endif
12772
12773     if (! (cmailMailedMove || RegisterMove())) return;
12774
12775     if (   cmailMailedMove
12776         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12777       snprintf(string, MSG_SIZ, partCommandString,
12778                appData.debugMode ? " -v" : "", appData.cmailGameName);
12779         commandOutput = popen(string, "r");
12780
12781         if (commandOutput == NULL) {
12782             DisplayError(_("Failed to invoke cmail"), 0);
12783         } else {
12784             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12785                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12786             }
12787             if (nBuffers > 1) {
12788                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12789                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12790                 nBytes = MSG_SIZ - 1;
12791             } else {
12792                 (void) memcpy(msg, buffer, nBytes);
12793             }
12794             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12795
12796             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12797                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12798
12799                 archived = TRUE;
12800                 for (i = 0; i < nCmailGames; i ++) {
12801                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12802                         archived = FALSE;
12803                     }
12804                 }
12805                 if (   archived
12806                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12807                         != NULL)) {
12808                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12809                            arcDir,
12810                            appData.cmailGameName,
12811                            gameInfo.date);
12812                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12813                     cmailMsgLoaded = FALSE;
12814                 }
12815             }
12816
12817             DisplayInformation(msg);
12818             pclose(commandOutput);
12819         }
12820     } else {
12821         if ((*cmailMsg) != '\0') {
12822             DisplayInformation(cmailMsg);
12823         }
12824     }
12825
12826     return;
12827 #endif /* !WIN32 */
12828 }
12829
12830 char *
12831 CmailMsg ()
12832 {
12833 #if WIN32
12834     return NULL;
12835 #else
12836     int  prependComma = 0;
12837     char number[5];
12838     char string[MSG_SIZ];       /* Space for game-list */
12839     int  i;
12840
12841     if (!cmailMsgLoaded) return "";
12842
12843     if (cmailMailedMove) {
12844       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12845     } else {
12846         /* Create a list of games left */
12847       snprintf(string, MSG_SIZ, "[");
12848         for (i = 0; i < nCmailGames; i ++) {
12849             if (! (   cmailMoveRegistered[i]
12850                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12851                 if (prependComma) {
12852                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12853                 } else {
12854                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12855                     prependComma = 1;
12856                 }
12857
12858                 strcat(string, number);
12859             }
12860         }
12861         strcat(string, "]");
12862
12863         if (nCmailMovesRegistered + nCmailResults == 0) {
12864             switch (nCmailGames) {
12865               case 1:
12866                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12867                 break;
12868
12869               case 2:
12870                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12871                 break;
12872
12873               default:
12874                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12875                          nCmailGames);
12876                 break;
12877             }
12878         } else {
12879             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12880               case 1:
12881                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12882                          string);
12883                 break;
12884
12885               case 0:
12886                 if (nCmailResults == nCmailGames) {
12887                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12888                 } else {
12889                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12890                 }
12891                 break;
12892
12893               default:
12894                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12895                          string);
12896             }
12897         }
12898     }
12899     return cmailMsg;
12900 #endif /* WIN32 */
12901 }
12902
12903 void
12904 ResetGameEvent ()
12905 {
12906     if (gameMode == Training)
12907       SetTrainingModeOff();
12908
12909     Reset(TRUE, TRUE);
12910     cmailMsgLoaded = FALSE;
12911     if (appData.icsActive) {
12912       SendToICS(ics_prefix);
12913       SendToICS("refresh\n");
12914     }
12915 }
12916
12917 void
12918 ExitEvent (int status)
12919 {
12920     exiting++;
12921     if (exiting > 2) {
12922       /* Give up on clean exit */
12923       exit(status);
12924     }
12925     if (exiting > 1) {
12926       /* Keep trying for clean exit */
12927       return;
12928     }
12929
12930     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12931
12932     if (telnetISR != NULL) {
12933       RemoveInputSource(telnetISR);
12934     }
12935     if (icsPR != NoProc) {
12936       DestroyChildProcess(icsPR, TRUE);
12937     }
12938
12939     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12940     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12941
12942     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12943     /* make sure this other one finishes before killing it!                  */
12944     if(endingGame) { int count = 0;
12945         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12946         while(endingGame && count++ < 10) DoSleep(1);
12947         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12948     }
12949
12950     /* Kill off chess programs */
12951     if (first.pr != NoProc) {
12952         ExitAnalyzeMode();
12953
12954         DoSleep( appData.delayBeforeQuit );
12955         SendToProgram("quit\n", &first);
12956         DoSleep( appData.delayAfterQuit );
12957         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12958     }
12959     if (second.pr != NoProc) {
12960         DoSleep( appData.delayBeforeQuit );
12961         SendToProgram("quit\n", &second);
12962         DoSleep( appData.delayAfterQuit );
12963         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12964     }
12965     if (first.isr != NULL) {
12966         RemoveInputSource(first.isr);
12967     }
12968     if (second.isr != NULL) {
12969         RemoveInputSource(second.isr);
12970     }
12971
12972     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12973     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12974
12975     ShutDownFrontEnd();
12976     exit(status);
12977 }
12978
12979 void
12980 PauseEvent ()
12981 {
12982     if (appData.debugMode)
12983         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12984     if (pausing) {
12985         pausing = FALSE;
12986         ModeHighlight();
12987         if (gameMode == MachinePlaysWhite ||
12988             gameMode == MachinePlaysBlack) {
12989             StartClocks();
12990         } else {
12991             DisplayBothClocks();
12992         }
12993         if (gameMode == PlayFromGameFile) {
12994             if (appData.timeDelay >= 0)
12995                 AutoPlayGameLoop();
12996         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12997             Reset(FALSE, TRUE);
12998             SendToICS(ics_prefix);
12999             SendToICS("refresh\n");
13000         } else if (currentMove < forwardMostMove) {
13001             ForwardInner(forwardMostMove);
13002         }
13003         pauseExamInvalid = FALSE;
13004     } else {
13005         switch (gameMode) {
13006           default:
13007             return;
13008           case IcsExamining:
13009             pauseExamForwardMostMove = forwardMostMove;
13010             pauseExamInvalid = FALSE;
13011             /* fall through */
13012           case IcsObserving:
13013           case IcsPlayingWhite:
13014           case IcsPlayingBlack:
13015             pausing = TRUE;
13016             ModeHighlight();
13017             return;
13018           case PlayFromGameFile:
13019             (void) StopLoadGameTimer();
13020             pausing = TRUE;
13021             ModeHighlight();
13022             break;
13023           case BeginningOfGame:
13024             if (appData.icsActive) return;
13025             /* else fall through */
13026           case MachinePlaysWhite:
13027           case MachinePlaysBlack:
13028           case TwoMachinesPlay:
13029             if (forwardMostMove == 0)
13030               return;           /* don't pause if no one has moved */
13031             if ((gameMode == MachinePlaysWhite &&
13032                  !WhiteOnMove(forwardMostMove)) ||
13033                 (gameMode == MachinePlaysBlack &&
13034                  WhiteOnMove(forwardMostMove))) {
13035                 StopClocks();
13036             }
13037             pausing = TRUE;
13038             ModeHighlight();
13039             break;
13040         }
13041     }
13042 }
13043
13044 void
13045 EditCommentEvent ()
13046 {
13047     char title[MSG_SIZ];
13048
13049     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13050       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13051     } else {
13052       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13053                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13054                parseList[currentMove - 1]);
13055     }
13056
13057     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13058 }
13059
13060
13061 void
13062 EditTagsEvent ()
13063 {
13064     char *tags = PGNTags(&gameInfo);
13065     bookUp = FALSE;
13066     EditTagsPopUp(tags, NULL);
13067     free(tags);
13068 }
13069
13070 void
13071 AnalyzeModeEvent ()
13072 {
13073     if (appData.noChessProgram || gameMode == AnalyzeMode)
13074       return;
13075
13076     if (gameMode != AnalyzeFile) {
13077         if (!appData.icsEngineAnalyze) {
13078                EditGameEvent();
13079                if (gameMode != EditGame) return;
13080         }
13081         ResurrectChessProgram();
13082         SendToProgram("analyze\n", &first);
13083         first.analyzing = TRUE;
13084         /*first.maybeThinking = TRUE;*/
13085         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13086         EngineOutputPopUp();
13087     }
13088     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13089     pausing = FALSE;
13090     ModeHighlight();
13091     SetGameInfo();
13092
13093     StartAnalysisClock();
13094     GetTimeMark(&lastNodeCountTime);
13095     lastNodeCount = 0;
13096 }
13097
13098 void
13099 AnalyzeFileEvent ()
13100 {
13101     if (appData.noChessProgram || gameMode == AnalyzeFile)
13102       return;
13103
13104     if (gameMode != AnalyzeMode) {
13105         EditGameEvent();
13106         if (gameMode != EditGame) return;
13107         ResurrectChessProgram();
13108         SendToProgram("analyze\n", &first);
13109         first.analyzing = TRUE;
13110         /*first.maybeThinking = TRUE;*/
13111         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13112         EngineOutputPopUp();
13113     }
13114     gameMode = AnalyzeFile;
13115     pausing = FALSE;
13116     ModeHighlight();
13117     SetGameInfo();
13118
13119     StartAnalysisClock();
13120     GetTimeMark(&lastNodeCountTime);
13121     lastNodeCount = 0;
13122     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13123 }
13124
13125 void
13126 MachineWhiteEvent ()
13127 {
13128     char buf[MSG_SIZ];
13129     char *bookHit = NULL;
13130
13131     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13132       return;
13133
13134
13135     if (gameMode == PlayFromGameFile ||
13136         gameMode == TwoMachinesPlay  ||
13137         gameMode == Training         ||
13138         gameMode == AnalyzeMode      ||
13139         gameMode == EndOfGame)
13140         EditGameEvent();
13141
13142     if (gameMode == EditPosition)
13143         EditPositionDone(TRUE);
13144
13145     if (!WhiteOnMove(currentMove)) {
13146         DisplayError(_("It is not White's turn"), 0);
13147         return;
13148     }
13149
13150     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13151       ExitAnalyzeMode();
13152
13153     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13154         gameMode == AnalyzeFile)
13155         TruncateGame();
13156
13157     ResurrectChessProgram();    /* in case it isn't running */
13158     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13159         gameMode = MachinePlaysWhite;
13160         ResetClocks();
13161     } else
13162     gameMode = MachinePlaysWhite;
13163     pausing = FALSE;
13164     ModeHighlight();
13165     SetGameInfo();
13166     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13167     DisplayTitle(buf);
13168     if (first.sendName) {
13169       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13170       SendToProgram(buf, &first);
13171     }
13172     if (first.sendTime) {
13173       if (first.useColors) {
13174         SendToProgram("black\n", &first); /*gnu kludge*/
13175       }
13176       SendTimeRemaining(&first, TRUE);
13177     }
13178     if (first.useColors) {
13179       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13180     }
13181     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13182     SetMachineThinkingEnables();
13183     first.maybeThinking = TRUE;
13184     StartClocks();
13185     firstMove = FALSE;
13186
13187     if (appData.autoFlipView && !flipView) {
13188       flipView = !flipView;
13189       DrawPosition(FALSE, NULL);
13190       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13191     }
13192
13193     if(bookHit) { // [HGM] book: simulate book reply
13194         static char bookMove[MSG_SIZ]; // a bit generous?
13195
13196         programStats.nodes = programStats.depth = programStats.time =
13197         programStats.score = programStats.got_only_move = 0;
13198         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13199
13200         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13201         strcat(bookMove, bookHit);
13202         HandleMachineMove(bookMove, &first);
13203     }
13204 }
13205
13206 void
13207 MachineBlackEvent ()
13208 {
13209   char buf[MSG_SIZ];
13210   char *bookHit = NULL;
13211
13212     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13213         return;
13214
13215
13216     if (gameMode == PlayFromGameFile ||
13217         gameMode == TwoMachinesPlay  ||
13218         gameMode == Training         ||
13219         gameMode == AnalyzeMode      ||
13220         gameMode == EndOfGame)
13221         EditGameEvent();
13222
13223     if (gameMode == EditPosition)
13224         EditPositionDone(TRUE);
13225
13226     if (WhiteOnMove(currentMove)) {
13227         DisplayError(_("It is not Black's turn"), 0);
13228         return;
13229     }
13230
13231     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13232       ExitAnalyzeMode();
13233
13234     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13235         gameMode == AnalyzeFile)
13236         TruncateGame();
13237
13238     ResurrectChessProgram();    /* in case it isn't running */
13239     gameMode = MachinePlaysBlack;
13240     pausing = FALSE;
13241     ModeHighlight();
13242     SetGameInfo();
13243     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13244     DisplayTitle(buf);
13245     if (first.sendName) {
13246       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13247       SendToProgram(buf, &first);
13248     }
13249     if (first.sendTime) {
13250       if (first.useColors) {
13251         SendToProgram("white\n", &first); /*gnu kludge*/
13252       }
13253       SendTimeRemaining(&first, FALSE);
13254     }
13255     if (first.useColors) {
13256       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13257     }
13258     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13259     SetMachineThinkingEnables();
13260     first.maybeThinking = TRUE;
13261     StartClocks();
13262
13263     if (appData.autoFlipView && flipView) {
13264       flipView = !flipView;
13265       DrawPosition(FALSE, NULL);
13266       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13267     }
13268     if(bookHit) { // [HGM] book: simulate book reply
13269         static char bookMove[MSG_SIZ]; // a bit generous?
13270
13271         programStats.nodes = programStats.depth = programStats.time =
13272         programStats.score = programStats.got_only_move = 0;
13273         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13274
13275         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13276         strcat(bookMove, bookHit);
13277         HandleMachineMove(bookMove, &first);
13278     }
13279 }
13280
13281
13282 void
13283 DisplayTwoMachinesTitle ()
13284 {
13285     char buf[MSG_SIZ];
13286     if (appData.matchGames > 0) {
13287         if(appData.tourneyFile[0]) {
13288           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13289                    gameInfo.white, _("vs."), gameInfo.black,
13290                    nextGame+1, appData.matchGames+1,
13291                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13292         } else 
13293         if (first.twoMachinesColor[0] == 'w') {
13294           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13295                    gameInfo.white, _("vs."),  gameInfo.black,
13296                    first.matchWins, second.matchWins,
13297                    matchGame - 1 - (first.matchWins + second.matchWins));
13298         } else {
13299           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13300                    gameInfo.white, _("vs."), gameInfo.black,
13301                    second.matchWins, first.matchWins,
13302                    matchGame - 1 - (first.matchWins + second.matchWins));
13303         }
13304     } else {
13305       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13306     }
13307     DisplayTitle(buf);
13308 }
13309
13310 void
13311 SettingsMenuIfReady ()
13312 {
13313   if (second.lastPing != second.lastPong) {
13314     DisplayMessage("", _("Waiting for second chess program"));
13315     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13316     return;
13317   }
13318   ThawUI();
13319   DisplayMessage("", "");
13320   SettingsPopUp(&second);
13321 }
13322
13323 int
13324 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13325 {
13326     char buf[MSG_SIZ];
13327     if (cps->pr == NoProc) {
13328         StartChessProgram(cps);
13329         if (cps->protocolVersion == 1) {
13330           retry();
13331         } else {
13332           /* kludge: allow timeout for initial "feature" command */
13333           FreezeUI();
13334           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13335           DisplayMessage("", buf);
13336           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13337         }
13338         return 1;
13339     }
13340     return 0;
13341 }
13342
13343 void
13344 TwoMachinesEvent P((void))
13345 {
13346     int i;
13347     char buf[MSG_SIZ];
13348     ChessProgramState *onmove;
13349     char *bookHit = NULL;
13350     static int stalling = 0;
13351     TimeMark now;
13352     long wait;
13353
13354     if (appData.noChessProgram) return;
13355
13356     switch (gameMode) {
13357       case TwoMachinesPlay:
13358         return;
13359       case MachinePlaysWhite:
13360       case MachinePlaysBlack:
13361         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13362             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13363             return;
13364         }
13365         /* fall through */
13366       case BeginningOfGame:
13367       case PlayFromGameFile:
13368       case EndOfGame:
13369         EditGameEvent();
13370         if (gameMode != EditGame) return;
13371         break;
13372       case EditPosition:
13373         EditPositionDone(TRUE);
13374         break;
13375       case AnalyzeMode:
13376       case AnalyzeFile:
13377         ExitAnalyzeMode();
13378         break;
13379       case EditGame:
13380       default:
13381         break;
13382     }
13383
13384     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
13385         snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it
13386         if(strcmp(buf, currentDebugFile)) { // name has changed
13387             FILE *f = fopen(buf, "w");
13388             if(f) { // if opening the new file failed, just keep using the old one
13389                 ASSIGN(currentDebugFile, buf);
13390                 fclose(debugFP);
13391                 debugFP = f;
13392             }
13393         }
13394     }
13395 //    forwardMostMove = currentMove;
13396     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13397
13398     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13399
13400     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13401     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13402       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13403       return;
13404     }
13405     if(!stalling) {
13406       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13407       SendToProgram("force\n", &second);
13408       stalling = 1;
13409       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13410       return;
13411     }
13412     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13413     if(appData.matchPause>10000 || appData.matchPause<10)
13414                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13415     wait = SubtractTimeMarks(&now, &pauseStart);
13416     if(wait < appData.matchPause) {
13417         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13418         return;
13419     }
13420     // we are now committed to starting the game
13421     stalling = 0;
13422     DisplayMessage("", "");
13423     if (startedFromSetupPosition) {
13424         SendBoard(&second, backwardMostMove);
13425     if (appData.debugMode) {
13426         fprintf(debugFP, "Two Machines\n");
13427     }
13428     }
13429     for (i = backwardMostMove; i < forwardMostMove; i++) {
13430         SendMoveToProgram(i, &second);
13431     }
13432
13433     gameMode = TwoMachinesPlay;
13434     pausing = FALSE;
13435     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13436     SetGameInfo();
13437     DisplayTwoMachinesTitle();
13438     firstMove = TRUE;
13439     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13440         onmove = &first;
13441     } else {
13442         onmove = &second;
13443     }
13444     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13445     SendToProgram(first.computerString, &first);
13446     if (first.sendName) {
13447       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13448       SendToProgram(buf, &first);
13449     }
13450     SendToProgram(second.computerString, &second);
13451     if (second.sendName) {
13452       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13453       SendToProgram(buf, &second);
13454     }
13455
13456     ResetClocks();
13457     if (!first.sendTime || !second.sendTime) {
13458         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13459         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13460     }
13461     if (onmove->sendTime) {
13462       if (onmove->useColors) {
13463         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13464       }
13465       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13466     }
13467     if (onmove->useColors) {
13468       SendToProgram(onmove->twoMachinesColor, onmove);
13469     }
13470     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13471 //    SendToProgram("go\n", onmove);
13472     onmove->maybeThinking = TRUE;
13473     SetMachineThinkingEnables();
13474
13475     StartClocks();
13476
13477     if(bookHit) { // [HGM] book: simulate book reply
13478         static char bookMove[MSG_SIZ]; // a bit generous?
13479
13480         programStats.nodes = programStats.depth = programStats.time =
13481         programStats.score = programStats.got_only_move = 0;
13482         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13483
13484         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13485         strcat(bookMove, bookHit);
13486         savedMessage = bookMove; // args for deferred call
13487         savedState = onmove;
13488         ScheduleDelayedEvent(DeferredBookMove, 1);
13489     }
13490 }
13491
13492 void
13493 TrainingEvent ()
13494 {
13495     if (gameMode == Training) {
13496       SetTrainingModeOff();
13497       gameMode = PlayFromGameFile;
13498       DisplayMessage("", _("Training mode off"));
13499     } else {
13500       gameMode = Training;
13501       animateTraining = appData.animate;
13502
13503       /* make sure we are not already at the end of the game */
13504       if (currentMove < forwardMostMove) {
13505         SetTrainingModeOn();
13506         DisplayMessage("", _("Training mode on"));
13507       } else {
13508         gameMode = PlayFromGameFile;
13509         DisplayError(_("Already at end of game"), 0);
13510       }
13511     }
13512     ModeHighlight();
13513 }
13514
13515 void
13516 IcsClientEvent ()
13517 {
13518     if (!appData.icsActive) return;
13519     switch (gameMode) {
13520       case IcsPlayingWhite:
13521       case IcsPlayingBlack:
13522       case IcsObserving:
13523       case IcsIdle:
13524       case BeginningOfGame:
13525       case IcsExamining:
13526         return;
13527
13528       case EditGame:
13529         break;
13530
13531       case EditPosition:
13532         EditPositionDone(TRUE);
13533         break;
13534
13535       case AnalyzeMode:
13536       case AnalyzeFile:
13537         ExitAnalyzeMode();
13538         break;
13539
13540       default:
13541         EditGameEvent();
13542         break;
13543     }
13544
13545     gameMode = IcsIdle;
13546     ModeHighlight();
13547     return;
13548 }
13549
13550 void
13551 EditGameEvent ()
13552 {
13553     int i;
13554
13555     switch (gameMode) {
13556       case Training:
13557         SetTrainingModeOff();
13558         break;
13559       case MachinePlaysWhite:
13560       case MachinePlaysBlack:
13561       case BeginningOfGame:
13562         SendToProgram("force\n", &first);
13563         SetUserThinkingEnables();
13564         break;
13565       case PlayFromGameFile:
13566         (void) StopLoadGameTimer();
13567         if (gameFileFP != NULL) {
13568             gameFileFP = NULL;
13569         }
13570         break;
13571       case EditPosition:
13572         EditPositionDone(TRUE);
13573         break;
13574       case AnalyzeMode:
13575       case AnalyzeFile:
13576         ExitAnalyzeMode();
13577         SendToProgram("force\n", &first);
13578         break;
13579       case TwoMachinesPlay:
13580         GameEnds(EndOfFile, NULL, GE_PLAYER);
13581         ResurrectChessProgram();
13582         SetUserThinkingEnables();
13583         break;
13584       case EndOfGame:
13585         ResurrectChessProgram();
13586         break;
13587       case IcsPlayingBlack:
13588       case IcsPlayingWhite:
13589         DisplayError(_("Warning: You are still playing a game"), 0);
13590         break;
13591       case IcsObserving:
13592         DisplayError(_("Warning: You are still observing a game"), 0);
13593         break;
13594       case IcsExamining:
13595         DisplayError(_("Warning: You are still examining a game"), 0);
13596         break;
13597       case IcsIdle:
13598         break;
13599       case EditGame:
13600       default:
13601         return;
13602     }
13603
13604     pausing = FALSE;
13605     StopClocks();
13606     first.offeredDraw = second.offeredDraw = 0;
13607
13608     if (gameMode == PlayFromGameFile) {
13609         whiteTimeRemaining = timeRemaining[0][currentMove];
13610         blackTimeRemaining = timeRemaining[1][currentMove];
13611         DisplayTitle("");
13612     }
13613
13614     if (gameMode == MachinePlaysWhite ||
13615         gameMode == MachinePlaysBlack ||
13616         gameMode == TwoMachinesPlay ||
13617         gameMode == EndOfGame) {
13618         i = forwardMostMove;
13619         while (i > currentMove) {
13620             SendToProgram("undo\n", &first);
13621             i--;
13622         }
13623         if(!adjustedClock) {
13624         whiteTimeRemaining = timeRemaining[0][currentMove];
13625         blackTimeRemaining = timeRemaining[1][currentMove];
13626         DisplayBothClocks();
13627         }
13628         if (whiteFlag || blackFlag) {
13629             whiteFlag = blackFlag = 0;
13630         }
13631         DisplayTitle("");
13632     }
13633
13634     gameMode = EditGame;
13635     ModeHighlight();
13636     SetGameInfo();
13637 }
13638
13639
13640 void
13641 EditPositionEvent ()
13642 {
13643     if (gameMode == EditPosition) {
13644         EditGameEvent();
13645         return;
13646     }
13647
13648     EditGameEvent();
13649     if (gameMode != EditGame) return;
13650
13651     gameMode = EditPosition;
13652     ModeHighlight();
13653     SetGameInfo();
13654     if (currentMove > 0)
13655       CopyBoard(boards[0], boards[currentMove]);
13656
13657     blackPlaysFirst = !WhiteOnMove(currentMove);
13658     ResetClocks();
13659     currentMove = forwardMostMove = backwardMostMove = 0;
13660     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13661     DisplayMove(-1);
13662 }
13663
13664 void
13665 ExitAnalyzeMode ()
13666 {
13667     /* [DM] icsEngineAnalyze - possible call from other functions */
13668     if (appData.icsEngineAnalyze) {
13669         appData.icsEngineAnalyze = FALSE;
13670
13671         DisplayMessage("",_("Close ICS engine analyze..."));
13672     }
13673     if (first.analysisSupport && first.analyzing) {
13674       SendToProgram("exit\n", &first);
13675       first.analyzing = FALSE;
13676     }
13677     thinkOutput[0] = NULLCHAR;
13678 }
13679
13680 void
13681 EditPositionDone (Boolean fakeRights)
13682 {
13683     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13684
13685     startedFromSetupPosition = TRUE;
13686     InitChessProgram(&first, FALSE);
13687     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13688       boards[0][EP_STATUS] = EP_NONE;
13689       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13690     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13691         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13692         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13693       } else boards[0][CASTLING][2] = NoRights;
13694     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13695         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13696         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13697       } else boards[0][CASTLING][5] = NoRights;
13698     }
13699     SendToProgram("force\n", &first);
13700     if (blackPlaysFirst) {
13701         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13702         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13703         currentMove = forwardMostMove = backwardMostMove = 1;
13704         CopyBoard(boards[1], boards[0]);
13705     } else {
13706         currentMove = forwardMostMove = backwardMostMove = 0;
13707     }
13708     SendBoard(&first, forwardMostMove);
13709     if (appData.debugMode) {
13710         fprintf(debugFP, "EditPosDone\n");
13711     }
13712     DisplayTitle("");
13713     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13714     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13715     gameMode = EditGame;
13716     ModeHighlight();
13717     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13718     ClearHighlights(); /* [AS] */
13719 }
13720
13721 /* Pause for `ms' milliseconds */
13722 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13723 void
13724 TimeDelay (long ms)
13725 {
13726     TimeMark m1, m2;
13727
13728     GetTimeMark(&m1);
13729     do {
13730         GetTimeMark(&m2);
13731     } while (SubtractTimeMarks(&m2, &m1) < ms);
13732 }
13733
13734 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13735 void
13736 SendMultiLineToICS (char *buf)
13737 {
13738     char temp[MSG_SIZ+1], *p;
13739     int len;
13740
13741     len = strlen(buf);
13742     if (len > MSG_SIZ)
13743       len = MSG_SIZ;
13744
13745     strncpy(temp, buf, len);
13746     temp[len] = 0;
13747
13748     p = temp;
13749     while (*p) {
13750         if (*p == '\n' || *p == '\r')
13751           *p = ' ';
13752         ++p;
13753     }
13754
13755     strcat(temp, "\n");
13756     SendToICS(temp);
13757     SendToPlayer(temp, strlen(temp));
13758 }
13759
13760 void
13761 SetWhiteToPlayEvent ()
13762 {
13763     if (gameMode == EditPosition) {
13764         blackPlaysFirst = FALSE;
13765         DisplayBothClocks();    /* works because currentMove is 0 */
13766     } else if (gameMode == IcsExamining) {
13767         SendToICS(ics_prefix);
13768         SendToICS("tomove white\n");
13769     }
13770 }
13771
13772 void
13773 SetBlackToPlayEvent ()
13774 {
13775     if (gameMode == EditPosition) {
13776         blackPlaysFirst = TRUE;
13777         currentMove = 1;        /* kludge */
13778         DisplayBothClocks();
13779         currentMove = 0;
13780     } else if (gameMode == IcsExamining) {
13781         SendToICS(ics_prefix);
13782         SendToICS("tomove black\n");
13783     }
13784 }
13785
13786 void
13787 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13788 {
13789     char buf[MSG_SIZ];
13790     ChessSquare piece = boards[0][y][x];
13791
13792     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13793
13794     switch (selection) {
13795       case ClearBoard:
13796         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13797             SendToICS(ics_prefix);
13798             SendToICS("bsetup clear\n");
13799         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13800             SendToICS(ics_prefix);
13801             SendToICS("clearboard\n");
13802         } else {
13803             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13804                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13805                 for (y = 0; y < BOARD_HEIGHT; y++) {
13806                     if (gameMode == IcsExamining) {
13807                         if (boards[currentMove][y][x] != EmptySquare) {
13808                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13809                                     AAA + x, ONE + y);
13810                             SendToICS(buf);
13811                         }
13812                     } else {
13813                         boards[0][y][x] = p;
13814                     }
13815                 }
13816             }
13817         }
13818         if (gameMode == EditPosition) {
13819             DrawPosition(FALSE, boards[0]);
13820         }
13821         break;
13822
13823       case WhitePlay:
13824         SetWhiteToPlayEvent();
13825         break;
13826
13827       case BlackPlay:
13828         SetBlackToPlayEvent();
13829         break;
13830
13831       case EmptySquare:
13832         if (gameMode == IcsExamining) {
13833             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13834             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13835             SendToICS(buf);
13836         } else {
13837             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13838                 if(x == BOARD_LEFT-2) {
13839                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13840                     boards[0][y][1] = 0;
13841                 } else
13842                 if(x == BOARD_RGHT+1) {
13843                     if(y >= gameInfo.holdingsSize) break;
13844                     boards[0][y][BOARD_WIDTH-2] = 0;
13845                 } else break;
13846             }
13847             boards[0][y][x] = EmptySquare;
13848             DrawPosition(FALSE, boards[0]);
13849         }
13850         break;
13851
13852       case PromotePiece:
13853         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13854            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13855             selection = (ChessSquare) (PROMOTED piece);
13856         } else if(piece == EmptySquare) selection = WhiteSilver;
13857         else selection = (ChessSquare)((int)piece - 1);
13858         goto defaultlabel;
13859
13860       case DemotePiece:
13861         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13862            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13863             selection = (ChessSquare) (DEMOTED piece);
13864         } else if(piece == EmptySquare) selection = BlackSilver;
13865         else selection = (ChessSquare)((int)piece + 1);
13866         goto defaultlabel;
13867
13868       case WhiteQueen:
13869       case BlackQueen:
13870         if(gameInfo.variant == VariantShatranj ||
13871            gameInfo.variant == VariantXiangqi  ||
13872            gameInfo.variant == VariantCourier  ||
13873            gameInfo.variant == VariantMakruk     )
13874             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13875         goto defaultlabel;
13876
13877       case WhiteKing:
13878       case BlackKing:
13879         if(gameInfo.variant == VariantXiangqi)
13880             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13881         if(gameInfo.variant == VariantKnightmate)
13882             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13883       default:
13884         defaultlabel:
13885         if (gameMode == IcsExamining) {
13886             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13887             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13888                      PieceToChar(selection), AAA + x, ONE + y);
13889             SendToICS(buf);
13890         } else {
13891             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13892                 int n;
13893                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13894                     n = PieceToNumber(selection - BlackPawn);
13895                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13896                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13897                     boards[0][BOARD_HEIGHT-1-n][1]++;
13898                 } else
13899                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13900                     n = PieceToNumber(selection);
13901                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13902                     boards[0][n][BOARD_WIDTH-1] = selection;
13903                     boards[0][n][BOARD_WIDTH-2]++;
13904                 }
13905             } else
13906             boards[0][y][x] = selection;
13907             DrawPosition(TRUE, boards[0]);
13908         }
13909         break;
13910     }
13911 }
13912
13913
13914 void
13915 DropMenuEvent (ChessSquare selection, int x, int y)
13916 {
13917     ChessMove moveType;
13918
13919     switch (gameMode) {
13920       case IcsPlayingWhite:
13921       case MachinePlaysBlack:
13922         if (!WhiteOnMove(currentMove)) {
13923             DisplayMoveError(_("It is Black's turn"));
13924             return;
13925         }
13926         moveType = WhiteDrop;
13927         break;
13928       case IcsPlayingBlack:
13929       case MachinePlaysWhite:
13930         if (WhiteOnMove(currentMove)) {
13931             DisplayMoveError(_("It is White's turn"));
13932             return;
13933         }
13934         moveType = BlackDrop;
13935         break;
13936       case EditGame:
13937         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13938         break;
13939       default:
13940         return;
13941     }
13942
13943     if (moveType == BlackDrop && selection < BlackPawn) {
13944       selection = (ChessSquare) ((int) selection
13945                                  + (int) BlackPawn - (int) WhitePawn);
13946     }
13947     if (boards[currentMove][y][x] != EmptySquare) {
13948         DisplayMoveError(_("That square is occupied"));
13949         return;
13950     }
13951
13952     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13953 }
13954
13955 void
13956 AcceptEvent ()
13957 {
13958     /* Accept a pending offer of any kind from opponent */
13959
13960     if (appData.icsActive) {
13961         SendToICS(ics_prefix);
13962         SendToICS("accept\n");
13963     } else if (cmailMsgLoaded) {
13964         if (currentMove == cmailOldMove &&
13965             commentList[cmailOldMove] != NULL &&
13966             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13967                    "Black offers a draw" : "White offers a draw")) {
13968             TruncateGame();
13969             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13970             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13971         } else {
13972             DisplayError(_("There is no pending offer on this move"), 0);
13973             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13974         }
13975     } else {
13976         /* Not used for offers from chess program */
13977     }
13978 }
13979
13980 void
13981 DeclineEvent ()
13982 {
13983     /* Decline a pending offer of any kind from opponent */
13984
13985     if (appData.icsActive) {
13986         SendToICS(ics_prefix);
13987         SendToICS("decline\n");
13988     } else if (cmailMsgLoaded) {
13989         if (currentMove == cmailOldMove &&
13990             commentList[cmailOldMove] != NULL &&
13991             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13992                    "Black offers a draw" : "White offers a draw")) {
13993 #ifdef NOTDEF
13994             AppendComment(cmailOldMove, "Draw declined", TRUE);
13995             DisplayComment(cmailOldMove - 1, "Draw declined");
13996 #endif /*NOTDEF*/
13997         } else {
13998             DisplayError(_("There is no pending offer on this move"), 0);
13999         }
14000     } else {
14001         /* Not used for offers from chess program */
14002     }
14003 }
14004
14005 void
14006 RematchEvent ()
14007 {
14008     /* Issue ICS rematch command */
14009     if (appData.icsActive) {
14010         SendToICS(ics_prefix);
14011         SendToICS("rematch\n");
14012     }
14013 }
14014
14015 void
14016 CallFlagEvent ()
14017 {
14018     /* Call your opponent's flag (claim a win on time) */
14019     if (appData.icsActive) {
14020         SendToICS(ics_prefix);
14021         SendToICS("flag\n");
14022     } else {
14023         switch (gameMode) {
14024           default:
14025             return;
14026           case MachinePlaysWhite:
14027             if (whiteFlag) {
14028                 if (blackFlag)
14029                   GameEnds(GameIsDrawn, "Both players ran out of time",
14030                            GE_PLAYER);
14031                 else
14032                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14033             } else {
14034                 DisplayError(_("Your opponent is not out of time"), 0);
14035             }
14036             break;
14037           case MachinePlaysBlack:
14038             if (blackFlag) {
14039                 if (whiteFlag)
14040                   GameEnds(GameIsDrawn, "Both players ran out of time",
14041                            GE_PLAYER);
14042                 else
14043                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14044             } else {
14045                 DisplayError(_("Your opponent is not out of time"), 0);
14046             }
14047             break;
14048         }
14049     }
14050 }
14051
14052 void
14053 ClockClick (int which)
14054 {       // [HGM] code moved to back-end from winboard.c
14055         if(which) { // black clock
14056           if (gameMode == EditPosition || gameMode == IcsExamining) {
14057             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14058             SetBlackToPlayEvent();
14059           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14060           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14061           } else if (shiftKey) {
14062             AdjustClock(which, -1);
14063           } else if (gameMode == IcsPlayingWhite ||
14064                      gameMode == MachinePlaysBlack) {
14065             CallFlagEvent();
14066           }
14067         } else { // white clock
14068           if (gameMode == EditPosition || gameMode == IcsExamining) {
14069             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14070             SetWhiteToPlayEvent();
14071           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14072           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14073           } else if (shiftKey) {
14074             AdjustClock(which, -1);
14075           } else if (gameMode == IcsPlayingBlack ||
14076                    gameMode == MachinePlaysWhite) {
14077             CallFlagEvent();
14078           }
14079         }
14080 }
14081
14082 void
14083 DrawEvent ()
14084 {
14085     /* Offer draw or accept pending draw offer from opponent */
14086
14087     if (appData.icsActive) {
14088         /* Note: tournament rules require draw offers to be
14089            made after you make your move but before you punch
14090            your clock.  Currently ICS doesn't let you do that;
14091            instead, you immediately punch your clock after making
14092            a move, but you can offer a draw at any time. */
14093
14094         SendToICS(ics_prefix);
14095         SendToICS("draw\n");
14096         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14097     } else if (cmailMsgLoaded) {
14098         if (currentMove == cmailOldMove &&
14099             commentList[cmailOldMove] != NULL &&
14100             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14101                    "Black offers a draw" : "White offers a draw")) {
14102             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14103             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14104         } else if (currentMove == cmailOldMove + 1) {
14105             char *offer = WhiteOnMove(cmailOldMove) ?
14106               "White offers a draw" : "Black offers a draw";
14107             AppendComment(currentMove, offer, TRUE);
14108             DisplayComment(currentMove - 1, offer);
14109             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14110         } else {
14111             DisplayError(_("You must make your move before offering a draw"), 0);
14112             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14113         }
14114     } else if (first.offeredDraw) {
14115         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14116     } else {
14117         if (first.sendDrawOffers) {
14118             SendToProgram("draw\n", &first);
14119             userOfferedDraw = TRUE;
14120         }
14121     }
14122 }
14123
14124 void
14125 AdjournEvent ()
14126 {
14127     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14128
14129     if (appData.icsActive) {
14130         SendToICS(ics_prefix);
14131         SendToICS("adjourn\n");
14132     } else {
14133         /* Currently GNU Chess doesn't offer or accept Adjourns */
14134     }
14135 }
14136
14137
14138 void
14139 AbortEvent ()
14140 {
14141     /* Offer Abort or accept pending Abort offer from opponent */
14142
14143     if (appData.icsActive) {
14144         SendToICS(ics_prefix);
14145         SendToICS("abort\n");
14146     } else {
14147         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14148     }
14149 }
14150
14151 void
14152 ResignEvent ()
14153 {
14154     /* Resign.  You can do this even if it's not your turn. */
14155
14156     if (appData.icsActive) {
14157         SendToICS(ics_prefix);
14158         SendToICS("resign\n");
14159     } else {
14160         switch (gameMode) {
14161           case MachinePlaysWhite:
14162             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14163             break;
14164           case MachinePlaysBlack:
14165             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14166             break;
14167           case EditGame:
14168             if (cmailMsgLoaded) {
14169                 TruncateGame();
14170                 if (WhiteOnMove(cmailOldMove)) {
14171                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14172                 } else {
14173                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14174                 }
14175                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14176             }
14177             break;
14178           default:
14179             break;
14180         }
14181     }
14182 }
14183
14184
14185 void
14186 StopObservingEvent ()
14187 {
14188     /* Stop observing current games */
14189     SendToICS(ics_prefix);
14190     SendToICS("unobserve\n");
14191 }
14192
14193 void
14194 StopExaminingEvent ()
14195 {
14196     /* Stop observing current game */
14197     SendToICS(ics_prefix);
14198     SendToICS("unexamine\n");
14199 }
14200
14201 void
14202 ForwardInner (int target)
14203 {
14204     int limit; int oldSeekGraphUp = seekGraphUp;
14205
14206     if (appData.debugMode)
14207         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14208                 target, currentMove, forwardMostMove);
14209
14210     if (gameMode == EditPosition)
14211       return;
14212
14213     seekGraphUp = FALSE;
14214     MarkTargetSquares(1);
14215
14216     if (gameMode == PlayFromGameFile && !pausing)
14217       PauseEvent();
14218
14219     if (gameMode == IcsExamining && pausing)
14220       limit = pauseExamForwardMostMove;
14221     else
14222       limit = forwardMostMove;
14223
14224     if (target > limit) target = limit;
14225
14226     if (target > 0 && moveList[target - 1][0]) {
14227         int fromX, fromY, toX, toY;
14228         toX = moveList[target - 1][2] - AAA;
14229         toY = moveList[target - 1][3] - ONE;
14230         if (moveList[target - 1][1] == '@') {
14231             if (appData.highlightLastMove) {
14232                 SetHighlights(-1, -1, toX, toY);
14233             }
14234         } else {
14235             fromX = moveList[target - 1][0] - AAA;
14236             fromY = moveList[target - 1][1] - ONE;
14237             if (target == currentMove + 1) {
14238                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14239             }
14240             if (appData.highlightLastMove) {
14241                 SetHighlights(fromX, fromY, toX, toY);
14242             }
14243         }
14244     }
14245     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14246         gameMode == Training || gameMode == PlayFromGameFile ||
14247         gameMode == AnalyzeFile) {
14248         while (currentMove < target) {
14249             SendMoveToProgram(currentMove++, &first);
14250         }
14251     } else {
14252         currentMove = target;
14253     }
14254
14255     if (gameMode == EditGame || gameMode == EndOfGame) {
14256         whiteTimeRemaining = timeRemaining[0][currentMove];
14257         blackTimeRemaining = timeRemaining[1][currentMove];
14258     }
14259     DisplayBothClocks();
14260     DisplayMove(currentMove - 1);
14261     DrawPosition(oldSeekGraphUp, boards[currentMove]);
14262     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14263     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14264         DisplayComment(currentMove - 1, commentList[currentMove]);
14265     }
14266 }
14267
14268
14269 void
14270 ForwardEvent ()
14271 {
14272     if (gameMode == IcsExamining && !pausing) {
14273         SendToICS(ics_prefix);
14274         SendToICS("forward\n");
14275     } else {
14276         ForwardInner(currentMove + 1);
14277     }
14278 }
14279
14280 void
14281 ToEndEvent ()
14282 {
14283     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14284         /* to optimze, we temporarily turn off analysis mode while we feed
14285          * the remaining moves to the engine. Otherwise we get analysis output
14286          * after each move.
14287          */
14288         if (first.analysisSupport) {
14289           SendToProgram("exit\nforce\n", &first);
14290           first.analyzing = FALSE;
14291         }
14292     }
14293
14294     if (gameMode == IcsExamining && !pausing) {
14295         SendToICS(ics_prefix);
14296         SendToICS("forward 999999\n");
14297     } else {
14298         ForwardInner(forwardMostMove);
14299     }
14300
14301     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14302         /* we have fed all the moves, so reactivate analysis mode */
14303         SendToProgram("analyze\n", &first);
14304         first.analyzing = TRUE;
14305         /*first.maybeThinking = TRUE;*/
14306         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14307     }
14308 }
14309
14310 void
14311 BackwardInner (int target)
14312 {
14313     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14314
14315     if (appData.debugMode)
14316         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14317                 target, currentMove, forwardMostMove);
14318
14319     if (gameMode == EditPosition) return;
14320     seekGraphUp = FALSE;
14321     MarkTargetSquares(1);
14322     if (currentMove <= backwardMostMove) {
14323         ClearHighlights();
14324         DrawPosition(full_redraw, boards[currentMove]);
14325         return;
14326     }
14327     if (gameMode == PlayFromGameFile && !pausing)
14328       PauseEvent();
14329
14330     if (moveList[target][0]) {
14331         int fromX, fromY, toX, toY;
14332         toX = moveList[target][2] - AAA;
14333         toY = moveList[target][3] - ONE;
14334         if (moveList[target][1] == '@') {
14335             if (appData.highlightLastMove) {
14336                 SetHighlights(-1, -1, toX, toY);
14337             }
14338         } else {
14339             fromX = moveList[target][0] - AAA;
14340             fromY = moveList[target][1] - ONE;
14341             if (target == currentMove - 1) {
14342                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14343             }
14344             if (appData.highlightLastMove) {
14345                 SetHighlights(fromX, fromY, toX, toY);
14346             }
14347         }
14348     }
14349     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14350         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14351         while (currentMove > target) {
14352             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14353                 // null move cannot be undone. Reload program with move history before it.
14354                 int i;
14355                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14356                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14357                 }
14358                 SendBoard(&first, i); 
14359                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14360                 break;
14361             }
14362             SendToProgram("undo\n", &first);
14363             currentMove--;
14364         }
14365     } else {
14366         currentMove = target;
14367     }
14368
14369     if (gameMode == EditGame || gameMode == EndOfGame) {
14370         whiteTimeRemaining = timeRemaining[0][currentMove];
14371         blackTimeRemaining = timeRemaining[1][currentMove];
14372     }
14373     DisplayBothClocks();
14374     DisplayMove(currentMove - 1);
14375     DrawPosition(full_redraw, boards[currentMove]);
14376     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14377     // [HGM] PV info: routine tests if comment empty
14378     DisplayComment(currentMove - 1, commentList[currentMove]);
14379 }
14380
14381 void
14382 BackwardEvent ()
14383 {
14384     if (gameMode == IcsExamining && !pausing) {
14385         SendToICS(ics_prefix);
14386         SendToICS("backward\n");
14387     } else {
14388         BackwardInner(currentMove - 1);
14389     }
14390 }
14391
14392 void
14393 ToStartEvent ()
14394 {
14395     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14396         /* to optimize, we temporarily turn off analysis mode while we undo
14397          * all the moves. Otherwise we get analysis output after each undo.
14398          */
14399         if (first.analysisSupport) {
14400           SendToProgram("exit\nforce\n", &first);
14401           first.analyzing = FALSE;
14402         }
14403     }
14404
14405     if (gameMode == IcsExamining && !pausing) {
14406         SendToICS(ics_prefix);
14407         SendToICS("backward 999999\n");
14408     } else {
14409         BackwardInner(backwardMostMove);
14410     }
14411
14412     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14413         /* we have fed all the moves, so reactivate analysis mode */
14414         SendToProgram("analyze\n", &first);
14415         first.analyzing = TRUE;
14416         /*first.maybeThinking = TRUE;*/
14417         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14418     }
14419 }
14420
14421 void
14422 ToNrEvent (int to)
14423 {
14424   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14425   if (to >= forwardMostMove) to = forwardMostMove;
14426   if (to <= backwardMostMove) to = backwardMostMove;
14427   if (to < currentMove) {
14428     BackwardInner(to);
14429   } else {
14430     ForwardInner(to);
14431   }
14432 }
14433
14434 void
14435 RevertEvent (Boolean annotate)
14436 {
14437     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14438         return;
14439     }
14440     if (gameMode != IcsExamining) {
14441         DisplayError(_("You are not examining a game"), 0);
14442         return;
14443     }
14444     if (pausing) {
14445         DisplayError(_("You can't revert while pausing"), 0);
14446         return;
14447     }
14448     SendToICS(ics_prefix);
14449     SendToICS("revert\n");
14450 }
14451
14452 void
14453 RetractMoveEvent ()
14454 {
14455     switch (gameMode) {
14456       case MachinePlaysWhite:
14457       case MachinePlaysBlack:
14458         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14459             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14460             return;
14461         }
14462         if (forwardMostMove < 2) return;
14463         currentMove = forwardMostMove = forwardMostMove - 2;
14464         whiteTimeRemaining = timeRemaining[0][currentMove];
14465         blackTimeRemaining = timeRemaining[1][currentMove];
14466         DisplayBothClocks();
14467         DisplayMove(currentMove - 1);
14468         ClearHighlights();/*!! could figure this out*/
14469         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14470         SendToProgram("remove\n", &first);
14471         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14472         break;
14473
14474       case BeginningOfGame:
14475       default:
14476         break;
14477
14478       case IcsPlayingWhite:
14479       case IcsPlayingBlack:
14480         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14481             SendToICS(ics_prefix);
14482             SendToICS("takeback 2\n");
14483         } else {
14484             SendToICS(ics_prefix);
14485             SendToICS("takeback 1\n");
14486         }
14487         break;
14488     }
14489 }
14490
14491 void
14492 MoveNowEvent ()
14493 {
14494     ChessProgramState *cps;
14495
14496     switch (gameMode) {
14497       case MachinePlaysWhite:
14498         if (!WhiteOnMove(forwardMostMove)) {
14499             DisplayError(_("It is your turn"), 0);
14500             return;
14501         }
14502         cps = &first;
14503         break;
14504       case MachinePlaysBlack:
14505         if (WhiteOnMove(forwardMostMove)) {
14506             DisplayError(_("It is your turn"), 0);
14507             return;
14508         }
14509         cps = &first;
14510         break;
14511       case TwoMachinesPlay:
14512         if (WhiteOnMove(forwardMostMove) ==
14513             (first.twoMachinesColor[0] == 'w')) {
14514             cps = &first;
14515         } else {
14516             cps = &second;
14517         }
14518         break;
14519       case BeginningOfGame:
14520       default:
14521         return;
14522     }
14523     SendToProgram("?\n", cps);
14524 }
14525
14526 void
14527 TruncateGameEvent ()
14528 {
14529     EditGameEvent();
14530     if (gameMode != EditGame) return;
14531     TruncateGame();
14532 }
14533
14534 void
14535 TruncateGame ()
14536 {
14537     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14538     if (forwardMostMove > currentMove) {
14539         if (gameInfo.resultDetails != NULL) {
14540             free(gameInfo.resultDetails);
14541             gameInfo.resultDetails = NULL;
14542             gameInfo.result = GameUnfinished;
14543         }
14544         forwardMostMove = currentMove;
14545         HistorySet(parseList, backwardMostMove, forwardMostMove,
14546                    currentMove-1);
14547     }
14548 }
14549
14550 void
14551 HintEvent ()
14552 {
14553     if (appData.noChessProgram) return;
14554     switch (gameMode) {
14555       case MachinePlaysWhite:
14556         if (WhiteOnMove(forwardMostMove)) {
14557             DisplayError(_("Wait until your turn"), 0);
14558             return;
14559         }
14560         break;
14561       case BeginningOfGame:
14562       case MachinePlaysBlack:
14563         if (!WhiteOnMove(forwardMostMove)) {
14564             DisplayError(_("Wait until your turn"), 0);
14565             return;
14566         }
14567         break;
14568       default:
14569         DisplayError(_("No hint available"), 0);
14570         return;
14571     }
14572     SendToProgram("hint\n", &first);
14573     hintRequested = TRUE;
14574 }
14575
14576 void
14577 BookEvent ()
14578 {
14579     if (appData.noChessProgram) return;
14580     switch (gameMode) {
14581       case MachinePlaysWhite:
14582         if (WhiteOnMove(forwardMostMove)) {
14583             DisplayError(_("Wait until your turn"), 0);
14584             return;
14585         }
14586         break;
14587       case BeginningOfGame:
14588       case MachinePlaysBlack:
14589         if (!WhiteOnMove(forwardMostMove)) {
14590             DisplayError(_("Wait until your turn"), 0);
14591             return;
14592         }
14593         break;
14594       case EditPosition:
14595         EditPositionDone(TRUE);
14596         break;
14597       case TwoMachinesPlay:
14598         return;
14599       default:
14600         break;
14601     }
14602     SendToProgram("bk\n", &first);
14603     bookOutput[0] = NULLCHAR;
14604     bookRequested = TRUE;
14605 }
14606
14607 void
14608 AboutGameEvent ()
14609 {
14610     char *tags = PGNTags(&gameInfo);
14611     TagsPopUp(tags, CmailMsg());
14612     free(tags);
14613 }
14614
14615 /* end button procedures */
14616
14617 void
14618 PrintPosition (FILE *fp, int move)
14619 {
14620     int i, j;
14621
14622     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14623         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14624             char c = PieceToChar(boards[move][i][j]);
14625             fputc(c == 'x' ? '.' : c, fp);
14626             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14627         }
14628     }
14629     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14630       fprintf(fp, "white to play\n");
14631     else
14632       fprintf(fp, "black to play\n");
14633 }
14634
14635 void
14636 PrintOpponents (FILE *fp)
14637 {
14638     if (gameInfo.white != NULL) {
14639         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14640     } else {
14641         fprintf(fp, "\n");
14642     }
14643 }
14644
14645 /* Find last component of program's own name, using some heuristics */
14646 void
14647 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14648 {
14649     char *p, *q, c;
14650     int local = (strcmp(host, "localhost") == 0);
14651     while (!local && (p = strchr(prog, ';')) != NULL) {
14652         p++;
14653         while (*p == ' ') p++;
14654         prog = p;
14655     }
14656     if (*prog == '"' || *prog == '\'') {
14657         q = strchr(prog + 1, *prog);
14658     } else {
14659         q = strchr(prog, ' ');
14660     }
14661     if (q == NULL) q = prog + strlen(prog);
14662     p = q;
14663     while (p >= prog && *p != '/' && *p != '\\') p--;
14664     p++;
14665     if(p == prog && *p == '"') p++;
14666     c = *q; *q = 0;
14667     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c;
14668     memcpy(buf, p, q - p);
14669     buf[q - p] = NULLCHAR;
14670     if (!local) {
14671         strcat(buf, "@");
14672         strcat(buf, host);
14673     }
14674 }
14675
14676 char *
14677 TimeControlTagValue ()
14678 {
14679     char buf[MSG_SIZ];
14680     if (!appData.clockMode) {
14681       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14682     } else if (movesPerSession > 0) {
14683       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14684     } else if (timeIncrement == 0) {
14685       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14686     } else {
14687       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14688     }
14689     return StrSave(buf);
14690 }
14691
14692 void
14693 SetGameInfo ()
14694 {
14695     /* This routine is used only for certain modes */
14696     VariantClass v = gameInfo.variant;
14697     ChessMove r = GameUnfinished;
14698     char *p = NULL;
14699
14700     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14701         r = gameInfo.result;
14702         p = gameInfo.resultDetails;
14703         gameInfo.resultDetails = NULL;
14704     }
14705     ClearGameInfo(&gameInfo);
14706     gameInfo.variant = v;
14707
14708     switch (gameMode) {
14709       case MachinePlaysWhite:
14710         gameInfo.event = StrSave( appData.pgnEventHeader );
14711         gameInfo.site = StrSave(HostName());
14712         gameInfo.date = PGNDate();
14713         gameInfo.round = StrSave("-");
14714         gameInfo.white = StrSave(first.tidy);
14715         gameInfo.black = StrSave(UserName());
14716         gameInfo.timeControl = TimeControlTagValue();
14717         break;
14718
14719       case MachinePlaysBlack:
14720         gameInfo.event = StrSave( appData.pgnEventHeader );
14721         gameInfo.site = StrSave(HostName());
14722         gameInfo.date = PGNDate();
14723         gameInfo.round = StrSave("-");
14724         gameInfo.white = StrSave(UserName());
14725         gameInfo.black = StrSave(first.tidy);
14726         gameInfo.timeControl = TimeControlTagValue();
14727         break;
14728
14729       case TwoMachinesPlay:
14730         gameInfo.event = StrSave( appData.pgnEventHeader );
14731         gameInfo.site = StrSave(HostName());
14732         gameInfo.date = PGNDate();
14733         if (roundNr > 0) {
14734             char buf[MSG_SIZ];
14735             snprintf(buf, MSG_SIZ, "%d", roundNr);
14736             gameInfo.round = StrSave(buf);
14737         } else {
14738             gameInfo.round = StrSave("-");
14739         }
14740         if (first.twoMachinesColor[0] == 'w') {
14741             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14742             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14743         } else {
14744             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14745             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14746         }
14747         gameInfo.timeControl = TimeControlTagValue();
14748         break;
14749
14750       case EditGame:
14751         gameInfo.event = StrSave("Edited game");
14752         gameInfo.site = StrSave(HostName());
14753         gameInfo.date = PGNDate();
14754         gameInfo.round = StrSave("-");
14755         gameInfo.white = StrSave("-");
14756         gameInfo.black = StrSave("-");
14757         gameInfo.result = r;
14758         gameInfo.resultDetails = p;
14759         break;
14760
14761       case EditPosition:
14762         gameInfo.event = StrSave("Edited position");
14763         gameInfo.site = StrSave(HostName());
14764         gameInfo.date = PGNDate();
14765         gameInfo.round = StrSave("-");
14766         gameInfo.white = StrSave("-");
14767         gameInfo.black = StrSave("-");
14768         break;
14769
14770       case IcsPlayingWhite:
14771       case IcsPlayingBlack:
14772       case IcsObserving:
14773       case IcsExamining:
14774         break;
14775
14776       case PlayFromGameFile:
14777         gameInfo.event = StrSave("Game from non-PGN file");
14778         gameInfo.site = StrSave(HostName());
14779         gameInfo.date = PGNDate();
14780         gameInfo.round = StrSave("-");
14781         gameInfo.white = StrSave("?");
14782         gameInfo.black = StrSave("?");
14783         break;
14784
14785       default:
14786         break;
14787     }
14788 }
14789
14790 void
14791 ReplaceComment (int index, char *text)
14792 {
14793     int len;
14794     char *p;
14795     float score;
14796
14797     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14798        pvInfoList[index-1].depth == len &&
14799        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14800        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14801     while (*text == '\n') text++;
14802     len = strlen(text);
14803     while (len > 0 && text[len - 1] == '\n') len--;
14804
14805     if (commentList[index] != NULL)
14806       free(commentList[index]);
14807
14808     if (len == 0) {
14809         commentList[index] = NULL;
14810         return;
14811     }
14812   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14813       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14814       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14815     commentList[index] = (char *) malloc(len + 2);
14816     strncpy(commentList[index], text, len);
14817     commentList[index][len] = '\n';
14818     commentList[index][len + 1] = NULLCHAR;
14819   } else {
14820     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14821     char *p;
14822     commentList[index] = (char *) malloc(len + 7);
14823     safeStrCpy(commentList[index], "{\n", 3);
14824     safeStrCpy(commentList[index]+2, text, len+1);
14825     commentList[index][len+2] = NULLCHAR;
14826     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14827     strcat(commentList[index], "\n}\n");
14828   }
14829 }
14830
14831 void
14832 CrushCRs (char *text)
14833 {
14834   char *p = text;
14835   char *q = text;
14836   char ch;
14837
14838   do {
14839     ch = *p++;
14840     if (ch == '\r') continue;
14841     *q++ = ch;
14842   } while (ch != '\0');
14843 }
14844
14845 void
14846 AppendComment (int index, char *text, Boolean addBraces)
14847 /* addBraces  tells if we should add {} */
14848 {
14849     int oldlen, len;
14850     char *old;
14851
14852 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14853     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14854
14855     CrushCRs(text);
14856     while (*text == '\n') text++;
14857     len = strlen(text);
14858     while (len > 0 && text[len - 1] == '\n') len--;
14859     text[len] = NULLCHAR;
14860
14861     if (len == 0) return;
14862
14863     if (commentList[index] != NULL) {
14864       Boolean addClosingBrace = addBraces;
14865         old = commentList[index];
14866         oldlen = strlen(old);
14867         while(commentList[index][oldlen-1] ==  '\n')
14868           commentList[index][--oldlen] = NULLCHAR;
14869         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14870         safeStrCpy(commentList[index], old, oldlen + len + 6);
14871         free(old);
14872         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14873         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14874           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14875           while (*text == '\n') { text++; len--; }
14876           commentList[index][--oldlen] = NULLCHAR;
14877       }
14878         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14879         else          strcat(commentList[index], "\n");
14880         strcat(commentList[index], text);
14881         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14882         else          strcat(commentList[index], "\n");
14883     } else {
14884         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14885         if(addBraces)
14886           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14887         else commentList[index][0] = NULLCHAR;
14888         strcat(commentList[index], text);
14889         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14890         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14891     }
14892 }
14893
14894 static char *
14895 FindStr (char * text, char * sub_text)
14896 {
14897     char * result = strstr( text, sub_text );
14898
14899     if( result != NULL ) {
14900         result += strlen( sub_text );
14901     }
14902
14903     return result;
14904 }
14905
14906 /* [AS] Try to extract PV info from PGN comment */
14907 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14908 char *
14909 GetInfoFromComment (int index, char * text)
14910 {
14911     char * sep = text, *p;
14912
14913     if( text != NULL && index > 0 ) {
14914         int score = 0;
14915         int depth = 0;
14916         int time = -1, sec = 0, deci;
14917         char * s_eval = FindStr( text, "[%eval " );
14918         char * s_emt = FindStr( text, "[%emt " );
14919
14920         if( s_eval != NULL || s_emt != NULL ) {
14921             /* New style */
14922             char delim;
14923
14924             if( s_eval != NULL ) {
14925                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14926                     return text;
14927                 }
14928
14929                 if( delim != ']' ) {
14930                     return text;
14931                 }
14932             }
14933
14934             if( s_emt != NULL ) {
14935             }
14936                 return text;
14937         }
14938         else {
14939             /* We expect something like: [+|-]nnn.nn/dd */
14940             int score_lo = 0;
14941
14942             if(*text != '{') return text; // [HGM] braces: must be normal comment
14943
14944             sep = strchr( text, '/' );
14945             if( sep == NULL || sep < (text+4) ) {
14946                 return text;
14947             }
14948
14949             p = text;
14950             if(p[1] == '(') { // comment starts with PV
14951                p = strchr(p, ')'); // locate end of PV
14952                if(p == NULL || sep < p+5) return text;
14953                // at this point we have something like "{(.*) +0.23/6 ..."
14954                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14955                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14956                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14957             }
14958             time = -1; sec = -1; deci = -1;
14959             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14960                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14961                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14962                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14963                 return text;
14964             }
14965
14966             if( score_lo < 0 || score_lo >= 100 ) {
14967                 return text;
14968             }
14969
14970             if(sec >= 0) time = 600*time + 10*sec; else
14971             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14972
14973             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14974
14975             /* [HGM] PV time: now locate end of PV info */
14976             while( *++sep >= '0' && *sep <= '9'); // strip depth
14977             if(time >= 0)
14978             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14979             if(sec >= 0)
14980             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14981             if(deci >= 0)
14982             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14983             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14984         }
14985
14986         if( depth <= 0 ) {
14987             return text;
14988         }
14989
14990         if( time < 0 ) {
14991             time = -1;
14992         }
14993
14994         pvInfoList[index-1].depth = depth;
14995         pvInfoList[index-1].score = score;
14996         pvInfoList[index-1].time  = 10*time; // centi-sec
14997         if(*sep == '}') *sep = 0; else *--sep = '{';
14998         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14999     }
15000     return sep;
15001 }
15002
15003 void
15004 SendToProgram (char *message, ChessProgramState *cps)
15005 {
15006     int count, outCount, error;
15007     char buf[MSG_SIZ];
15008
15009     if (cps->pr == NoProc) return;
15010     Attention(cps);
15011
15012     if (appData.debugMode) {
15013         TimeMark now;
15014         GetTimeMark(&now);
15015         fprintf(debugFP, "%ld >%-6s: %s",
15016                 SubtractTimeMarks(&now, &programStartTime),
15017                 cps->which, message);
15018     }
15019
15020     count = strlen(message);
15021     outCount = OutputToProcess(cps->pr, message, count, &error);
15022     if (outCount < count && !exiting
15023                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15024       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15025       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15026         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15027             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15028                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15029                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15030                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15031             } else {
15032                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15033                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15034                 gameInfo.result = res;
15035             }
15036             gameInfo.resultDetails = StrSave(buf);
15037         }
15038         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15039         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15040     }
15041 }
15042
15043 void
15044 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15045 {
15046     char *end_str;
15047     char buf[MSG_SIZ];
15048     ChessProgramState *cps = (ChessProgramState *)closure;
15049
15050     if (isr != cps->isr) return; /* Killed intentionally */
15051     if (count <= 0) {
15052         if (count == 0) {
15053             RemoveInputSource(cps->isr);
15054             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15055             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15056                     _(cps->which), cps->program);
15057         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15058                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15059                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15060                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15061                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15062                 } else {
15063                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15064                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15065                     gameInfo.result = res;
15066                 }
15067                 gameInfo.resultDetails = StrSave(buf);
15068             }
15069             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15070             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15071         } else {
15072             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15073                     _(cps->which), cps->program);
15074             RemoveInputSource(cps->isr);
15075
15076             /* [AS] Program is misbehaving badly... kill it */
15077             if( count == -2 ) {
15078                 DestroyChildProcess( cps->pr, 9 );
15079                 cps->pr = NoProc;
15080             }
15081
15082             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15083         }
15084         return;
15085     }
15086
15087     if ((end_str = strchr(message, '\r')) != NULL)
15088       *end_str = NULLCHAR;
15089     if ((end_str = strchr(message, '\n')) != NULL)
15090       *end_str = NULLCHAR;
15091
15092     if (appData.debugMode) {
15093         TimeMark now; int print = 1;
15094         char *quote = ""; char c; int i;
15095
15096         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15097                 char start = message[0];
15098                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15099                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15100                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15101                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15102                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15103                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15104                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15105                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15106                    sscanf(message, "hint: %c", &c)!=1 && 
15107                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15108                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15109                     print = (appData.engineComments >= 2);
15110                 }
15111                 message[0] = start; // restore original message
15112         }
15113         if(print) {
15114                 GetTimeMark(&now);
15115                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15116                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15117                         quote,
15118                         message);
15119         }
15120     }
15121
15122     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15123     if (appData.icsEngineAnalyze) {
15124         if (strstr(message, "whisper") != NULL ||
15125              strstr(message, "kibitz") != NULL ||
15126             strstr(message, "tellics") != NULL) return;
15127     }
15128
15129     HandleMachineMove(message, cps);
15130 }
15131
15132
15133 void
15134 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15135 {
15136     char buf[MSG_SIZ];
15137     int seconds;
15138
15139     if( timeControl_2 > 0 ) {
15140         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15141             tc = timeControl_2;
15142         }
15143     }
15144     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15145     inc /= cps->timeOdds;
15146     st  /= cps->timeOdds;
15147
15148     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15149
15150     if (st > 0) {
15151       /* Set exact time per move, normally using st command */
15152       if (cps->stKludge) {
15153         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15154         seconds = st % 60;
15155         if (seconds == 0) {
15156           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15157         } else {
15158           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15159         }
15160       } else {
15161         snprintf(buf, MSG_SIZ, "st %d\n", st);
15162       }
15163     } else {
15164       /* Set conventional or incremental time control, using level command */
15165       if (seconds == 0) {
15166         /* Note old gnuchess bug -- minutes:seconds used to not work.
15167            Fixed in later versions, but still avoid :seconds
15168            when seconds is 0. */
15169         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15170       } else {
15171         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15172                  seconds, inc/1000.);
15173       }
15174     }
15175     SendToProgram(buf, cps);
15176
15177     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15178     /* Orthogonally, limit search to given depth */
15179     if (sd > 0) {
15180       if (cps->sdKludge) {
15181         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15182       } else {
15183         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15184       }
15185       SendToProgram(buf, cps);
15186     }
15187
15188     if(cps->nps >= 0) { /* [HGM] nps */
15189         if(cps->supportsNPS == FALSE)
15190           cps->nps = -1; // don't use if engine explicitly says not supported!
15191         else {
15192           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15193           SendToProgram(buf, cps);
15194         }
15195     }
15196 }
15197
15198 ChessProgramState *
15199 WhitePlayer ()
15200 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15201 {
15202     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15203        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15204         return &second;
15205     return &first;
15206 }
15207
15208 void
15209 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15210 {
15211     char message[MSG_SIZ];
15212     long time, otime;
15213
15214     /* Note: this routine must be called when the clocks are stopped
15215        or when they have *just* been set or switched; otherwise
15216        it will be off by the time since the current tick started.
15217     */
15218     if (machineWhite) {
15219         time = whiteTimeRemaining / 10;
15220         otime = blackTimeRemaining / 10;
15221     } else {
15222         time = blackTimeRemaining / 10;
15223         otime = whiteTimeRemaining / 10;
15224     }
15225     /* [HGM] translate opponent's time by time-odds factor */
15226     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15227
15228     if (time <= 0) time = 1;
15229     if (otime <= 0) otime = 1;
15230
15231     snprintf(message, MSG_SIZ, "time %ld\n", time);
15232     SendToProgram(message, cps);
15233
15234     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15235     SendToProgram(message, cps);
15236 }
15237
15238 int
15239 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15240 {
15241   char buf[MSG_SIZ];
15242   int len = strlen(name);
15243   int val;
15244
15245   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15246     (*p) += len + 1;
15247     sscanf(*p, "%d", &val);
15248     *loc = (val != 0);
15249     while (**p && **p != ' ')
15250       (*p)++;
15251     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15252     SendToProgram(buf, cps);
15253     return TRUE;
15254   }
15255   return FALSE;
15256 }
15257
15258 int
15259 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15260 {
15261   char buf[MSG_SIZ];
15262   int len = strlen(name);
15263   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15264     (*p) += len + 1;
15265     sscanf(*p, "%d", loc);
15266     while (**p && **p != ' ') (*p)++;
15267     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15268     SendToProgram(buf, cps);
15269     return TRUE;
15270   }
15271   return FALSE;
15272 }
15273
15274 int
15275 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15276 {
15277   char buf[MSG_SIZ];
15278   int len = strlen(name);
15279   if (strncmp((*p), name, len) == 0
15280       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15281     (*p) += len + 2;
15282     sscanf(*p, "%[^\"]", loc);
15283     while (**p && **p != '\"') (*p)++;
15284     if (**p == '\"') (*p)++;
15285     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15286     SendToProgram(buf, cps);
15287     return TRUE;
15288   }
15289   return FALSE;
15290 }
15291
15292 int
15293 ParseOption (Option *opt, ChessProgramState *cps)
15294 // [HGM] options: process the string that defines an engine option, and determine
15295 // name, type, default value, and allowed value range
15296 {
15297         char *p, *q, buf[MSG_SIZ];
15298         int n, min = (-1)<<31, max = 1<<31, def;
15299
15300         if(p = strstr(opt->name, " -spin ")) {
15301             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15302             if(max < min) max = min; // enforce consistency
15303             if(def < min) def = min;
15304             if(def > max) def = max;
15305             opt->value = def;
15306             opt->min = min;
15307             opt->max = max;
15308             opt->type = Spin;
15309         } else if((p = strstr(opt->name, " -slider "))) {
15310             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15311             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15312             if(max < min) max = min; // enforce consistency
15313             if(def < min) def = min;
15314             if(def > max) def = max;
15315             opt->value = def;
15316             opt->min = min;
15317             opt->max = max;
15318             opt->type = Spin; // Slider;
15319         } else if((p = strstr(opt->name, " -string "))) {
15320             opt->textValue = p+9;
15321             opt->type = TextBox;
15322         } else if((p = strstr(opt->name, " -file "))) {
15323             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15324             opt->textValue = p+7;
15325             opt->type = FileName; // FileName;
15326         } else if((p = strstr(opt->name, " -path "))) {
15327             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15328             opt->textValue = p+7;
15329             opt->type = PathName; // PathName;
15330         } else if(p = strstr(opt->name, " -check ")) {
15331             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15332             opt->value = (def != 0);
15333             opt->type = CheckBox;
15334         } else if(p = strstr(opt->name, " -combo ")) {
15335             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15336             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15337             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15338             opt->value = n = 0;
15339             while(q = StrStr(q, " /// ")) {
15340                 n++; *q = 0;    // count choices, and null-terminate each of them
15341                 q += 5;
15342                 if(*q == '*') { // remember default, which is marked with * prefix
15343                     q++;
15344                     opt->value = n;
15345                 }
15346                 cps->comboList[cps->comboCnt++] = q;
15347             }
15348             cps->comboList[cps->comboCnt++] = NULL;
15349             opt->max = n + 1;
15350             opt->type = ComboBox;
15351         } else if(p = strstr(opt->name, " -button")) {
15352             opt->type = Button;
15353         } else if(p = strstr(opt->name, " -save")) {
15354             opt->type = SaveButton;
15355         } else return FALSE;
15356         *p = 0; // terminate option name
15357         // now look if the command-line options define a setting for this engine option.
15358         if(cps->optionSettings && cps->optionSettings[0])
15359             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15360         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15361           snprintf(buf, MSG_SIZ, "option %s", p);
15362                 if(p = strstr(buf, ",")) *p = 0;
15363                 if(q = strchr(buf, '=')) switch(opt->type) {
15364                     case ComboBox:
15365                         for(n=0; n<opt->max; n++)
15366                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15367                         break;
15368                     case TextBox:
15369                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15370                         break;
15371                     case Spin:
15372                     case CheckBox:
15373                         opt->value = atoi(q+1);
15374                     default:
15375                         break;
15376                 }
15377                 strcat(buf, "\n");
15378                 SendToProgram(buf, cps);
15379         }
15380         return TRUE;
15381 }
15382
15383 void
15384 FeatureDone (ChessProgramState *cps, int val)
15385 {
15386   DelayedEventCallback cb = GetDelayedEvent();
15387   if ((cb == InitBackEnd3 && cps == &first) ||
15388       (cb == SettingsMenuIfReady && cps == &second) ||
15389       (cb == LoadEngine) ||
15390       (cb == TwoMachinesEventIfReady)) {
15391     CancelDelayedEvent();
15392     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15393   }
15394   cps->initDone = val;
15395 }
15396
15397 /* Parse feature command from engine */
15398 void
15399 ParseFeatures (char *args, ChessProgramState *cps)
15400 {
15401   char *p = args;
15402   char *q;
15403   int val;
15404   char buf[MSG_SIZ];
15405
15406   for (;;) {
15407     while (*p == ' ') p++;
15408     if (*p == NULLCHAR) return;
15409
15410     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15411     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15412     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15413     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15414     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15415     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15416     if (BoolFeature(&p, "reuse", &val, cps)) {
15417       /* Engine can disable reuse, but can't enable it if user said no */
15418       if (!val) cps->reuse = FALSE;
15419       continue;
15420     }
15421     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15422     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15423       if (gameMode == TwoMachinesPlay) {
15424         DisplayTwoMachinesTitle();
15425       } else {
15426         DisplayTitle("");
15427       }
15428       continue;
15429     }
15430     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15431     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15432     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15433     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15434     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15435     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15436     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15437     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15438     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15439     if (IntFeature(&p, "done", &val, cps)) {
15440       FeatureDone(cps, val);
15441       continue;
15442     }
15443     /* Added by Tord: */
15444     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15445     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15446     /* End of additions by Tord */
15447
15448     /* [HGM] added features: */
15449     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15450     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15451     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15452     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15453     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15454     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15455     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15456         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15457           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15458             SendToProgram(buf, cps);
15459             continue;
15460         }
15461         if(cps->nrOptions >= MAX_OPTIONS) {
15462             cps->nrOptions--;
15463             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15464             DisplayError(buf, 0);
15465         }
15466         continue;
15467     }
15468     /* End of additions by HGM */
15469
15470     /* unknown feature: complain and skip */
15471     q = p;
15472     while (*q && *q != '=') q++;
15473     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15474     SendToProgram(buf, cps);
15475     p = q;
15476     if (*p == '=') {
15477       p++;
15478       if (*p == '\"') {
15479         p++;
15480         while (*p && *p != '\"') p++;
15481         if (*p == '\"') p++;
15482       } else {
15483         while (*p && *p != ' ') p++;
15484       }
15485     }
15486   }
15487
15488 }
15489
15490 void
15491 PeriodicUpdatesEvent (int newState)
15492 {
15493     if (newState == appData.periodicUpdates)
15494       return;
15495
15496     appData.periodicUpdates=newState;
15497
15498     /* Display type changes, so update it now */
15499 //    DisplayAnalysis();
15500
15501     /* Get the ball rolling again... */
15502     if (newState) {
15503         AnalysisPeriodicEvent(1);
15504         StartAnalysisClock();
15505     }
15506 }
15507
15508 void
15509 PonderNextMoveEvent (int newState)
15510 {
15511     if (newState == appData.ponderNextMove) return;
15512     if (gameMode == EditPosition) EditPositionDone(TRUE);
15513     if (newState) {
15514         SendToProgram("hard\n", &first);
15515         if (gameMode == TwoMachinesPlay) {
15516             SendToProgram("hard\n", &second);
15517         }
15518     } else {
15519         SendToProgram("easy\n", &first);
15520         thinkOutput[0] = NULLCHAR;
15521         if (gameMode == TwoMachinesPlay) {
15522             SendToProgram("easy\n", &second);
15523         }
15524     }
15525     appData.ponderNextMove = newState;
15526 }
15527
15528 void
15529 NewSettingEvent (int option, int *feature, char *command, int value)
15530 {
15531     char buf[MSG_SIZ];
15532
15533     if (gameMode == EditPosition) EditPositionDone(TRUE);
15534     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15535     if(feature == NULL || *feature) SendToProgram(buf, &first);
15536     if (gameMode == TwoMachinesPlay) {
15537         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15538     }
15539 }
15540
15541 void
15542 ShowThinkingEvent ()
15543 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15544 {
15545     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15546     int newState = appData.showThinking
15547         // [HGM] thinking: other features now need thinking output as well
15548         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15549
15550     if (oldState == newState) return;
15551     oldState = newState;
15552     if (gameMode == EditPosition) EditPositionDone(TRUE);
15553     if (oldState) {
15554         SendToProgram("post\n", &first);
15555         if (gameMode == TwoMachinesPlay) {
15556             SendToProgram("post\n", &second);
15557         }
15558     } else {
15559         SendToProgram("nopost\n", &first);
15560         thinkOutput[0] = NULLCHAR;
15561         if (gameMode == TwoMachinesPlay) {
15562             SendToProgram("nopost\n", &second);
15563         }
15564     }
15565 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15566 }
15567
15568 void
15569 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15570 {
15571   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15572   if (pr == NoProc) return;
15573   AskQuestion(title, question, replyPrefix, pr);
15574 }
15575
15576 void
15577 TypeInEvent (char firstChar)
15578 {
15579     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15580         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15581         gameMode == AnalyzeMode || gameMode == EditGame || 
15582         gameMode == EditPosition || gameMode == IcsExamining ||
15583         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15584         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15585                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15586                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15587         gameMode == Training) PopUpMoveDialog(firstChar);
15588 }
15589
15590 void
15591 TypeInDoneEvent (char *move)
15592 {
15593         Board board;
15594         int n, fromX, fromY, toX, toY;
15595         char promoChar;
15596         ChessMove moveType;
15597
15598         // [HGM] FENedit
15599         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15600                 EditPositionPasteFEN(move);
15601                 return;
15602         }
15603         // [HGM] movenum: allow move number to be typed in any mode
15604         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15605           ToNrEvent(2*n-1);
15606           return;
15607         }
15608         // undocumented kludge: allow command-line option to be typed in!
15609         // (potentially fatal, and does not implement the effect of the option.)
15610         // should only be used for options that are values on which future decisions will be made,
15611         // and definitely not on options that would be used during initialization.
15612         if(strstr(move, "!!! -") == move) {
15613             ParseArgsFromString(move+4);
15614             return;
15615         }
15616
15617       if (gameMode != EditGame && currentMove != forwardMostMove && 
15618         gameMode != Training) {
15619         DisplayMoveError(_("Displayed move is not current"));
15620       } else {
15621         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15622           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15623         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15624         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15625           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15626           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15627         } else {
15628           DisplayMoveError(_("Could not parse move"));
15629         }
15630       }
15631 }
15632
15633 void
15634 DisplayMove (int moveNumber)
15635 {
15636     char message[MSG_SIZ];
15637     char res[MSG_SIZ];
15638     char cpThinkOutput[MSG_SIZ];
15639
15640     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15641
15642     if (moveNumber == forwardMostMove - 1 ||
15643         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15644
15645         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15646
15647         if (strchr(cpThinkOutput, '\n')) {
15648             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15649         }
15650     } else {
15651         *cpThinkOutput = NULLCHAR;
15652     }
15653
15654     /* [AS] Hide thinking from human user */
15655     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15656         *cpThinkOutput = NULLCHAR;
15657         if( thinkOutput[0] != NULLCHAR ) {
15658             int i;
15659
15660             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15661                 cpThinkOutput[i] = '.';
15662             }
15663             cpThinkOutput[i] = NULLCHAR;
15664             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15665         }
15666     }
15667
15668     if (moveNumber == forwardMostMove - 1 &&
15669         gameInfo.resultDetails != NULL) {
15670         if (gameInfo.resultDetails[0] == NULLCHAR) {
15671           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15672         } else {
15673           snprintf(res, MSG_SIZ, " {%s} %s",
15674                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15675         }
15676     } else {
15677         res[0] = NULLCHAR;
15678     }
15679
15680     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15681         DisplayMessage(res, cpThinkOutput);
15682     } else {
15683       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15684                 WhiteOnMove(moveNumber) ? " " : ".. ",
15685                 parseList[moveNumber], res);
15686         DisplayMessage(message, cpThinkOutput);
15687     }
15688 }
15689
15690 void
15691 DisplayComment (int moveNumber, char *text)
15692 {
15693     char title[MSG_SIZ];
15694
15695     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15696       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15697     } else {
15698       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15699               WhiteOnMove(moveNumber) ? " " : ".. ",
15700               parseList[moveNumber]);
15701     }
15702     if (text != NULL && (appData.autoDisplayComment || commentUp))
15703         CommentPopUp(title, text);
15704 }
15705
15706 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15707  * might be busy thinking or pondering.  It can be omitted if your
15708  * gnuchess is configured to stop thinking immediately on any user
15709  * input.  However, that gnuchess feature depends on the FIONREAD
15710  * ioctl, which does not work properly on some flavors of Unix.
15711  */
15712 void
15713 Attention (ChessProgramState *cps)
15714 {
15715 #if ATTENTION
15716     if (!cps->useSigint) return;
15717     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15718     switch (gameMode) {
15719       case MachinePlaysWhite:
15720       case MachinePlaysBlack:
15721       case TwoMachinesPlay:
15722       case IcsPlayingWhite:
15723       case IcsPlayingBlack:
15724       case AnalyzeMode:
15725       case AnalyzeFile:
15726         /* Skip if we know it isn't thinking */
15727         if (!cps->maybeThinking) return;
15728         if (appData.debugMode)
15729           fprintf(debugFP, "Interrupting %s\n", cps->which);
15730         InterruptChildProcess(cps->pr);
15731         cps->maybeThinking = FALSE;
15732         break;
15733       default:
15734         break;
15735     }
15736 #endif /*ATTENTION*/
15737 }
15738
15739 int
15740 CheckFlags ()
15741 {
15742     if (whiteTimeRemaining <= 0) {
15743         if (!whiteFlag) {
15744             whiteFlag = TRUE;
15745             if (appData.icsActive) {
15746                 if (appData.autoCallFlag &&
15747                     gameMode == IcsPlayingBlack && !blackFlag) {
15748                   SendToICS(ics_prefix);
15749                   SendToICS("flag\n");
15750                 }
15751             } else {
15752                 if (blackFlag) {
15753                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15754                 } else {
15755                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15756                     if (appData.autoCallFlag) {
15757                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15758                         return TRUE;
15759                     }
15760                 }
15761             }
15762         }
15763     }
15764     if (blackTimeRemaining <= 0) {
15765         if (!blackFlag) {
15766             blackFlag = TRUE;
15767             if (appData.icsActive) {
15768                 if (appData.autoCallFlag &&
15769                     gameMode == IcsPlayingWhite && !whiteFlag) {
15770                   SendToICS(ics_prefix);
15771                   SendToICS("flag\n");
15772                 }
15773             } else {
15774                 if (whiteFlag) {
15775                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15776                 } else {
15777                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15778                     if (appData.autoCallFlag) {
15779                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15780                         return TRUE;
15781                     }
15782                 }
15783             }
15784         }
15785     }
15786     return FALSE;
15787 }
15788
15789 void
15790 CheckTimeControl ()
15791 {
15792     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15793         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15794
15795     /*
15796      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15797      */
15798     if ( !WhiteOnMove(forwardMostMove) ) {
15799         /* White made time control */
15800         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15801         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15802         /* [HGM] time odds: correct new time quota for time odds! */
15803                                             / WhitePlayer()->timeOdds;
15804         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15805     } else {
15806         lastBlack -= blackTimeRemaining;
15807         /* Black made time control */
15808         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15809                                             / WhitePlayer()->other->timeOdds;
15810         lastWhite = whiteTimeRemaining;
15811     }
15812 }
15813
15814 void
15815 DisplayBothClocks ()
15816 {
15817     int wom = gameMode == EditPosition ?
15818       !blackPlaysFirst : WhiteOnMove(currentMove);
15819     DisplayWhiteClock(whiteTimeRemaining, wom);
15820     DisplayBlackClock(blackTimeRemaining, !wom);
15821 }
15822
15823
15824 /* Timekeeping seems to be a portability nightmare.  I think everyone
15825    has ftime(), but I'm really not sure, so I'm including some ifdefs
15826    to use other calls if you don't.  Clocks will be less accurate if
15827    you have neither ftime nor gettimeofday.
15828 */
15829
15830 /* VS 2008 requires the #include outside of the function */
15831 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15832 #include <sys/timeb.h>
15833 #endif
15834
15835 /* Get the current time as a TimeMark */
15836 void
15837 GetTimeMark (TimeMark *tm)
15838 {
15839 #if HAVE_GETTIMEOFDAY
15840
15841     struct timeval timeVal;
15842     struct timezone timeZone;
15843
15844     gettimeofday(&timeVal, &timeZone);
15845     tm->sec = (long) timeVal.tv_sec;
15846     tm->ms = (int) (timeVal.tv_usec / 1000L);
15847
15848 #else /*!HAVE_GETTIMEOFDAY*/
15849 #if HAVE_FTIME
15850
15851 // include <sys/timeb.h> / moved to just above start of function
15852     struct timeb timeB;
15853
15854     ftime(&timeB);
15855     tm->sec = (long) timeB.time;
15856     tm->ms = (int) timeB.millitm;
15857
15858 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15859     tm->sec = (long) time(NULL);
15860     tm->ms = 0;
15861 #endif
15862 #endif
15863 }
15864
15865 /* Return the difference in milliseconds between two
15866    time marks.  We assume the difference will fit in a long!
15867 */
15868 long
15869 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15870 {
15871     return 1000L*(tm2->sec - tm1->sec) +
15872            (long) (tm2->ms - tm1->ms);
15873 }
15874
15875
15876 /*
15877  * Code to manage the game clocks.
15878  *
15879  * In tournament play, black starts the clock and then white makes a move.
15880  * We give the human user a slight advantage if he is playing white---the
15881  * clocks don't run until he makes his first move, so it takes zero time.
15882  * Also, we don't account for network lag, so we could get out of sync
15883  * with GNU Chess's clock -- but then, referees are always right.
15884  */
15885
15886 static TimeMark tickStartTM;
15887 static long intendedTickLength;
15888
15889 long
15890 NextTickLength (long timeRemaining)
15891 {
15892     long nominalTickLength, nextTickLength;
15893
15894     if (timeRemaining > 0L && timeRemaining <= 10000L)
15895       nominalTickLength = 100L;
15896     else
15897       nominalTickLength = 1000L;
15898     nextTickLength = timeRemaining % nominalTickLength;
15899     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15900
15901     return nextTickLength;
15902 }
15903
15904 /* Adjust clock one minute up or down */
15905 void
15906 AdjustClock (Boolean which, int dir)
15907 {
15908     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15909     if(which) blackTimeRemaining += 60000*dir;
15910     else      whiteTimeRemaining += 60000*dir;
15911     DisplayBothClocks();
15912     adjustedClock = TRUE;
15913 }
15914
15915 /* Stop clocks and reset to a fresh time control */
15916 void
15917 ResetClocks ()
15918 {
15919     (void) StopClockTimer();
15920     if (appData.icsActive) {
15921         whiteTimeRemaining = blackTimeRemaining = 0;
15922     } else if (searchTime) {
15923         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15924         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15925     } else { /* [HGM] correct new time quote for time odds */
15926         whiteTC = blackTC = fullTimeControlString;
15927         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15928         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15929     }
15930     if (whiteFlag || blackFlag) {
15931         DisplayTitle("");
15932         whiteFlag = blackFlag = FALSE;
15933     }
15934     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15935     DisplayBothClocks();
15936     adjustedClock = FALSE;
15937 }
15938
15939 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15940
15941 /* Decrement running clock by amount of time that has passed */
15942 void
15943 DecrementClocks ()
15944 {
15945     long timeRemaining;
15946     long lastTickLength, fudge;
15947     TimeMark now;
15948
15949     if (!appData.clockMode) return;
15950     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15951
15952     GetTimeMark(&now);
15953
15954     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15955
15956     /* Fudge if we woke up a little too soon */
15957     fudge = intendedTickLength - lastTickLength;
15958     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15959
15960     if (WhiteOnMove(forwardMostMove)) {
15961         if(whiteNPS >= 0) lastTickLength = 0;
15962         timeRemaining = whiteTimeRemaining -= lastTickLength;
15963         if(timeRemaining < 0 && !appData.icsActive) {
15964             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15965             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15966                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15967                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15968             }
15969         }
15970         DisplayWhiteClock(whiteTimeRemaining - fudge,
15971                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15972     } else {
15973         if(blackNPS >= 0) lastTickLength = 0;
15974         timeRemaining = blackTimeRemaining -= lastTickLength;
15975         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15976             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15977             if(suddenDeath) {
15978                 blackStartMove = forwardMostMove;
15979                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15980             }
15981         }
15982         DisplayBlackClock(blackTimeRemaining - fudge,
15983                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15984     }
15985     if (CheckFlags()) return;
15986
15987     tickStartTM = now;
15988     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15989     StartClockTimer(intendedTickLength);
15990
15991     /* if the time remaining has fallen below the alarm threshold, sound the
15992      * alarm. if the alarm has sounded and (due to a takeback or time control
15993      * with increment) the time remaining has increased to a level above the
15994      * threshold, reset the alarm so it can sound again.
15995      */
15996
15997     if (appData.icsActive && appData.icsAlarm) {
15998
15999         /* make sure we are dealing with the user's clock */
16000         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16001                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16002            )) return;
16003
16004         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16005             alarmSounded = FALSE;
16006         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16007             PlayAlarmSound();
16008             alarmSounded = TRUE;
16009         }
16010     }
16011 }
16012
16013
16014 /* A player has just moved, so stop the previously running
16015    clock and (if in clock mode) start the other one.
16016    We redisplay both clocks in case we're in ICS mode, because
16017    ICS gives us an update to both clocks after every move.
16018    Note that this routine is called *after* forwardMostMove
16019    is updated, so the last fractional tick must be subtracted
16020    from the color that is *not* on move now.
16021 */
16022 void
16023 SwitchClocks (int newMoveNr)
16024 {
16025     long lastTickLength;
16026     TimeMark now;
16027     int flagged = FALSE;
16028
16029     GetTimeMark(&now);
16030
16031     if (StopClockTimer() && appData.clockMode) {
16032         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16033         if (!WhiteOnMove(forwardMostMove)) {
16034             if(blackNPS >= 0) lastTickLength = 0;
16035             blackTimeRemaining -= lastTickLength;
16036            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16037 //         if(pvInfoList[forwardMostMove].time == -1)
16038                  pvInfoList[forwardMostMove].time =               // use GUI time
16039                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16040         } else {
16041            if(whiteNPS >= 0) lastTickLength = 0;
16042            whiteTimeRemaining -= lastTickLength;
16043            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16044 //         if(pvInfoList[forwardMostMove].time == -1)
16045                  pvInfoList[forwardMostMove].time =
16046                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16047         }
16048         flagged = CheckFlags();
16049     }
16050     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16051     CheckTimeControl();
16052
16053     if (flagged || !appData.clockMode) return;
16054
16055     switch (gameMode) {
16056       case MachinePlaysBlack:
16057       case MachinePlaysWhite:
16058       case BeginningOfGame:
16059         if (pausing) return;
16060         break;
16061
16062       case EditGame:
16063       case PlayFromGameFile:
16064       case IcsExamining:
16065         return;
16066
16067       default:
16068         break;
16069     }
16070
16071     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16072         if(WhiteOnMove(forwardMostMove))
16073              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16074         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16075     }
16076
16077     tickStartTM = now;
16078     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16079       whiteTimeRemaining : blackTimeRemaining);
16080     StartClockTimer(intendedTickLength);
16081 }
16082
16083
16084 /* Stop both clocks */
16085 void
16086 StopClocks ()
16087 {
16088     long lastTickLength;
16089     TimeMark now;
16090
16091     if (!StopClockTimer()) return;
16092     if (!appData.clockMode) return;
16093
16094     GetTimeMark(&now);
16095
16096     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16097     if (WhiteOnMove(forwardMostMove)) {
16098         if(whiteNPS >= 0) lastTickLength = 0;
16099         whiteTimeRemaining -= lastTickLength;
16100         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16101     } else {
16102         if(blackNPS >= 0) lastTickLength = 0;
16103         blackTimeRemaining -= lastTickLength;
16104         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16105     }
16106     CheckFlags();
16107 }
16108
16109 /* Start clock of player on move.  Time may have been reset, so
16110    if clock is already running, stop and restart it. */
16111 void
16112 StartClocks ()
16113 {
16114     (void) StopClockTimer(); /* in case it was running already */
16115     DisplayBothClocks();
16116     if (CheckFlags()) return;
16117
16118     if (!appData.clockMode) return;
16119     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16120
16121     GetTimeMark(&tickStartTM);
16122     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16123       whiteTimeRemaining : blackTimeRemaining);
16124
16125    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16126     whiteNPS = blackNPS = -1;
16127     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16128        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16129         whiteNPS = first.nps;
16130     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16131        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16132         blackNPS = first.nps;
16133     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16134         whiteNPS = second.nps;
16135     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16136         blackNPS = second.nps;
16137     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16138
16139     StartClockTimer(intendedTickLength);
16140 }
16141
16142 char *
16143 TimeString (long ms)
16144 {
16145     long second, minute, hour, day;
16146     char *sign = "";
16147     static char buf[32];
16148
16149     if (ms > 0 && ms <= 9900) {
16150       /* convert milliseconds to tenths, rounding up */
16151       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16152
16153       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16154       return buf;
16155     }
16156
16157     /* convert milliseconds to seconds, rounding up */
16158     /* use floating point to avoid strangeness of integer division
16159        with negative dividends on many machines */
16160     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16161
16162     if (second < 0) {
16163         sign = "-";
16164         second = -second;
16165     }
16166
16167     day = second / (60 * 60 * 24);
16168     second = second % (60 * 60 * 24);
16169     hour = second / (60 * 60);
16170     second = second % (60 * 60);
16171     minute = second / 60;
16172     second = second % 60;
16173
16174     if (day > 0)
16175       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16176               sign, day, hour, minute, second);
16177     else if (hour > 0)
16178       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16179     else
16180       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16181
16182     return buf;
16183 }
16184
16185
16186 /*
16187  * This is necessary because some C libraries aren't ANSI C compliant yet.
16188  */
16189 char *
16190 StrStr (char *string, char *match)
16191 {
16192     int i, length;
16193
16194     length = strlen(match);
16195
16196     for (i = strlen(string) - length; i >= 0; i--, string++)
16197       if (!strncmp(match, string, length))
16198         return string;
16199
16200     return NULL;
16201 }
16202
16203 char *
16204 StrCaseStr (char *string, char *match)
16205 {
16206     int i, j, length;
16207
16208     length = strlen(match);
16209
16210     for (i = strlen(string) - length; i >= 0; i--, string++) {
16211         for (j = 0; j < length; j++) {
16212             if (ToLower(match[j]) != ToLower(string[j]))
16213               break;
16214         }
16215         if (j == length) return string;
16216     }
16217
16218     return NULL;
16219 }
16220
16221 #ifndef _amigados
16222 int
16223 StrCaseCmp (char *s1, char *s2)
16224 {
16225     char c1, c2;
16226
16227     for (;;) {
16228         c1 = ToLower(*s1++);
16229         c2 = ToLower(*s2++);
16230         if (c1 > c2) return 1;
16231         if (c1 < c2) return -1;
16232         if (c1 == NULLCHAR) return 0;
16233     }
16234 }
16235
16236
16237 int
16238 ToLower (int c)
16239 {
16240     return isupper(c) ? tolower(c) : c;
16241 }
16242
16243
16244 int
16245 ToUpper (int c)
16246 {
16247     return islower(c) ? toupper(c) : c;
16248 }
16249 #endif /* !_amigados    */
16250
16251 char *
16252 StrSave (char *s)
16253 {
16254   char *ret;
16255
16256   if ((ret = (char *) malloc(strlen(s) + 1)))
16257     {
16258       safeStrCpy(ret, s, strlen(s)+1);
16259     }
16260   return ret;
16261 }
16262
16263 char *
16264 StrSavePtr (char *s, char **savePtr)
16265 {
16266     if (*savePtr) {
16267         free(*savePtr);
16268     }
16269     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16270       safeStrCpy(*savePtr, s, strlen(s)+1);
16271     }
16272     return(*savePtr);
16273 }
16274
16275 char *
16276 PGNDate ()
16277 {
16278     time_t clock;
16279     struct tm *tm;
16280     char buf[MSG_SIZ];
16281
16282     clock = time((time_t *)NULL);
16283     tm = localtime(&clock);
16284     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16285             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16286     return StrSave(buf);
16287 }
16288
16289
16290 char *
16291 PositionToFEN (int move, char *overrideCastling)
16292 {
16293     int i, j, fromX, fromY, toX, toY;
16294     int whiteToPlay;
16295     char buf[MSG_SIZ];
16296     char *p, *q;
16297     int emptycount;
16298     ChessSquare piece;
16299
16300     whiteToPlay = (gameMode == EditPosition) ?
16301       !blackPlaysFirst : (move % 2 == 0);
16302     p = buf;
16303
16304     /* Piece placement data */
16305     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16306         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16307         emptycount = 0;
16308         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16309             if (boards[move][i][j] == EmptySquare) {
16310                 emptycount++;
16311             } else { ChessSquare piece = boards[move][i][j];
16312                 if (emptycount > 0) {
16313                     if(emptycount<10) /* [HGM] can be >= 10 */
16314                         *p++ = '0' + emptycount;
16315                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16316                     emptycount = 0;
16317                 }
16318                 if(PieceToChar(piece) == '+') {
16319                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16320                     *p++ = '+';
16321                     piece = (ChessSquare)(DEMOTED piece);
16322                 }
16323                 *p++ = PieceToChar(piece);
16324                 if(p[-1] == '~') {
16325                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16326                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16327                     *p++ = '~';
16328                 }
16329             }
16330         }
16331         if (emptycount > 0) {
16332             if(emptycount<10) /* [HGM] can be >= 10 */
16333                 *p++ = '0' + emptycount;
16334             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16335             emptycount = 0;
16336         }
16337         *p++ = '/';
16338     }
16339     *(p - 1) = ' ';
16340
16341     /* [HGM] print Crazyhouse or Shogi holdings */
16342     if( gameInfo.holdingsWidth ) {
16343         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16344         q = p;
16345         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16346             piece = boards[move][i][BOARD_WIDTH-1];
16347             if( piece != EmptySquare )
16348               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16349                   *p++ = PieceToChar(piece);
16350         }
16351         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16352             piece = boards[move][BOARD_HEIGHT-i-1][0];
16353             if( piece != EmptySquare )
16354               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16355                   *p++ = PieceToChar(piece);
16356         }
16357
16358         if( q == p ) *p++ = '-';
16359         *p++ = ']';
16360         *p++ = ' ';
16361     }
16362
16363     /* Active color */
16364     *p++ = whiteToPlay ? 'w' : 'b';
16365     *p++ = ' ';
16366
16367   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16368     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16369   } else {
16370   if(nrCastlingRights) {
16371      q = p;
16372      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16373        /* [HGM] write directly from rights */
16374            if(boards[move][CASTLING][2] != NoRights &&
16375               boards[move][CASTLING][0] != NoRights   )
16376                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16377            if(boards[move][CASTLING][2] != NoRights &&
16378               boards[move][CASTLING][1] != NoRights   )
16379                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16380            if(boards[move][CASTLING][5] != NoRights &&
16381               boards[move][CASTLING][3] != NoRights   )
16382                 *p++ = boards[move][CASTLING][3] + AAA;
16383            if(boards[move][CASTLING][5] != NoRights &&
16384               boards[move][CASTLING][4] != NoRights   )
16385                 *p++ = boards[move][CASTLING][4] + AAA;
16386      } else {
16387
16388         /* [HGM] write true castling rights */
16389         if( nrCastlingRights == 6 ) {
16390             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16391                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16392             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16393                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16394             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16395                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16396             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16397                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16398         }
16399      }
16400      if (q == p) *p++ = '-'; /* No castling rights */
16401      *p++ = ' ';
16402   }
16403
16404   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16405      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16406     /* En passant target square */
16407     if (move > backwardMostMove) {
16408         fromX = moveList[move - 1][0] - AAA;
16409         fromY = moveList[move - 1][1] - ONE;
16410         toX = moveList[move - 1][2] - AAA;
16411         toY = moveList[move - 1][3] - ONE;
16412         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16413             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16414             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16415             fromX == toX) {
16416             /* 2-square pawn move just happened */
16417             *p++ = toX + AAA;
16418             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16419         } else {
16420             *p++ = '-';
16421         }
16422     } else if(move == backwardMostMove) {
16423         // [HGM] perhaps we should always do it like this, and forget the above?
16424         if((signed char)boards[move][EP_STATUS] >= 0) {
16425             *p++ = boards[move][EP_STATUS] + AAA;
16426             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16427         } else {
16428             *p++ = '-';
16429         }
16430     } else {
16431         *p++ = '-';
16432     }
16433     *p++ = ' ';
16434   }
16435   }
16436
16437     /* [HGM] find reversible plies */
16438     {   int i = 0, j=move;
16439
16440         if (appData.debugMode) { int k;
16441             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16442             for(k=backwardMostMove; k<=forwardMostMove; k++)
16443                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16444
16445         }
16446
16447         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16448         if( j == backwardMostMove ) i += initialRulePlies;
16449         sprintf(p, "%d ", i);
16450         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16451     }
16452     /* Fullmove number */
16453     sprintf(p, "%d", (move / 2) + 1);
16454
16455     return StrSave(buf);
16456 }
16457
16458 Boolean
16459 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16460 {
16461     int i, j;
16462     char *p, c;
16463     int emptycount;
16464     ChessSquare piece;
16465
16466     p = fen;
16467
16468     /* [HGM] by default clear Crazyhouse holdings, if present */
16469     if(gameInfo.holdingsWidth) {
16470        for(i=0; i<BOARD_HEIGHT; i++) {
16471            board[i][0]             = EmptySquare; /* black holdings */
16472            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16473            board[i][1]             = (ChessSquare) 0; /* black counts */
16474            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16475        }
16476     }
16477
16478     /* Piece placement data */
16479     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16480         j = 0;
16481         for (;;) {
16482             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16483                 if (*p == '/') p++;
16484                 emptycount = gameInfo.boardWidth - j;
16485                 while (emptycount--)
16486                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16487                 break;
16488 #if(BOARD_FILES >= 10)
16489             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16490                 p++; emptycount=10;
16491                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16492                 while (emptycount--)
16493                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16494 #endif
16495             } else if (isdigit(*p)) {
16496                 emptycount = *p++ - '0';
16497                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16498                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16499                 while (emptycount--)
16500                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16501             } else if (*p == '+' || isalpha(*p)) {
16502                 if (j >= gameInfo.boardWidth) return FALSE;
16503                 if(*p=='+') {
16504                     piece = CharToPiece(*++p);
16505                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16506                     piece = (ChessSquare) (PROMOTED piece ); p++;
16507                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16508                 } else piece = CharToPiece(*p++);
16509
16510                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16511                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16512                     piece = (ChessSquare) (PROMOTED piece);
16513                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16514                     p++;
16515                 }
16516                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16517             } else {
16518                 return FALSE;
16519             }
16520         }
16521     }
16522     while (*p == '/' || *p == ' ') p++;
16523
16524     /* [HGM] look for Crazyhouse holdings here */
16525     while(*p==' ') p++;
16526     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16527         if(*p == '[') p++;
16528         if(*p == '-' ) p++; /* empty holdings */ else {
16529             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16530             /* if we would allow FEN reading to set board size, we would   */
16531             /* have to add holdings and shift the board read so far here   */
16532             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16533                 p++;
16534                 if((int) piece >= (int) BlackPawn ) {
16535                     i = (int)piece - (int)BlackPawn;
16536                     i = PieceToNumber((ChessSquare)i);
16537                     if( i >= gameInfo.holdingsSize ) return FALSE;
16538                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16539                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16540                 } else {
16541                     i = (int)piece - (int)WhitePawn;
16542                     i = PieceToNumber((ChessSquare)i);
16543                     if( i >= gameInfo.holdingsSize ) return FALSE;
16544                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16545                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16546                 }
16547             }
16548         }
16549         if(*p == ']') p++;
16550     }
16551
16552     while(*p == ' ') p++;
16553
16554     /* Active color */
16555     c = *p++;
16556     if(appData.colorNickNames) {
16557       if( c == appData.colorNickNames[0] ) c = 'w'; else
16558       if( c == appData.colorNickNames[1] ) c = 'b';
16559     }
16560     switch (c) {
16561       case 'w':
16562         *blackPlaysFirst = FALSE;
16563         break;
16564       case 'b':
16565         *blackPlaysFirst = TRUE;
16566         break;
16567       default:
16568         return FALSE;
16569     }
16570
16571     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16572     /* return the extra info in global variiables             */
16573
16574     /* set defaults in case FEN is incomplete */
16575     board[EP_STATUS] = EP_UNKNOWN;
16576     for(i=0; i<nrCastlingRights; i++ ) {
16577         board[CASTLING][i] =
16578             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16579     }   /* assume possible unless obviously impossible */
16580     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16581     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16582     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16583                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16584     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16585     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16586     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16587                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16588     FENrulePlies = 0;
16589
16590     while(*p==' ') p++;
16591     if(nrCastlingRights) {
16592       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16593           /* castling indicator present, so default becomes no castlings */
16594           for(i=0; i<nrCastlingRights; i++ ) {
16595                  board[CASTLING][i] = NoRights;
16596           }
16597       }
16598       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16599              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16600              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16601              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16602         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16603
16604         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16605             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16606             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16607         }
16608         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16609             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16610         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16611                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16612         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16613                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16614         switch(c) {
16615           case'K':
16616               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16617               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16618               board[CASTLING][2] = whiteKingFile;
16619               break;
16620           case'Q':
16621               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16622               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16623               board[CASTLING][2] = whiteKingFile;
16624               break;
16625           case'k':
16626               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16627               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16628               board[CASTLING][5] = blackKingFile;
16629               break;
16630           case'q':
16631               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16632               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16633               board[CASTLING][5] = blackKingFile;
16634           case '-':
16635               break;
16636           default: /* FRC castlings */
16637               if(c >= 'a') { /* black rights */
16638                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16639                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16640                   if(i == BOARD_RGHT) break;
16641                   board[CASTLING][5] = i;
16642                   c -= AAA;
16643                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16644                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16645                   if(c > i)
16646                       board[CASTLING][3] = c;
16647                   else
16648                       board[CASTLING][4] = c;
16649               } else { /* white rights */
16650                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16651                     if(board[0][i] == WhiteKing) break;
16652                   if(i == BOARD_RGHT) break;
16653                   board[CASTLING][2] = i;
16654                   c -= AAA - 'a' + 'A';
16655                   if(board[0][c] >= WhiteKing) break;
16656                   if(c > i)
16657                       board[CASTLING][0] = c;
16658                   else
16659                       board[CASTLING][1] = c;
16660               }
16661         }
16662       }
16663       for(i=0; i<nrCastlingRights; i++)
16664         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16665     if (appData.debugMode) {
16666         fprintf(debugFP, "FEN castling rights:");
16667         for(i=0; i<nrCastlingRights; i++)
16668         fprintf(debugFP, " %d", board[CASTLING][i]);
16669         fprintf(debugFP, "\n");
16670     }
16671
16672       while(*p==' ') p++;
16673     }
16674
16675     /* read e.p. field in games that know e.p. capture */
16676     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16677        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16678       if(*p=='-') {
16679         p++; board[EP_STATUS] = EP_NONE;
16680       } else {
16681          char c = *p++ - AAA;
16682
16683          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16684          if(*p >= '0' && *p <='9') p++;
16685          board[EP_STATUS] = c;
16686       }
16687     }
16688
16689
16690     if(sscanf(p, "%d", &i) == 1) {
16691         FENrulePlies = i; /* 50-move ply counter */
16692         /* (The move number is still ignored)    */
16693     }
16694
16695     return TRUE;
16696 }
16697
16698 void
16699 EditPositionPasteFEN (char *fen)
16700 {
16701   if (fen != NULL) {
16702     Board initial_position;
16703
16704     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16705       DisplayError(_("Bad FEN position in clipboard"), 0);
16706       return ;
16707     } else {
16708       int savedBlackPlaysFirst = blackPlaysFirst;
16709       EditPositionEvent();
16710       blackPlaysFirst = savedBlackPlaysFirst;
16711       CopyBoard(boards[0], initial_position);
16712       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16713       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16714       DisplayBothClocks();
16715       DrawPosition(FALSE, boards[currentMove]);
16716     }
16717   }
16718 }
16719
16720 static char cseq[12] = "\\   ";
16721
16722 Boolean
16723 set_cont_sequence (char *new_seq)
16724 {
16725     int len;
16726     Boolean ret;
16727
16728     // handle bad attempts to set the sequence
16729         if (!new_seq)
16730                 return 0; // acceptable error - no debug
16731
16732     len = strlen(new_seq);
16733     ret = (len > 0) && (len < sizeof(cseq));
16734     if (ret)
16735       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16736     else if (appData.debugMode)
16737       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16738     return ret;
16739 }
16740
16741 /*
16742     reformat a source message so words don't cross the width boundary.  internal
16743     newlines are not removed.  returns the wrapped size (no null character unless
16744     included in source message).  If dest is NULL, only calculate the size required
16745     for the dest buffer.  lp argument indicats line position upon entry, and it's
16746     passed back upon exit.
16747 */
16748 int
16749 wrap (char *dest, char *src, int count, int width, int *lp)
16750 {
16751     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16752
16753     cseq_len = strlen(cseq);
16754     old_line = line = *lp;
16755     ansi = len = clen = 0;
16756
16757     for (i=0; i < count; i++)
16758     {
16759         if (src[i] == '\033')
16760             ansi = 1;
16761
16762         // if we hit the width, back up
16763         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16764         {
16765             // store i & len in case the word is too long
16766             old_i = i, old_len = len;
16767
16768             // find the end of the last word
16769             while (i && src[i] != ' ' && src[i] != '\n')
16770             {
16771                 i--;
16772                 len--;
16773             }
16774
16775             // word too long?  restore i & len before splitting it
16776             if ((old_i-i+clen) >= width)
16777             {
16778                 i = old_i;
16779                 len = old_len;
16780             }
16781
16782             // extra space?
16783             if (i && src[i-1] == ' ')
16784                 len--;
16785
16786             if (src[i] != ' ' && src[i] != '\n')
16787             {
16788                 i--;
16789                 if (len)
16790                     len--;
16791             }
16792
16793             // now append the newline and continuation sequence
16794             if (dest)
16795                 dest[len] = '\n';
16796             len++;
16797             if (dest)
16798                 strncpy(dest+len, cseq, cseq_len);
16799             len += cseq_len;
16800             line = cseq_len;
16801             clen = cseq_len;
16802             continue;
16803         }
16804
16805         if (dest)
16806             dest[len] = src[i];
16807         len++;
16808         if (!ansi)
16809             line++;
16810         if (src[i] == '\n')
16811             line = 0;
16812         if (src[i] == 'm')
16813             ansi = 0;
16814     }
16815     if (dest && appData.debugMode)
16816     {
16817         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16818             count, width, line, len, *lp);
16819         show_bytes(debugFP, src, count);
16820         fprintf(debugFP, "\ndest: ");
16821         show_bytes(debugFP, dest, len);
16822         fprintf(debugFP, "\n");
16823     }
16824     *lp = dest ? line : old_line;
16825
16826     return len;
16827 }
16828
16829 // [HGM] vari: routines for shelving variations
16830 Boolean modeRestore = FALSE;
16831
16832 void
16833 PushInner (int firstMove, int lastMove)
16834 {
16835         int i, j, nrMoves = lastMove - firstMove;
16836
16837         // push current tail of game on stack
16838         savedResult[storedGames] = gameInfo.result;
16839         savedDetails[storedGames] = gameInfo.resultDetails;
16840         gameInfo.resultDetails = NULL;
16841         savedFirst[storedGames] = firstMove;
16842         savedLast [storedGames] = lastMove;
16843         savedFramePtr[storedGames] = framePtr;
16844         framePtr -= nrMoves; // reserve space for the boards
16845         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16846             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16847             for(j=0; j<MOVE_LEN; j++)
16848                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16849             for(j=0; j<2*MOVE_LEN; j++)
16850                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16851             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16852             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16853             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16854             pvInfoList[firstMove+i-1].depth = 0;
16855             commentList[framePtr+i] = commentList[firstMove+i];
16856             commentList[firstMove+i] = NULL;
16857         }
16858
16859         storedGames++;
16860         forwardMostMove = firstMove; // truncate game so we can start variation
16861 }
16862
16863 void
16864 PushTail (int firstMove, int lastMove)
16865 {
16866         if(appData.icsActive) { // only in local mode
16867                 forwardMostMove = currentMove; // mimic old ICS behavior
16868                 return;
16869         }
16870         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16871
16872         PushInner(firstMove, lastMove);
16873         if(storedGames == 1) GreyRevert(FALSE);
16874         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16875 }
16876
16877 void
16878 PopInner (Boolean annotate)
16879 {
16880         int i, j, nrMoves;
16881         char buf[8000], moveBuf[20];
16882
16883         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16884         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16885         nrMoves = savedLast[storedGames] - currentMove;
16886         if(annotate) {
16887                 int cnt = 10;
16888                 if(!WhiteOnMove(currentMove))
16889                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16890                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16891                 for(i=currentMove; i<forwardMostMove; i++) {
16892                         if(WhiteOnMove(i))
16893                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16894                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16895                         strcat(buf, moveBuf);
16896                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16897                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16898                 }
16899                 strcat(buf, ")");
16900         }
16901         for(i=1; i<=nrMoves; i++) { // copy last variation back
16902             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16903             for(j=0; j<MOVE_LEN; j++)
16904                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16905             for(j=0; j<2*MOVE_LEN; j++)
16906                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16907             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16908             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16909             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16910             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16911             commentList[currentMove+i] = commentList[framePtr+i];
16912             commentList[framePtr+i] = NULL;
16913         }
16914         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16915         framePtr = savedFramePtr[storedGames];
16916         gameInfo.result = savedResult[storedGames];
16917         if(gameInfo.resultDetails != NULL) {
16918             free(gameInfo.resultDetails);
16919       }
16920         gameInfo.resultDetails = savedDetails[storedGames];
16921         forwardMostMove = currentMove + nrMoves;
16922 }
16923
16924 Boolean
16925 PopTail (Boolean annotate)
16926 {
16927         if(appData.icsActive) return FALSE; // only in local mode
16928         if(!storedGames) return FALSE; // sanity
16929         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16930
16931         PopInner(annotate);
16932         if(currentMove < forwardMostMove) ForwardEvent(); else
16933         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16934
16935         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16936         return TRUE;
16937 }
16938
16939 void
16940 CleanupTail ()
16941 {       // remove all shelved variations
16942         int i;
16943         for(i=0; i<storedGames; i++) {
16944             if(savedDetails[i])
16945                 free(savedDetails[i]);
16946             savedDetails[i] = NULL;
16947         }
16948         for(i=framePtr; i<MAX_MOVES; i++) {
16949                 if(commentList[i]) free(commentList[i]);
16950                 commentList[i] = NULL;
16951         }
16952         framePtr = MAX_MOVES-1;
16953         storedGames = 0;
16954 }
16955
16956 void
16957 LoadVariation (int index, char *text)
16958 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16959         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16960         int level = 0, move;
16961
16962         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16963         // first find outermost bracketing variation
16964         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16965             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16966                 if(*p == '{') wait = '}'; else
16967                 if(*p == '[') wait = ']'; else
16968                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16969                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16970             }
16971             if(*p == wait) wait = NULLCHAR; // closing ]} found
16972             p++;
16973         }
16974         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16975         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16976         end[1] = NULLCHAR; // clip off comment beyond variation
16977         ToNrEvent(currentMove-1);
16978         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16979         // kludge: use ParsePV() to append variation to game
16980         move = currentMove;
16981         ParsePV(start, TRUE, TRUE);
16982         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16983         ClearPremoveHighlights();
16984         CommentPopDown();
16985         ToNrEvent(currentMove+1);
16986 }
16987