Fix initial holdings ICS seirawan games
[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 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
397 /*
398     [AS] Note: sometimes, the sscanf() function is used to parse the input
399     into a fixed-size buffer. Because of this, we must be prepared to
400     receive strings as long as the size of the input buffer, which is currently
401     set to 4K for Windows and 8K for the rest.
402     So, we must either allocate sufficiently large buffers here, or
403     reduce the size of the input buffer in the input reading part.
404 */
405
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
409
410 ChessProgramState first, second, pairing;
411
412 /* premove variables */
413 int premoveToX = 0;
414 int premoveToY = 0;
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
418 int gotPremove = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
421
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
424
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
452
453 int have_sent_ICS_logon = 0;
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
466
467 /* animateTraining preserves the state of appData.animate
468  * when Training mode is activated. This allows the
469  * response to be animated when appData.animate == TRUE and
470  * appData.animateDragging == TRUE.
471  */
472 Boolean animateTraining;
473
474 GameInfo gameInfo;
475
476 AppData appData;
477
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char  initialRights[BOARD_FILES];
482 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int   initialRulePlies, FENrulePlies;
484 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int loadFlag = 0;
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
488
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
492 int storedGames = 0;
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
498
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
596 };
597
598 #ifdef GOTHIC
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
604 };
605 #else // !GOTHIC
606 #define GothicArray CapablancaArray
607 #endif // !GOTHIC
608
609 #ifdef FALCON
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
615 };
616 #else // !FALCON
617 #define FalconArray CapablancaArray
618 #endif // !FALCON
619
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
626
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 };
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
637
638
639 Board initialPosition;
640
641
642 /* Convert str to a rating. Checks for special cases of "----",
643
644    "++++", etc. Also strips ()'s */
645 int
646 string_to_rating(str)
647   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 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(params[0]) {
900         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     appData.seedBase = random() + (random()<<15);
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len >= MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len >= MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for draw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantGrand:      /* should work */
1122       case VariantSpartan:    /* should work */
1123         break;
1124       }
1125     }
1126
1127 }
1128
1129 int NextIntegerFromString( char ** str, long * value )
1130 {
1131     int result = -1;
1132     char * s = *str;
1133
1134     while( *s == ' ' || *s == '\t' ) {
1135         s++;
1136     }
1137
1138     *value = 0;
1139
1140     if( *s >= '0' && *s <= '9' ) {
1141         while( *s >= '0' && *s <= '9' ) {
1142             *value = *value * 10 + (*s - '0');
1143             s++;
1144         }
1145
1146         result = 0;
1147     }
1148
1149     *str = s;
1150
1151     return result;
1152 }
1153
1154 int NextTimeControlFromString( char ** str, long * value )
1155 {
1156     long temp;
1157     int result = NextIntegerFromString( str, &temp );
1158
1159     if( result == 0 ) {
1160         *value = temp * 60; /* Minutes */
1161         if( **str == ':' ) {
1162             (*str)++;
1163             result = NextIntegerFromString( str, &temp );
1164             *value += temp; /* Seconds */
1165         }
1166     }
1167
1168     return result;
1169 }
1170
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173     int result = -1, type = 0; long temp, temp2;
1174
1175     if(**str != ':') return -1; // old params remain in force!
1176     (*str)++;
1177     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178     if( NextIntegerFromString( str, &temp ) ) return -1;
1179     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180
1181     if(**str != '/') {
1182         /* time only: incremental or sudden-death time control */
1183         if(**str == '+') { /* increment follows; read it */
1184             (*str)++;
1185             if(**str == '!') type = *(*str)++; // Bronstein TC
1186             if(result = NextIntegerFromString( str, &temp2)) return -1;
1187             *inc = temp2 * 1000;
1188             if(**str == '.') { // read fraction of increment
1189                 char *start = ++(*str);
1190                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191                 temp2 *= 1000;
1192                 while(start++ < *str) temp2 /= 10;
1193                 *inc += temp2;
1194             }
1195         } else *inc = 0;
1196         *moves = 0; *tc = temp * 1000; *incType = type;
1197         return 0;
1198     }
1199
1200     (*str)++; /* classical time control */
1201     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1202
1203     if(result == 0) {
1204         *moves = temp;
1205         *tc    = temp2 * 1000;
1206         *inc   = 0;
1207         *incType = type;
1208     }
1209     return result;
1210 }
1211
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 {   /* [HGM] get time to add from the multi-session time-control string */
1214     int incType, moves=1; /* kludge to force reading of first session */
1215     long time, increment;
1216     char *s = tcString;
1217
1218     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220     do {
1221         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224         if(movenr == -1) return time;    /* last move before new session     */
1225         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227         if(!moves) return increment;     /* current session is incremental   */
1228         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229     } while(movenr >= -1);               /* try again for next session       */
1230
1231     return 0; // no new time quota on this move
1232 }
1233
1234 int
1235 ParseTimeControl(tc, ti, mps)
1236      char *tc;
1237      float ti;
1238      int mps;
1239 {
1240   long tc1;
1241   long tc2;
1242   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243   int min, sec=0;
1244
1245   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1248   if(ti > 0) {
1249
1250     if(mps)
1251       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252     else 
1253       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254   } else {
1255     if(mps)
1256       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257     else 
1258       snprintf(buf, MSG_SIZ, ":%s", mytc);
1259   }
1260   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261   
1262   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1263     return FALSE;
1264   }
1265
1266   if( *tc == '/' ) {
1267     /* Parse second time control */
1268     tc++;
1269
1270     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1271       return FALSE;
1272     }
1273
1274     if( tc2 == 0 ) {
1275       return FALSE;
1276     }
1277
1278     timeControl_2 = tc2 * 1000;
1279   }
1280   else {
1281     timeControl_2 = 0;
1282   }
1283
1284   if( tc1 == 0 ) {
1285     return FALSE;
1286   }
1287
1288   timeControl = tc1 * 1000;
1289
1290   if (ti >= 0) {
1291     timeIncrement = ti * 1000;  /* convert to ms */
1292     movesPerSession = 0;
1293   } else {
1294     timeIncrement = 0;
1295     movesPerSession = mps;
1296   }
1297   return TRUE;
1298 }
1299
1300 void
1301 InitBackEnd2()
1302 {
1303     if (appData.debugMode) {
1304         fprintf(debugFP, "%s\n", programVersion);
1305     }
1306
1307     set_cont_sequence(appData.wrapContSeq);
1308     if (appData.matchGames > 0) {
1309         appData.matchMode = TRUE;
1310     } else if (appData.matchMode) {
1311         appData.matchGames = 1;
1312     }
1313     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314         appData.matchGames = appData.sameColorGames;
1315     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318     }
1319     Reset(TRUE, FALSE);
1320     if (appData.noChessProgram || first.protocolVersion == 1) {
1321       InitBackEnd3();
1322     } else {
1323       /* kludge: allow timeout for initial "feature" commands */
1324       FreezeUI();
1325       DisplayMessage("", _("Starting chess program"));
1326       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1327     }
1328 }
1329
1330 int
1331 CalculateIndex(int index, int gameNr)
1332 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333     int res;
1334     if(index > 0) return index; // fixed nmber
1335     if(index == 0) return 1;
1336     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1338     return res;
1339 }
1340
1341 int
1342 LoadGameOrPosition(int gameNr)
1343 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344     if (*appData.loadGameFile != NULLCHAR) {
1345         if (!LoadGameFromFile(appData.loadGameFile,
1346                 CalculateIndex(appData.loadGameIndex, gameNr),
1347                               appData.loadGameFile, FALSE)) {
1348             DisplayFatalError(_("Bad game file"), 0, 1);
1349             return 0;
1350         }
1351     } else if (*appData.loadPositionFile != NULLCHAR) {
1352         if (!LoadPositionFromFile(appData.loadPositionFile,
1353                 CalculateIndex(appData.loadPositionIndex, gameNr),
1354                                   appData.loadPositionFile)) {
1355             DisplayFatalError(_("Bad position file"), 0, 1);
1356             return 0;
1357         }
1358     }
1359     return 1;
1360 }
1361
1362 void
1363 ReserveGame(int gameNr, char resChar)
1364 {
1365     FILE *tf = fopen(appData.tourneyFile, "r+");
1366     char *p, *q, c, buf[MSG_SIZ];
1367     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368     safeStrCpy(buf, lastMsg, MSG_SIZ);
1369     DisplayMessage(_("Pick new game"), "");
1370     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371     ParseArgsFromFile(tf);
1372     p = q = appData.results;
1373     if(appData.debugMode) {
1374       char *r = appData.participants;
1375       fprintf(debugFP, "results = '%s'\n", p);
1376       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377       fprintf(debugFP, "\n");
1378     }
1379     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380     nextGame = q - p;
1381     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382     safeStrCpy(q, p, strlen(p) + 2);
1383     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387         q[nextGame] = '*';
1388     }
1389     fseek(tf, -(strlen(p)+4), SEEK_END);
1390     c = fgetc(tf);
1391     if(c != '"') // depending on DOS or Unix line endings we can be one off
1392          fseek(tf, -(strlen(p)+2), SEEK_END);
1393     else fseek(tf, -(strlen(p)+3), SEEK_END);
1394     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395     DisplayMessage(buf, "");
1396     free(p); appData.results = q;
1397     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399         UnloadEngine(&first);  // next game belongs to other pairing;
1400         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1401     }
1402 }
1403
1404 void
1405 MatchEvent(int mode)
1406 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407         int dummy;
1408         if(matchMode) { // already in match mode: switch it off
1409             abortMatch = TRUE;
1410             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1411             return;
1412         }
1413 //      if(gameMode != BeginningOfGame) {
1414 //          DisplayError(_("You can only start a match from the initial position."), 0);
1415 //          return;
1416 //      }
1417         abortMatch = FALSE;
1418         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419         /* Set up machine vs. machine match */
1420         nextGame = 0;
1421         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422         if(appData.tourneyFile[0]) {
1423             ReserveGame(-1, 0);
1424             if(nextGame > appData.matchGames) {
1425                 char buf[MSG_SIZ];
1426                 if(strchr(appData.results, '*') == NULL) {
1427                     FILE *f;
1428                     appData.tourneyCycles++;
1429                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1430                         fclose(f);
1431                         NextTourneyGame(-1, &dummy);
1432                         ReserveGame(-1, 0);
1433                         if(nextGame <= appData.matchGames) {
1434                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435                             matchMode = mode;
1436                             ScheduleDelayedEvent(NextMatchGame, 10000);
1437                             return;
1438                         }
1439                     }
1440                 }
1441                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442                 DisplayError(buf, 0);
1443                 appData.tourneyFile[0] = 0;
1444                 return;
1445             }
1446         } else
1447         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1448             DisplayFatalError(_("Can't have a match with no chess programs"),
1449                               0, 2);
1450             return;
1451         }
1452         matchMode = mode;
1453         matchGame = roundNr = 1;
1454         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1455         NextMatchGame();
1456 }
1457
1458 void
1459 InitBackEnd3 P((void))
1460 {
1461     GameMode initialMode;
1462     char buf[MSG_SIZ];
1463     int err, len;
1464
1465     InitChessProgram(&first, startedFromSetupPosition);
1466
1467     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1468         free(programVersion);
1469         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1471     }
1472
1473     if (appData.icsActive) {
1474 #ifdef WIN32
1475         /* [DM] Make a console window if needed [HGM] merged ifs */
1476         ConsoleCreate();
1477 #endif
1478         err = establish();
1479         if (err != 0)
1480           {
1481             if (*appData.icsCommPort != NULLCHAR)
1482               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483                              appData.icsCommPort);
1484             else
1485               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486                         appData.icsHost, appData.icsPort);
1487
1488             if( (len >= MSG_SIZ) && appData.debugMode )
1489               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490
1491             DisplayFatalError(buf, err, 1);
1492             return;
1493         }
1494         SetICSMode();
1495         telnetISR =
1496           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497         fromUserISR =
1498           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501     } else if (appData.noChessProgram) {
1502         SetNCPMode();
1503     } else {
1504         SetGNUMode();
1505     }
1506
1507     if (*appData.cmailGameName != NULLCHAR) {
1508         SetCmailMode();
1509         OpenLoopback(&cmailPR);
1510         cmailISR =
1511           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1512     }
1513
1514     ThawUI();
1515     DisplayMessage("", "");
1516     if (StrCaseCmp(appData.initialMode, "") == 0) {
1517       initialMode = BeginningOfGame;
1518       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1522         ModeHighlight();
1523       }
1524     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525       initialMode = TwoMachinesPlay;
1526     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527       initialMode = AnalyzeFile;
1528     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529       initialMode = AnalyzeMode;
1530     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531       initialMode = MachinePlaysWhite;
1532     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533       initialMode = MachinePlaysBlack;
1534     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535       initialMode = EditGame;
1536     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537       initialMode = EditPosition;
1538     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539       initialMode = Training;
1540     } else {
1541       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542       if( (len >= MSG_SIZ) && appData.debugMode )
1543         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544
1545       DisplayFatalError(buf, 0, 2);
1546       return;
1547     }
1548
1549     if (appData.matchMode) {
1550         if(appData.tourneyFile[0]) { // start tourney from command line
1551             FILE *f;
1552             if(f = fopen(appData.tourneyFile, "r")) {
1553                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554                 fclose(f);
1555                 appData.clockMode = TRUE;
1556                 SetGNUMode();
1557             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1558         }
1559         MatchEvent(TRUE);
1560     } else if (*appData.cmailGameName != NULLCHAR) {
1561         /* Set up cmail mode */
1562         ReloadCmailMsgEvent(TRUE);
1563     } else {
1564         /* Set up other modes */
1565         if (initialMode == AnalyzeFile) {
1566           if (*appData.loadGameFile == NULLCHAR) {
1567             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568             return;
1569           }
1570         }
1571         if (*appData.loadGameFile != NULLCHAR) {
1572             (void) LoadGameFromFile(appData.loadGameFile,
1573                                     appData.loadGameIndex,
1574                                     appData.loadGameFile, TRUE);
1575         } else if (*appData.loadPositionFile != NULLCHAR) {
1576             (void) LoadPositionFromFile(appData.loadPositionFile,
1577                                         appData.loadPositionIndex,
1578                                         appData.loadPositionFile);
1579             /* [HGM] try to make self-starting even after FEN load */
1580             /* to allow automatic setup of fairy variants with wtm */
1581             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582                 gameMode = BeginningOfGame;
1583                 setboardSpoiledMachineBlack = 1;
1584             }
1585             /* [HGM] loadPos: make that every new game uses the setup */
1586             /* from file as long as we do not switch variant          */
1587             if(!blackPlaysFirst) {
1588                 startedFromPositionFile = TRUE;
1589                 CopyBoard(filePosition, boards[0]);
1590             }
1591         }
1592         if (initialMode == AnalyzeMode) {
1593           if (appData.noChessProgram) {
1594             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1595             return;
1596           }
1597           if (appData.icsActive) {
1598             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599             return;
1600           }
1601           AnalyzeModeEvent();
1602         } else if (initialMode == AnalyzeFile) {
1603           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604           ShowThinkingEvent();
1605           AnalyzeFileEvent();
1606           AnalysisPeriodicEvent(1);
1607         } else if (initialMode == MachinePlaysWhite) {
1608           if (appData.noChessProgram) {
1609             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610                               0, 2);
1611             return;
1612           }
1613           if (appData.icsActive) {
1614             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615                               0, 2);
1616             return;
1617           }
1618           MachineWhiteEvent();
1619         } else if (initialMode == MachinePlaysBlack) {
1620           if (appData.noChessProgram) {
1621             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622                               0, 2);
1623             return;
1624           }
1625           if (appData.icsActive) {
1626             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627                               0, 2);
1628             return;
1629           }
1630           MachineBlackEvent();
1631         } else if (initialMode == TwoMachinesPlay) {
1632           if (appData.noChessProgram) {
1633             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634                               0, 2);
1635             return;
1636           }
1637           if (appData.icsActive) {
1638             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1639                               0, 2);
1640             return;
1641           }
1642           TwoMachinesEvent();
1643         } else if (initialMode == EditGame) {
1644           EditGameEvent();
1645         } else if (initialMode == EditPosition) {
1646           EditPositionEvent();
1647         } else if (initialMode == Training) {
1648           if (*appData.loadGameFile == NULLCHAR) {
1649             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1650             return;
1651           }
1652           TrainingEvent();
1653         }
1654     }
1655 }
1656
1657 void
1658 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1659 {
1660     DisplayBook(current+1);
1661
1662     MoveHistorySet( movelist, first, last, current, pvInfoList );
1663
1664     EvalGraphSet( first, last, current, pvInfoList );
1665
1666     MakeEngineOutputTitle();
1667 }
1668
1669 /*
1670  * Establish will establish a contact to a remote host.port.
1671  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672  *  used to talk to the host.
1673  * Returns 0 if okay, error code if not.
1674  */
1675 int
1676 establish()
1677 {
1678     char buf[MSG_SIZ];
1679
1680     if (*appData.icsCommPort != NULLCHAR) {
1681         /* Talk to the host through a serial comm port */
1682         return OpenCommPort(appData.icsCommPort, &icsPR);
1683
1684     } else if (*appData.gateway != NULLCHAR) {
1685         if (*appData.remoteShell == NULLCHAR) {
1686             /* Use the rcmd protocol to run telnet program on a gateway host */
1687             snprintf(buf, sizeof(buf), "%s %s %s",
1688                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1689             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690
1691         } else {
1692             /* Use the rsh program to run telnet program on a gateway host */
1693             if (*appData.remoteUser == NULLCHAR) {
1694                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695                         appData.gateway, appData.telnetProgram,
1696                         appData.icsHost, appData.icsPort);
1697             } else {
1698                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699                         appData.remoteShell, appData.gateway,
1700                         appData.remoteUser, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             }
1703             return StartChildProcess(buf, "", &icsPR);
1704
1705         }
1706     } else if (appData.useTelnet) {
1707         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708
1709     } else {
1710         /* TCP socket interface differs somewhat between
1711            Unix and NT; handle details in the front end.
1712            */
1713         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714     }
1715 }
1716
1717 void EscapeExpand(char *p, char *q)
1718 {       // [HGM] initstring: routine to shape up string arguments
1719         while(*p++ = *q++) if(p[-1] == '\\')
1720             switch(*q++) {
1721                 case 'n': p[-1] = '\n'; break;
1722                 case 'r': p[-1] = '\r'; break;
1723                 case 't': p[-1] = '\t'; break;
1724                 case '\\': p[-1] = '\\'; break;
1725                 case 0: *p = 0; return;
1726                 default: p[-1] = q[-1]; break;
1727             }
1728 }
1729
1730 void
1731 show_bytes(fp, buf, count)
1732      FILE *fp;
1733      char *buf;
1734      int count;
1735 {
1736     while (count--) {
1737         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1738             fprintf(fp, "\\%03o", *buf & 0xff);
1739         } else {
1740             putc(*buf, fp);
1741         }
1742         buf++;
1743     }
1744     fflush(fp);
1745 }
1746
1747 /* Returns an errno value */
1748 int
1749 OutputMaybeTelnet(pr, message, count, outError)
1750      ProcRef pr;
1751      char *message;
1752      int count;
1753      int *outError;
1754 {
1755     char buf[8192], *p, *q, *buflim;
1756     int left, newcount, outcount;
1757
1758     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1759         *appData.gateway != NULLCHAR) {
1760         if (appData.debugMode) {
1761             fprintf(debugFP, ">ICS: ");
1762             show_bytes(debugFP, message, count);
1763             fprintf(debugFP, "\n");
1764         }
1765         return OutputToProcess(pr, message, count, outError);
1766     }
1767
1768     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769     p = message;
1770     q = buf;
1771     left = count;
1772     newcount = 0;
1773     while (left) {
1774         if (q >= buflim) {
1775             if (appData.debugMode) {
1776                 fprintf(debugFP, ">ICS: ");
1777                 show_bytes(debugFP, buf, newcount);
1778                 fprintf(debugFP, "\n");
1779             }
1780             outcount = OutputToProcess(pr, buf, newcount, outError);
1781             if (outcount < newcount) return -1; /* to be sure */
1782             q = buf;
1783             newcount = 0;
1784         }
1785         if (*p == '\n') {
1786             *q++ = '\r';
1787             newcount++;
1788         } else if (((unsigned char) *p) == TN_IAC) {
1789             *q++ = (char) TN_IAC;
1790             newcount ++;
1791         }
1792         *q++ = *p++;
1793         newcount++;
1794         left--;
1795     }
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     return count;
1804 }
1805
1806 void
1807 read_from_player(isr, closure, message, count, error)
1808      InputSourceRef isr;
1809      VOIDSTAR closure;
1810      char *message;
1811      int count;
1812      int error;
1813 {
1814     int outError, outCount;
1815     static int gotEof = 0;
1816
1817     /* Pass data read from player on to ICS */
1818     if (count > 0) {
1819         gotEof = 0;
1820         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1821         if (outCount < count) {
1822             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1823         }
1824     } else if (count < 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1827     } else if (gotEof++ > 0) {
1828         RemoveInputSource(isr);
1829         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1830     }
1831 }
1832
1833 void
1834 KeepAlive()
1835 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1836     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1837     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1838     SendToICS("date\n");
1839     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1840 }
1841
1842 /* added routine for printf style output to ics */
1843 void ics_printf(char *format, ...)
1844 {
1845     char buffer[MSG_SIZ];
1846     va_list args;
1847
1848     va_start(args, format);
1849     vsnprintf(buffer, sizeof(buffer), format, args);
1850     buffer[sizeof(buffer)-1] = '\0';
1851     SendToICS(buffer);
1852     va_end(args);
1853 }
1854
1855 void
1856 SendToICS(s)
1857      char *s;
1858 {
1859     int count, outCount, outError;
1860
1861     if (icsPR == NoProc) return;
1862
1863     count = strlen(s);
1864     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1865     if (outCount < count) {
1866         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1867     }
1868 }
1869
1870 /* This is used for sending logon scripts to the ICS. Sending
1871    without a delay causes problems when using timestamp on ICC
1872    (at least on my machine). */
1873 void
1874 SendToICSDelayed(s,msdelay)
1875      char *s;
1876      long msdelay;
1877 {
1878     int count, outCount, outError;
1879
1880     if (icsPR == NoProc) return;
1881
1882     count = strlen(s);
1883     if (appData.debugMode) {
1884         fprintf(debugFP, ">ICS: ");
1885         show_bytes(debugFP, s, count);
1886         fprintf(debugFP, "\n");
1887     }
1888     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1889                                       msdelay);
1890     if (outCount < count) {
1891         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1892     }
1893 }
1894
1895
1896 /* Remove all highlighting escape sequences in s
1897    Also deletes any suffix starting with '('
1898    */
1899 char *
1900 StripHighlightAndTitle(s)
1901      char *s;
1902 {
1903     static char retbuf[MSG_SIZ];
1904     char *p = retbuf;
1905
1906     while (*s != NULLCHAR) {
1907         while (*s == '\033') {
1908             while (*s != NULLCHAR && !isalpha(*s)) s++;
1909             if (*s != NULLCHAR) s++;
1910         }
1911         while (*s != NULLCHAR && *s != '\033') {
1912             if (*s == '(' || *s == '[') {
1913                 *p = NULLCHAR;
1914                 return retbuf;
1915             }
1916             *p++ = *s++;
1917         }
1918     }
1919     *p = NULLCHAR;
1920     return retbuf;
1921 }
1922
1923 /* Remove all highlighting escape sequences in s */
1924 char *
1925 StripHighlight(s)
1926      char *s;
1927 {
1928     static char retbuf[MSG_SIZ];
1929     char *p = retbuf;
1930
1931     while (*s != NULLCHAR) {
1932         while (*s == '\033') {
1933             while (*s != NULLCHAR && !isalpha(*s)) s++;
1934             if (*s != NULLCHAR) s++;
1935         }
1936         while (*s != NULLCHAR && *s != '\033') {
1937             *p++ = *s++;
1938         }
1939     }
1940     *p = NULLCHAR;
1941     return retbuf;
1942 }
1943
1944 char *variantNames[] = VARIANT_NAMES;
1945 char *
1946 VariantName(v)
1947      VariantClass v;
1948 {
1949     return variantNames[v];
1950 }
1951
1952
1953 /* Identify a variant from the strings the chess servers use or the
1954    PGN Variant tag names we use. */
1955 VariantClass
1956 StringToVariant(e)
1957      char *e;
1958 {
1959     char *p;
1960     int wnum = -1;
1961     VariantClass v = VariantNormal;
1962     int i, found = FALSE;
1963     char buf[MSG_SIZ];
1964     int len;
1965
1966     if (!e) return v;
1967
1968     /* [HGM] skip over optional board-size prefixes */
1969     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1970         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1971         while( *e++ != '_');
1972     }
1973
1974     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1975         v = VariantNormal;
1976         found = TRUE;
1977     } else
1978     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1979       if (StrCaseStr(e, variantNames[i])) {
1980         v = (VariantClass) i;
1981         found = TRUE;
1982         break;
1983       }
1984     }
1985
1986     if (!found) {
1987       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1988           || StrCaseStr(e, "wild/fr")
1989           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1990         v = VariantFischeRandom;
1991       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1992                  (i = 1, p = StrCaseStr(e, "w"))) {
1993         p += i;
1994         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1995         if (isdigit(*p)) {
1996           wnum = atoi(p);
1997         } else {
1998           wnum = -1;
1999         }
2000         switch (wnum) {
2001         case 0: /* FICS only, actually */
2002         case 1:
2003           /* Castling legal even if K starts on d-file */
2004           v = VariantWildCastle;
2005           break;
2006         case 2:
2007         case 3:
2008         case 4:
2009           /* Castling illegal even if K & R happen to start in
2010              normal positions. */
2011           v = VariantNoCastle;
2012           break;
2013         case 5:
2014         case 7:
2015         case 8:
2016         case 10:
2017         case 11:
2018         case 12:
2019         case 13:
2020         case 14:
2021         case 15:
2022         case 18:
2023         case 19:
2024           /* Castling legal iff K & R start in normal positions */
2025           v = VariantNormal;
2026           break;
2027         case 6:
2028         case 20:
2029         case 21:
2030           /* Special wilds for position setup; unclear what to do here */
2031           v = VariantLoadable;
2032           break;
2033         case 9:
2034           /* Bizarre ICC game */
2035           v = VariantTwoKings;
2036           break;
2037         case 16:
2038           v = VariantKriegspiel;
2039           break;
2040         case 17:
2041           v = VariantLosers;
2042           break;
2043         case 22:
2044           v = VariantFischeRandom;
2045           break;
2046         case 23:
2047           v = VariantCrazyhouse;
2048           break;
2049         case 24:
2050           v = VariantBughouse;
2051           break;
2052         case 25:
2053           v = Variant3Check;
2054           break;
2055         case 26:
2056           /* Not quite the same as FICS suicide! */
2057           v = VariantGiveaway;
2058           break;
2059         case 27:
2060           v = VariantAtomic;
2061           break;
2062         case 28:
2063           v = VariantShatranj;
2064           break;
2065
2066         /* Temporary names for future ICC types.  The name *will* change in
2067            the next xboard/WinBoard release after ICC defines it. */
2068         case 29:
2069           v = Variant29;
2070           break;
2071         case 30:
2072           v = Variant30;
2073           break;
2074         case 31:
2075           v = Variant31;
2076           break;
2077         case 32:
2078           v = Variant32;
2079           break;
2080         case 33:
2081           v = Variant33;
2082           break;
2083         case 34:
2084           v = Variant34;
2085           break;
2086         case 35:
2087           v = Variant35;
2088           break;
2089         case 36:
2090           v = Variant36;
2091           break;
2092         case 37:
2093           v = VariantShogi;
2094           break;
2095         case 38:
2096           v = VariantXiangqi;
2097           break;
2098         case 39:
2099           v = VariantCourier;
2100           break;
2101         case 40:
2102           v = VariantGothic;
2103           break;
2104         case 41:
2105           v = VariantCapablanca;
2106           break;
2107         case 42:
2108           v = VariantKnightmate;
2109           break;
2110         case 43:
2111           v = VariantFairy;
2112           break;
2113         case 44:
2114           v = VariantCylinder;
2115           break;
2116         case 45:
2117           v = VariantFalcon;
2118           break;
2119         case 46:
2120           v = VariantCapaRandom;
2121           break;
2122         case 47:
2123           v = VariantBerolina;
2124           break;
2125         case 48:
2126           v = VariantJanus;
2127           break;
2128         case 49:
2129           v = VariantSuper;
2130           break;
2131         case 50:
2132           v = VariantGreat;
2133           break;
2134         case -1:
2135           /* Found "wild" or "w" in the string but no number;
2136              must assume it's normal chess. */
2137           v = VariantNormal;
2138           break;
2139         default:
2140           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2141           if( (len >= MSG_SIZ) && appData.debugMode )
2142             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2143
2144           DisplayError(buf, 0);
2145           v = VariantUnknown;
2146           break;
2147         }
2148       }
2149     }
2150     if (appData.debugMode) {
2151       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2152               e, wnum, VariantName(v));
2153     }
2154     return v;
2155 }
2156
2157 static int leftover_start = 0, leftover_len = 0;
2158 char star_match[STAR_MATCH_N][MSG_SIZ];
2159
2160 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2161    advance *index beyond it, and set leftover_start to the new value of
2162    *index; else return FALSE.  If pattern contains the character '*', it
2163    matches any sequence of characters not containing '\r', '\n', or the
2164    character following the '*' (if any), and the matched sequence(s) are
2165    copied into star_match.
2166    */
2167 int
2168 looking_at(buf, index, pattern)
2169      char *buf;
2170      int *index;
2171      char *pattern;
2172 {
2173     char *bufp = &buf[*index], *patternp = pattern;
2174     int star_count = 0;
2175     char *matchp = star_match[0];
2176
2177     for (;;) {
2178         if (*patternp == NULLCHAR) {
2179             *index = leftover_start = bufp - buf;
2180             *matchp = NULLCHAR;
2181             return TRUE;
2182         }
2183         if (*bufp == NULLCHAR) return FALSE;
2184         if (*patternp == '*') {
2185             if (*bufp == *(patternp + 1)) {
2186                 *matchp = NULLCHAR;
2187                 matchp = star_match[++star_count];
2188                 patternp += 2;
2189                 bufp++;
2190                 continue;
2191             } else if (*bufp == '\n' || *bufp == '\r') {
2192                 patternp++;
2193                 if (*patternp == NULLCHAR)
2194                   continue;
2195                 else
2196                   return FALSE;
2197             } else {
2198                 *matchp++ = *bufp++;
2199                 continue;
2200             }
2201         }
2202         if (*patternp != *bufp) return FALSE;
2203         patternp++;
2204         bufp++;
2205     }
2206 }
2207
2208 void
2209 SendToPlayer(data, length)
2210      char *data;
2211      int length;
2212 {
2213     int error, outCount;
2214     outCount = OutputToProcess(NoProc, data, length, &error);
2215     if (outCount < length) {
2216         DisplayFatalError(_("Error writing to display"), error, 1);
2217     }
2218 }
2219
2220 void
2221 PackHolding(packed, holding)
2222      char packed[];
2223      char *holding;
2224 {
2225     char *p = holding;
2226     char *q = packed;
2227     int runlength = 0;
2228     int curr = 9999;
2229     do {
2230         if (*p == curr) {
2231             runlength++;
2232         } else {
2233             switch (runlength) {
2234               case 0:
2235                 break;
2236               case 1:
2237                 *q++ = curr;
2238                 break;
2239               case 2:
2240                 *q++ = curr;
2241                 *q++ = curr;
2242                 break;
2243               default:
2244                 sprintf(q, "%d", runlength);
2245                 while (*q) q++;
2246                 *q++ = curr;
2247                 break;
2248             }
2249             runlength = 1;
2250             curr = *p;
2251         }
2252     } while (*p++);
2253     *q = NULLCHAR;
2254 }
2255
2256 /* Telnet protocol requests from the front end */
2257 void
2258 TelnetRequest(ddww, option)
2259      unsigned char ddww, option;
2260 {
2261     unsigned char msg[3];
2262     int outCount, outError;
2263
2264     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2265
2266     if (appData.debugMode) {
2267         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2268         switch (ddww) {
2269           case TN_DO:
2270             ddwwStr = "DO";
2271             break;
2272           case TN_DONT:
2273             ddwwStr = "DONT";
2274             break;
2275           case TN_WILL:
2276             ddwwStr = "WILL";
2277             break;
2278           case TN_WONT:
2279             ddwwStr = "WONT";
2280             break;
2281           default:
2282             ddwwStr = buf1;
2283             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2284             break;
2285         }
2286         switch (option) {
2287           case TN_ECHO:
2288             optionStr = "ECHO";
2289             break;
2290           default:
2291             optionStr = buf2;
2292             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2293             break;
2294         }
2295         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2296     }
2297     msg[0] = TN_IAC;
2298     msg[1] = ddww;
2299     msg[2] = option;
2300     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2301     if (outCount < 3) {
2302         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2303     }
2304 }
2305
2306 void
2307 DoEcho()
2308 {
2309     if (!appData.icsActive) return;
2310     TelnetRequest(TN_DO, TN_ECHO);
2311 }
2312
2313 void
2314 DontEcho()
2315 {
2316     if (!appData.icsActive) return;
2317     TelnetRequest(TN_DONT, TN_ECHO);
2318 }
2319
2320 void
2321 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2322 {
2323     /* put the holdings sent to us by the server on the board holdings area */
2324     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2325     char p;
2326     ChessSquare piece;
2327
2328     if(gameInfo.holdingsWidth < 2)  return;
2329     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2330         return; // prevent overwriting by pre-board holdings
2331
2332     if( (int)lowestPiece >= BlackPawn ) {
2333         holdingsColumn = 0;
2334         countsColumn = 1;
2335         holdingsStartRow = BOARD_HEIGHT-1;
2336         direction = -1;
2337     } else {
2338         holdingsColumn = BOARD_WIDTH-1;
2339         countsColumn = BOARD_WIDTH-2;
2340         holdingsStartRow = 0;
2341         direction = 1;
2342     }
2343
2344     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2345         board[i][holdingsColumn] = EmptySquare;
2346         board[i][countsColumn]   = (ChessSquare) 0;
2347     }
2348     while( (p=*holdings++) != NULLCHAR ) {
2349         piece = CharToPiece( ToUpper(p) );
2350         if(piece == EmptySquare) continue;
2351         /*j = (int) piece - (int) WhitePawn;*/
2352         j = PieceToNumber(piece);
2353         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2354         if(j < 0) continue;               /* should not happen */
2355         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2356         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2357         board[holdingsStartRow+j*direction][countsColumn]++;
2358     }
2359 }
2360
2361
2362 void
2363 VariantSwitch(Board board, VariantClass newVariant)
2364 {
2365    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2366    static Board oldBoard;
2367
2368    startedFromPositionFile = FALSE;
2369    if(gameInfo.variant == newVariant) return;
2370
2371    /* [HGM] This routine is called each time an assignment is made to
2372     * gameInfo.variant during a game, to make sure the board sizes
2373     * are set to match the new variant. If that means adding or deleting
2374     * holdings, we shift the playing board accordingly
2375     * This kludge is needed because in ICS observe mode, we get boards
2376     * of an ongoing game without knowing the variant, and learn about the
2377     * latter only later. This can be because of the move list we requested,
2378     * in which case the game history is refilled from the beginning anyway,
2379     * but also when receiving holdings of a crazyhouse game. In the latter
2380     * case we want to add those holdings to the already received position.
2381     */
2382
2383
2384    if (appData.debugMode) {
2385      fprintf(debugFP, "Switch board from %s to %s\n",
2386              VariantName(gameInfo.variant), VariantName(newVariant));
2387      setbuf(debugFP, NULL);
2388    }
2389    shuffleOpenings = 0;       /* [HGM] shuffle */
2390    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2391    switch(newVariant)
2392      {
2393      case VariantShogi:
2394        newWidth = 9;  newHeight = 9;
2395        gameInfo.holdingsSize = 7;
2396      case VariantBughouse:
2397      case VariantCrazyhouse:
2398        newHoldingsWidth = 2; break;
2399      case VariantGreat:
2400        newWidth = 10;
2401      case VariantSuper:
2402        newHoldingsWidth = 2;
2403        gameInfo.holdingsSize = 8;
2404        break;
2405      case VariantGothic:
2406      case VariantCapablanca:
2407      case VariantCapaRandom:
2408        newWidth = 10;
2409      default:
2410        newHoldingsWidth = gameInfo.holdingsSize = 0;
2411      };
2412
2413    if(newWidth  != gameInfo.boardWidth  ||
2414       newHeight != gameInfo.boardHeight ||
2415       newHoldingsWidth != gameInfo.holdingsWidth ) {
2416
2417      /* shift position to new playing area, if needed */
2418      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2419        for(i=0; i<BOARD_HEIGHT; i++)
2420          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2421            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2422              board[i][j];
2423        for(i=0; i<newHeight; i++) {
2424          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2425          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2426        }
2427      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2428        for(i=0; i<BOARD_HEIGHT; i++)
2429          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2430            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431              board[i][j];
2432      }
2433      gameInfo.boardWidth  = newWidth;
2434      gameInfo.boardHeight = newHeight;
2435      gameInfo.holdingsWidth = newHoldingsWidth;
2436      gameInfo.variant = newVariant;
2437      InitDrawingSizes(-2, 0);
2438    } else gameInfo.variant = newVariant;
2439    CopyBoard(oldBoard, board);   // remember correctly formatted board
2440      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2441    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2442 }
2443
2444 static int loggedOn = FALSE;
2445
2446 /*-- Game start info cache: --*/
2447 int gs_gamenum;
2448 char gs_kind[MSG_SIZ];
2449 static char player1Name[128] = "";
2450 static char player2Name[128] = "";
2451 static char cont_seq[] = "\n\\   ";
2452 static int player1Rating = -1;
2453 static int player2Rating = -1;
2454 /*----------------------------*/
2455
2456 ColorClass curColor = ColorNormal;
2457 int suppressKibitz = 0;
2458
2459 // [HGM] seekgraph
2460 Boolean soughtPending = FALSE;
2461 Boolean seekGraphUp;
2462 #define MAX_SEEK_ADS 200
2463 #define SQUARE 0x80
2464 char *seekAdList[MAX_SEEK_ADS];
2465 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2466 float tcList[MAX_SEEK_ADS];
2467 char colorList[MAX_SEEK_ADS];
2468 int nrOfSeekAds = 0;
2469 int minRating = 1010, maxRating = 2800;
2470 int hMargin = 10, vMargin = 20, h, w;
2471 extern int squareSize, lineGap;
2472
2473 void
2474 PlotSeekAd(int i)
2475 {
2476         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2477         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2478         if(r < minRating+100 && r >=0 ) r = minRating+100;
2479         if(r > maxRating) r = maxRating;
2480         if(tc < 1.) tc = 1.;
2481         if(tc > 95.) tc = 95.;
2482         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2483         y = ((double)r - minRating)/(maxRating - minRating)
2484             * (h-vMargin-squareSize/8-1) + vMargin;
2485         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2486         if(strstr(seekAdList[i], " u ")) color = 1;
2487         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2488            !strstr(seekAdList[i], "bullet") &&
2489            !strstr(seekAdList[i], "blitz") &&
2490            !strstr(seekAdList[i], "standard") ) color = 2;
2491         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2492         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2493 }
2494
2495 void
2496 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2497 {
2498         char buf[MSG_SIZ], *ext = "";
2499         VariantClass v = StringToVariant(type);
2500         if(strstr(type, "wild")) {
2501             ext = type + 4; // append wild number
2502             if(v == VariantFischeRandom) type = "chess960"; else
2503             if(v == VariantLoadable) type = "setup"; else
2504             type = VariantName(v);
2505         }
2506         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2507         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2508             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2509             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2510             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2511             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2512             seekNrList[nrOfSeekAds] = nr;
2513             zList[nrOfSeekAds] = 0;
2514             seekAdList[nrOfSeekAds++] = StrSave(buf);
2515             if(plot) PlotSeekAd(nrOfSeekAds-1);
2516         }
2517 }
2518
2519 void
2520 EraseSeekDot(int i)
2521 {
2522     int x = xList[i], y = yList[i], d=squareSize/4, k;
2523     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2524     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2525     // now replot every dot that overlapped
2526     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2527         int xx = xList[k], yy = yList[k];
2528         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2529             DrawSeekDot(xx, yy, colorList[k]);
2530     }
2531 }
2532
2533 void
2534 RemoveSeekAd(int nr)
2535 {
2536         int i;
2537         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2538             EraseSeekDot(i);
2539             if(seekAdList[i]) free(seekAdList[i]);
2540             seekAdList[i] = seekAdList[--nrOfSeekAds];
2541             seekNrList[i] = seekNrList[nrOfSeekAds];
2542             ratingList[i] = ratingList[nrOfSeekAds];
2543             colorList[i]  = colorList[nrOfSeekAds];
2544             tcList[i] = tcList[nrOfSeekAds];
2545             xList[i]  = xList[nrOfSeekAds];
2546             yList[i]  = yList[nrOfSeekAds];
2547             zList[i]  = zList[nrOfSeekAds];
2548             seekAdList[nrOfSeekAds] = NULL;
2549             break;
2550         }
2551 }
2552
2553 Boolean
2554 MatchSoughtLine(char *line)
2555 {
2556     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2557     int nr, base, inc, u=0; char dummy;
2558
2559     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2561        (u=1) &&
2562        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2563         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2564         // match: compact and save the line
2565         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2566         return TRUE;
2567     }
2568     return FALSE;
2569 }
2570
2571 int
2572 DrawSeekGraph()
2573 {
2574     int i;
2575     if(!seekGraphUp) return FALSE;
2576     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2577     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2578
2579     DrawSeekBackground(0, 0, w, h);
2580     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2581     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2582     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2583         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2584         yy = h-1-yy;
2585         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2586         if(i%500 == 0) {
2587             char buf[MSG_SIZ];
2588             snprintf(buf, MSG_SIZ, "%d", i);
2589             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2590         }
2591     }
2592     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2593     for(i=1; i<100; i+=(i<10?1:5)) {
2594         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2595         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2596         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2597             char buf[MSG_SIZ];
2598             snprintf(buf, MSG_SIZ, "%d", i);
2599             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2600         }
2601     }
2602     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2603     return TRUE;
2604 }
2605
2606 int SeekGraphClick(ClickType click, int x, int y, int moving)
2607 {
2608     static int lastDown = 0, displayed = 0, lastSecond;
2609     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2610         if(click == Release || moving) return FALSE;
2611         nrOfSeekAds = 0;
2612         soughtPending = TRUE;
2613         SendToICS(ics_prefix);
2614         SendToICS("sought\n"); // should this be "sought all"?
2615     } else { // issue challenge based on clicked ad
2616         int dist = 10000; int i, closest = 0, second = 0;
2617         for(i=0; i<nrOfSeekAds; i++) {
2618             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2619             if(d < dist) { dist = d; closest = i; }
2620             second += (d - zList[i] < 120); // count in-range ads
2621             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2622         }
2623         if(dist < 120) {
2624             char buf[MSG_SIZ];
2625             second = (second > 1);
2626             if(displayed != closest || second != lastSecond) {
2627                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2628                 lastSecond = second; displayed = closest;
2629             }
2630             if(click == Press) {
2631                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2632                 lastDown = closest;
2633                 return TRUE;
2634             } // on press 'hit', only show info
2635             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2636             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2637             SendToICS(ics_prefix);
2638             SendToICS(buf);
2639             return TRUE; // let incoming board of started game pop down the graph
2640         } else if(click == Release) { // release 'miss' is ignored
2641             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2642             if(moving == 2) { // right up-click
2643                 nrOfSeekAds = 0; // refresh graph
2644                 soughtPending = TRUE;
2645                 SendToICS(ics_prefix);
2646                 SendToICS("sought\n"); // should this be "sought all"?
2647             }
2648             return TRUE;
2649         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2650         // press miss or release hit 'pop down' seek graph
2651         seekGraphUp = FALSE;
2652         DrawPosition(TRUE, NULL);
2653     }
2654     return TRUE;
2655 }
2656
2657 void
2658 read_from_ics(isr, closure, data, count, error)
2659      InputSourceRef isr;
2660      VOIDSTAR closure;
2661      char *data;
2662      int count;
2663      int error;
2664 {
2665 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2666 #define STARTED_NONE 0
2667 #define STARTED_MOVES 1
2668 #define STARTED_BOARD 2
2669 #define STARTED_OBSERVE 3
2670 #define STARTED_HOLDINGS 4
2671 #define STARTED_CHATTER 5
2672 #define STARTED_COMMENT 6
2673 #define STARTED_MOVES_NOHIDE 7
2674
2675     static int started = STARTED_NONE;
2676     static char parse[20000];
2677     static int parse_pos = 0;
2678     static char buf[BUF_SIZE + 1];
2679     static int firstTime = TRUE, intfSet = FALSE;
2680     static ColorClass prevColor = ColorNormal;
2681     static int savingComment = FALSE;
2682     static int cmatch = 0; // continuation sequence match
2683     char *bp;
2684     char str[MSG_SIZ];
2685     int i, oldi;
2686     int buf_len;
2687     int next_out;
2688     int tkind;
2689     int backup;    /* [DM] For zippy color lines */
2690     char *p;
2691     char talker[MSG_SIZ]; // [HGM] chat
2692     int channel;
2693
2694     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2695
2696     if (appData.debugMode) {
2697       if (!error) {
2698         fprintf(debugFP, "<ICS: ");
2699         show_bytes(debugFP, data, count);
2700         fprintf(debugFP, "\n");
2701       }
2702     }
2703
2704     if (appData.debugMode) { int f = forwardMostMove;
2705         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2706                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2707                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2708     }
2709     if (count > 0) {
2710         /* If last read ended with a partial line that we couldn't parse,
2711            prepend it to the new read and try again. */
2712         if (leftover_len > 0) {
2713             for (i=0; i<leftover_len; i++)
2714               buf[i] = buf[leftover_start + i];
2715         }
2716
2717     /* copy new characters into the buffer */
2718     bp = buf + leftover_len;
2719     buf_len=leftover_len;
2720     for (i=0; i<count; i++)
2721     {
2722         // ignore these
2723         if (data[i] == '\r')
2724             continue;
2725
2726         // join lines split by ICS?
2727         if (!appData.noJoin)
2728         {
2729             /*
2730                 Joining just consists of finding matches against the
2731                 continuation sequence, and discarding that sequence
2732                 if found instead of copying it.  So, until a match
2733                 fails, there's nothing to do since it might be the
2734                 complete sequence, and thus, something we don't want
2735                 copied.
2736             */
2737             if (data[i] == cont_seq[cmatch])
2738             {
2739                 cmatch++;
2740                 if (cmatch == strlen(cont_seq))
2741                 {
2742                     cmatch = 0; // complete match.  just reset the counter
2743
2744                     /*
2745                         it's possible for the ICS to not include the space
2746                         at the end of the last word, making our [correct]
2747                         join operation fuse two separate words.  the server
2748                         does this when the space occurs at the width setting.
2749                     */
2750                     if (!buf_len || buf[buf_len-1] != ' ')
2751                     {
2752                         *bp++ = ' ';
2753                         buf_len++;
2754                     }
2755                 }
2756                 continue;
2757             }
2758             else if (cmatch)
2759             {
2760                 /*
2761                     match failed, so we have to copy what matched before
2762                     falling through and copying this character.  In reality,
2763                     this will only ever be just the newline character, but
2764                     it doesn't hurt to be precise.
2765                 */
2766                 strncpy(bp, cont_seq, cmatch);
2767                 bp += cmatch;
2768                 buf_len += cmatch;
2769                 cmatch = 0;
2770             }
2771         }
2772
2773         // copy this char
2774         *bp++ = data[i];
2775         buf_len++;
2776     }
2777
2778         buf[buf_len] = NULLCHAR;
2779 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2780         next_out = 0;
2781         leftover_start = 0;
2782
2783         i = 0;
2784         while (i < buf_len) {
2785             /* Deal with part of the TELNET option negotiation
2786                protocol.  We refuse to do anything beyond the
2787                defaults, except that we allow the WILL ECHO option,
2788                which ICS uses to turn off password echoing when we are
2789                directly connected to it.  We reject this option
2790                if localLineEditing mode is on (always on in xboard)
2791                and we are talking to port 23, which might be a real
2792                telnet server that will try to keep WILL ECHO on permanently.
2793              */
2794             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2795                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2796                 unsigned char option;
2797                 oldi = i;
2798                 switch ((unsigned char) buf[++i]) {
2799                   case TN_WILL:
2800                     if (appData.debugMode)
2801                       fprintf(debugFP, "\n<WILL ");
2802                     switch (option = (unsigned char) buf[++i]) {
2803                       case TN_ECHO:
2804                         if (appData.debugMode)
2805                           fprintf(debugFP, "ECHO ");
2806                         /* Reply only if this is a change, according
2807                            to the protocol rules. */
2808                         if (remoteEchoOption) break;
2809                         if (appData.localLineEditing &&
2810                             atoi(appData.icsPort) == TN_PORT) {
2811                             TelnetRequest(TN_DONT, TN_ECHO);
2812                         } else {
2813                             EchoOff();
2814                             TelnetRequest(TN_DO, TN_ECHO);
2815                             remoteEchoOption = TRUE;
2816                         }
2817                         break;
2818                       default:
2819                         if (appData.debugMode)
2820                           fprintf(debugFP, "%d ", option);
2821                         /* Whatever this is, we don't want it. */
2822                         TelnetRequest(TN_DONT, option);
2823                         break;
2824                     }
2825                     break;
2826                   case TN_WONT:
2827                     if (appData.debugMode)
2828                       fprintf(debugFP, "\n<WONT ");
2829                     switch (option = (unsigned char) buf[++i]) {
2830                       case TN_ECHO:
2831                         if (appData.debugMode)
2832                           fprintf(debugFP, "ECHO ");
2833                         /* Reply only if this is a change, according
2834                            to the protocol rules. */
2835                         if (!remoteEchoOption) break;
2836                         EchoOn();
2837                         TelnetRequest(TN_DONT, TN_ECHO);
2838                         remoteEchoOption = FALSE;
2839                         break;
2840                       default:
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", (unsigned char) option);
2843                         /* Whatever this is, it must already be turned
2844                            off, because we never agree to turn on
2845                            anything non-default, so according to the
2846                            protocol rules, we don't reply. */
2847                         break;
2848                     }
2849                     break;
2850                   case TN_DO:
2851                     if (appData.debugMode)
2852                       fprintf(debugFP, "\n<DO ");
2853                     switch (option = (unsigned char) buf[++i]) {
2854                       default:
2855                         /* Whatever this is, we refuse to do it. */
2856                         if (appData.debugMode)
2857                           fprintf(debugFP, "%d ", option);
2858                         TelnetRequest(TN_WONT, option);
2859                         break;
2860                     }
2861                     break;
2862                   case TN_DONT:
2863                     if (appData.debugMode)
2864                       fprintf(debugFP, "\n<DONT ");
2865                     switch (option = (unsigned char) buf[++i]) {
2866                       default:
2867                         if (appData.debugMode)
2868                           fprintf(debugFP, "%d ", option);
2869                         /* Whatever this is, we are already not doing
2870                            it, because we never agree to do anything
2871                            non-default, so according to the protocol
2872                            rules, we don't reply. */
2873                         break;
2874                     }
2875                     break;
2876                   case TN_IAC:
2877                     if (appData.debugMode)
2878                       fprintf(debugFP, "\n<IAC ");
2879                     /* Doubled IAC; pass it through */
2880                     i--;
2881                     break;
2882                   default:
2883                     if (appData.debugMode)
2884                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2885                     /* Drop all other telnet commands on the floor */
2886                     break;
2887                 }
2888                 if (oldi > next_out)
2889                   SendToPlayer(&buf[next_out], oldi - next_out);
2890                 if (++i > next_out)
2891                   next_out = i;
2892                 continue;
2893             }
2894
2895             /* OK, this at least will *usually* work */
2896             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2897                 loggedOn = TRUE;
2898             }
2899
2900             if (loggedOn && !intfSet) {
2901                 if (ics_type == ICS_ICC) {
2902                   snprintf(str, MSG_SIZ,
2903                           "/set-quietly interface %s\n/set-quietly style 12\n",
2904                           programVersion);
2905                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2906                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2907                 } else if (ics_type == ICS_CHESSNET) {
2908                   snprintf(str, MSG_SIZ, "/style 12\n");
2909                 } else {
2910                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2911                   strcat(str, programVersion);
2912                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2913                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2915 #ifdef WIN32
2916                   strcat(str, "$iset nohighlight 1\n");
2917 #endif
2918                   strcat(str, "$iset lock 1\n$style 12\n");
2919                 }
2920                 SendToICS(str);
2921                 NotifyFrontendLogin();
2922                 intfSet = TRUE;
2923             }
2924
2925             if (started == STARTED_COMMENT) {
2926                 /* Accumulate characters in comment */
2927                 parse[parse_pos++] = buf[i];
2928                 if (buf[i] == '\n') {
2929                     parse[parse_pos] = NULLCHAR;
2930                     if(chattingPartner>=0) {
2931                         char mess[MSG_SIZ];
2932                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2933                         OutputChatMessage(chattingPartner, mess);
2934                         chattingPartner = -1;
2935                         next_out = i+1; // [HGM] suppress printing in ICS window
2936                     } else
2937                     if(!suppressKibitz) // [HGM] kibitz
2938                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2939                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2940                         int nrDigit = 0, nrAlph = 0, j;
2941                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2942                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2943                         parse[parse_pos] = NULLCHAR;
2944                         // try to be smart: if it does not look like search info, it should go to
2945                         // ICS interaction window after all, not to engine-output window.
2946                         for(j=0; j<parse_pos; j++) { // count letters and digits
2947                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2948                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2949                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2950                         }
2951                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2952                             int depth=0; float score;
2953                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2954                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2955                                 pvInfoList[forwardMostMove-1].depth = depth;
2956                                 pvInfoList[forwardMostMove-1].score = 100*score;
2957                             }
2958                             OutputKibitz(suppressKibitz, parse);
2959                         } else {
2960                             char tmp[MSG_SIZ];
2961                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2962                             SendToPlayer(tmp, strlen(tmp));
2963                         }
2964                         next_out = i+1; // [HGM] suppress printing in ICS window
2965                     }
2966                     started = STARTED_NONE;
2967                 } else {
2968                     /* Don't match patterns against characters in comment */
2969                     i++;
2970                     continue;
2971                 }
2972             }
2973             if (started == STARTED_CHATTER) {
2974                 if (buf[i] != '\n') {
2975                     /* Don't match patterns against characters in chatter */
2976                     i++;
2977                     continue;
2978                 }
2979                 started = STARTED_NONE;
2980                 if(suppressKibitz) next_out = i+1;
2981             }
2982
2983             /* Kludge to deal with rcmd protocol */
2984             if (firstTime && looking_at(buf, &i, "\001*")) {
2985                 DisplayFatalError(&buf[1], 0, 1);
2986                 continue;
2987             } else {
2988                 firstTime = FALSE;
2989             }
2990
2991             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2992                 ics_type = ICS_ICC;
2993                 ics_prefix = "/";
2994                 if (appData.debugMode)
2995                   fprintf(debugFP, "ics_type %d\n", ics_type);
2996                 continue;
2997             }
2998             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2999                 ics_type = ICS_FICS;
3000                 ics_prefix = "$";
3001                 if (appData.debugMode)
3002                   fprintf(debugFP, "ics_type %d\n", ics_type);
3003                 continue;
3004             }
3005             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3006                 ics_type = ICS_CHESSNET;
3007                 ics_prefix = "/";
3008                 if (appData.debugMode)
3009                   fprintf(debugFP, "ics_type %d\n", ics_type);
3010                 continue;
3011             }
3012
3013             if (!loggedOn &&
3014                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3015                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3016                  looking_at(buf, &i, "will be \"*\""))) {
3017               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3018               continue;
3019             }
3020
3021             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3022               char buf[MSG_SIZ];
3023               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3024               DisplayIcsInteractionTitle(buf);
3025               have_set_title = TRUE;
3026             }
3027
3028             /* skip finger notes */
3029             if (started == STARTED_NONE &&
3030                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3031                  (buf[i] == '1' && buf[i+1] == '0')) &&
3032                 buf[i+2] == ':' && buf[i+3] == ' ') {
3033               started = STARTED_CHATTER;
3034               i += 3;
3035               continue;
3036             }
3037
3038             oldi = i;
3039             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3040             if(appData.seekGraph) {
3041                 if(soughtPending && MatchSoughtLine(buf+i)) {
3042                     i = strstr(buf+i, "rated") - buf;
3043                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3044                     next_out = leftover_start = i;
3045                     started = STARTED_CHATTER;
3046                     suppressKibitz = TRUE;
3047                     continue;
3048                 }
3049                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3050                         && looking_at(buf, &i, "* ads displayed")) {
3051                     soughtPending = FALSE;
3052                     seekGraphUp = TRUE;
3053                     DrawSeekGraph();
3054                     continue;
3055                 }
3056                 if(appData.autoRefresh) {
3057                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3058                         int s = (ics_type == ICS_ICC); // ICC format differs
3059                         if(seekGraphUp)
3060                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3061                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3062                         looking_at(buf, &i, "*% "); // eat prompt
3063                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3064                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065                         next_out = i; // suppress
3066                         continue;
3067                     }
3068                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3069                         char *p = star_match[0];
3070                         while(*p) {
3071                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3072                             while(*p && *p++ != ' '); // next
3073                         }
3074                         looking_at(buf, &i, "*% "); // eat prompt
3075                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076                         next_out = i;
3077                         continue;
3078                     }
3079                 }
3080             }
3081
3082             /* skip formula vars */
3083             if (started == STARTED_NONE &&
3084                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3085               started = STARTED_CHATTER;
3086               i += 3;
3087               continue;
3088             }
3089
3090             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3091             if (appData.autoKibitz && started == STARTED_NONE &&
3092                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3093                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3094                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3095                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3096                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3097                         suppressKibitz = TRUE;
3098                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099                         next_out = i;
3100                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3101                                 && (gameMode == IcsPlayingWhite)) ||
3102                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3103                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3104                             started = STARTED_CHATTER; // own kibitz we simply discard
3105                         else {
3106                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3107                             parse_pos = 0; parse[0] = NULLCHAR;
3108                             savingComment = TRUE;
3109                             suppressKibitz = gameMode != IcsObserving ? 2 :
3110                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3111                         }
3112                         continue;
3113                 } else
3114                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3115                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3116                          && atoi(star_match[0])) {
3117                     // suppress the acknowledgements of our own autoKibitz
3118                     char *p;
3119                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3121                     SendToPlayer(star_match[0], strlen(star_match[0]));
3122                     if(looking_at(buf, &i, "*% ")) // eat prompt
3123                         suppressKibitz = FALSE;
3124                     next_out = i;
3125                     continue;
3126                 }
3127             } // [HGM] kibitz: end of patch
3128
3129             // [HGM] chat: intercept tells by users for which we have an open chat window
3130             channel = -1;
3131             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3132                                            looking_at(buf, &i, "* whispers:") ||
3133                                            looking_at(buf, &i, "* kibitzes:") ||
3134                                            looking_at(buf, &i, "* shouts:") ||
3135                                            looking_at(buf, &i, "* c-shouts:") ||
3136                                            looking_at(buf, &i, "--> * ") ||
3137                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3138                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3139                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3140                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3141                 int p;
3142                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3143                 chattingPartner = -1;
3144
3145                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3146                 for(p=0; p<MAX_CHAT; p++) {
3147                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3148                     talker[0] = '['; strcat(talker, "] ");
3149                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3150                     chattingPartner = p; break;
3151                     }
3152                 } else
3153                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3154                 for(p=0; p<MAX_CHAT; p++) {
3155                     if(!strcmp("kibitzes", chatPartner[p])) {
3156                         talker[0] = '['; strcat(talker, "] ");
3157                         chattingPartner = p; break;
3158                     }
3159                 } else
3160                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3161                 for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("whispers", chatPartner[p])) {
3163                         talker[0] = '['; strcat(talker, "] ");
3164                         chattingPartner = p; break;
3165                     }
3166                 } else
3167                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3168                   if(buf[i-8] == '-' && buf[i-3] == 't')
3169                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3170                     if(!strcmp("c-shouts", chatPartner[p])) {
3171                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3172                         chattingPartner = p; break;
3173                     }
3174                   }
3175                   if(chattingPartner < 0)
3176                   for(p=0; p<MAX_CHAT; p++) {
3177                     if(!strcmp("shouts", chatPartner[p])) {
3178                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3179                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3180                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3181                         chattingPartner = p; break;
3182                     }
3183                   }
3184                 }
3185                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3186                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3187                     talker[0] = 0; Colorize(ColorTell, FALSE);
3188                     chattingPartner = p; break;
3189                 }
3190                 if(chattingPartner<0) i = oldi; else {
3191                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3192                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3193                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3194                     started = STARTED_COMMENT;
3195                     parse_pos = 0; parse[0] = NULLCHAR;
3196                     savingComment = 3 + chattingPartner; // counts as TRUE
3197                     suppressKibitz = TRUE;
3198                     continue;
3199                 }
3200             } // [HGM] chat: end of patch
3201
3202           backup = i;
3203             if (appData.zippyTalk || appData.zippyPlay) {
3204                 /* [DM] Backup address for color zippy lines */
3205 #if ZIPPY
3206                if (loggedOn == TRUE)
3207                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3208                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3209 #endif
3210             } // [DM] 'else { ' deleted
3211                 if (
3212                     /* Regular tells and says */
3213                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3214                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3215                     looking_at(buf, &i, "* says: ") ||
3216                     /* Don't color "message" or "messages" output */
3217                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3218                     looking_at(buf, &i, "*. * at *:*: ") ||
3219                     looking_at(buf, &i, "--* (*:*): ") ||
3220                     /* Message notifications (same color as tells) */
3221                     looking_at(buf, &i, "* has left a message ") ||
3222                     looking_at(buf, &i, "* just sent you a message:\n") ||
3223                     /* Whispers and kibitzes */
3224                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3225                     looking_at(buf, &i, "* kibitzes: ") ||
3226                     /* Channel tells */
3227                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3228
3229                   if (tkind == 1 && strchr(star_match[0], ':')) {
3230                       /* Avoid "tells you:" spoofs in channels */
3231                      tkind = 3;
3232                   }
3233                   if (star_match[0][0] == NULLCHAR ||
3234                       strchr(star_match[0], ' ') ||
3235                       (tkind == 3 && strchr(star_match[1], ' '))) {
3236                     /* Reject bogus matches */
3237                     i = oldi;
3238                   } else {
3239                     if (appData.colorize) {
3240                       if (oldi > next_out) {
3241                         SendToPlayer(&buf[next_out], oldi - next_out);
3242                         next_out = oldi;
3243                       }
3244                       switch (tkind) {
3245                       case 1:
3246                         Colorize(ColorTell, FALSE);
3247                         curColor = ColorTell;
3248                         break;
3249                       case 2:
3250                         Colorize(ColorKibitz, FALSE);
3251                         curColor = ColorKibitz;
3252                         break;
3253                       case 3:
3254                         p = strrchr(star_match[1], '(');
3255                         if (p == NULL) {
3256                           p = star_match[1];
3257                         } else {
3258                           p++;
3259                         }
3260                         if (atoi(p) == 1) {
3261                           Colorize(ColorChannel1, FALSE);
3262                           curColor = ColorChannel1;
3263                         } else {
3264                           Colorize(ColorChannel, FALSE);
3265                           curColor = ColorChannel;
3266                         }
3267                         break;
3268                       case 5:
3269                         curColor = ColorNormal;
3270                         break;
3271                       }
3272                     }
3273                     if (started == STARTED_NONE && appData.autoComment &&
3274                         (gameMode == IcsObserving ||
3275                          gameMode == IcsPlayingWhite ||
3276                          gameMode == IcsPlayingBlack)) {
3277                       parse_pos = i - oldi;
3278                       memcpy(parse, &buf[oldi], parse_pos);
3279                       parse[parse_pos] = NULLCHAR;
3280                       started = STARTED_COMMENT;
3281                       savingComment = TRUE;
3282                     } else {
3283                       started = STARTED_CHATTER;
3284                       savingComment = FALSE;
3285                     }
3286                     loggedOn = TRUE;
3287                     continue;
3288                   }
3289                 }
3290
3291                 if (looking_at(buf, &i, "* s-shouts: ") ||
3292                     looking_at(buf, &i, "* c-shouts: ")) {
3293                     if (appData.colorize) {
3294                         if (oldi > next_out) {
3295                             SendToPlayer(&buf[next_out], oldi - next_out);
3296                             next_out = oldi;
3297                         }
3298                         Colorize(ColorSShout, FALSE);
3299                         curColor = ColorSShout;
3300                     }
3301                     loggedOn = TRUE;
3302                     started = STARTED_CHATTER;
3303                     continue;
3304                 }
3305
3306                 if (looking_at(buf, &i, "--->")) {
3307                     loggedOn = TRUE;
3308                     continue;
3309                 }
3310
3311                 if (looking_at(buf, &i, "* shouts: ") ||
3312                     looking_at(buf, &i, "--> ")) {
3313                     if (appData.colorize) {
3314                         if (oldi > next_out) {
3315                             SendToPlayer(&buf[next_out], oldi - next_out);
3316                             next_out = oldi;
3317                         }
3318                         Colorize(ColorShout, FALSE);
3319                         curColor = ColorShout;
3320                     }
3321                     loggedOn = TRUE;
3322                     started = STARTED_CHATTER;
3323                     continue;
3324                 }
3325
3326                 if (looking_at( buf, &i, "Challenge:")) {
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorChallenge, FALSE);
3333                         curColor = ColorChallenge;
3334                     }
3335                     loggedOn = TRUE;
3336                     continue;
3337                 }
3338
3339                 if (looking_at(buf, &i, "* offers you") ||
3340                     looking_at(buf, &i, "* offers to be") ||
3341                     looking_at(buf, &i, "* would like to") ||
3342                     looking_at(buf, &i, "* requests to") ||
3343                     looking_at(buf, &i, "Your opponent offers") ||
3344                     looking_at(buf, &i, "Your opponent requests")) {
3345
3346                     if (appData.colorize) {
3347                         if (oldi > next_out) {
3348                             SendToPlayer(&buf[next_out], oldi - next_out);
3349                             next_out = oldi;
3350                         }
3351                         Colorize(ColorRequest, FALSE);
3352                         curColor = ColorRequest;
3353                     }
3354                     continue;
3355                 }
3356
3357                 if (looking_at(buf, &i, "* (*) seeking")) {
3358                     if (appData.colorize) {
3359                         if (oldi > next_out) {
3360                             SendToPlayer(&buf[next_out], oldi - next_out);
3361                             next_out = oldi;
3362                         }
3363                         Colorize(ColorSeek, FALSE);
3364                         curColor = ColorSeek;
3365                     }
3366                     continue;
3367             }
3368
3369           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3370
3371             if (looking_at(buf, &i, "\\   ")) {
3372                 if (prevColor != ColorNormal) {
3373                     if (oldi > next_out) {
3374                         SendToPlayer(&buf[next_out], oldi - next_out);
3375                         next_out = oldi;
3376                     }
3377                     Colorize(prevColor, TRUE);
3378                     curColor = prevColor;
3379                 }
3380                 if (savingComment) {
3381                     parse_pos = i - oldi;
3382                     memcpy(parse, &buf[oldi], parse_pos);
3383                     parse[parse_pos] = NULLCHAR;
3384                     started = STARTED_COMMENT;
3385                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3386                         chattingPartner = savingComment - 3; // kludge to remember the box
3387                 } else {
3388                     started = STARTED_CHATTER;
3389                 }
3390                 continue;
3391             }
3392
3393             if (looking_at(buf, &i, "Black Strength :") ||
3394                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3395                 looking_at(buf, &i, "<10>") ||
3396                 looking_at(buf, &i, "#@#")) {
3397                 /* Wrong board style */
3398                 loggedOn = TRUE;
3399                 SendToICS(ics_prefix);
3400                 SendToICS("set style 12\n");
3401                 SendToICS(ics_prefix);
3402                 SendToICS("refresh\n");
3403                 continue;
3404             }
3405
3406             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3407                 ICSInitScript();
3408                 have_sent_ICS_logon = 1;
3409                 continue;
3410             }
3411
3412             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3413                 (looking_at(buf, &i, "\n<12> ") ||
3414                  looking_at(buf, &i, "<12> "))) {
3415                 loggedOn = TRUE;
3416                 if (oldi > next_out) {
3417                     SendToPlayer(&buf[next_out], oldi - next_out);
3418                 }
3419                 next_out = i;
3420                 started = STARTED_BOARD;
3421                 parse_pos = 0;
3422                 continue;
3423             }
3424
3425             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3426                 looking_at(buf, &i, "<b1> ")) {
3427                 if (oldi > next_out) {
3428                     SendToPlayer(&buf[next_out], oldi - next_out);
3429                 }
3430                 next_out = i;
3431                 started = STARTED_HOLDINGS;
3432                 parse_pos = 0;
3433                 continue;
3434             }
3435
3436             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3437                 loggedOn = TRUE;
3438                 /* Header for a move list -- first line */
3439
3440                 switch (ics_getting_history) {
3441                   case H_FALSE:
3442                     switch (gameMode) {
3443                       case IcsIdle:
3444                       case BeginningOfGame:
3445                         /* User typed "moves" or "oldmoves" while we
3446                            were idle.  Pretend we asked for these
3447                            moves and soak them up so user can step
3448                            through them and/or save them.
3449                            */
3450                         Reset(FALSE, TRUE);
3451                         gameMode = IcsObserving;
3452                         ModeHighlight();
3453                         ics_gamenum = -1;
3454                         ics_getting_history = H_GOT_UNREQ_HEADER;
3455                         break;
3456                       case EditGame: /*?*/
3457                       case EditPosition: /*?*/
3458                         /* Should above feature work in these modes too? */
3459                         /* For now it doesn't */
3460                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3461                         break;
3462                       default:
3463                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3464                         break;
3465                     }
3466                     break;
3467                   case H_REQUESTED:
3468                     /* Is this the right one? */
3469                     if (gameInfo.white && gameInfo.black &&
3470                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3471                         strcmp(gameInfo.black, star_match[2]) == 0) {
3472                         /* All is well */
3473                         ics_getting_history = H_GOT_REQ_HEADER;
3474                     }
3475                     break;
3476                   case H_GOT_REQ_HEADER:
3477                   case H_GOT_UNREQ_HEADER:
3478                   case H_GOT_UNWANTED_HEADER:
3479                   case H_GETTING_MOVES:
3480                     /* Should not happen */
3481                     DisplayError(_("Error gathering move list: two headers"), 0);
3482                     ics_getting_history = H_FALSE;
3483                     break;
3484                 }
3485
3486                 /* Save player ratings into gameInfo if needed */
3487                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3488                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3489                     (gameInfo.whiteRating == -1 ||
3490                      gameInfo.blackRating == -1)) {
3491
3492                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3493                     gameInfo.blackRating = string_to_rating(star_match[3]);
3494                     if (appData.debugMode)
3495                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3496                               gameInfo.whiteRating, gameInfo.blackRating);
3497                 }
3498                 continue;
3499             }
3500
3501             if (looking_at(buf, &i,
3502               "* * match, initial time: * minute*, increment: * second")) {
3503                 /* Header for a move list -- second line */
3504                 /* Initial board will follow if this is a wild game */
3505                 if (gameInfo.event != NULL) free(gameInfo.event);
3506                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3507                 gameInfo.event = StrSave(str);
3508                 /* [HGM] we switched variant. Translate boards if needed. */
3509                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3510                 continue;
3511             }
3512
3513             if (looking_at(buf, &i, "Move  ")) {
3514                 /* Beginning of a move list */
3515                 switch (ics_getting_history) {
3516                   case H_FALSE:
3517                     /* Normally should not happen */
3518                     /* Maybe user hit reset while we were parsing */
3519                     break;
3520                   case H_REQUESTED:
3521                     /* Happens if we are ignoring a move list that is not
3522                      * the one we just requested.  Common if the user
3523                      * tries to observe two games without turning off
3524                      * getMoveList */
3525                     break;
3526                   case H_GETTING_MOVES:
3527                     /* Should not happen */
3528                     DisplayError(_("Error gathering move list: nested"), 0);
3529                     ics_getting_history = H_FALSE;
3530                     break;
3531                   case H_GOT_REQ_HEADER:
3532                     ics_getting_history = H_GETTING_MOVES;
3533                     started = STARTED_MOVES;
3534                     parse_pos = 0;
3535                     if (oldi > next_out) {
3536                         SendToPlayer(&buf[next_out], oldi - next_out);
3537                     }
3538                     break;
3539                   case H_GOT_UNREQ_HEADER:
3540                     ics_getting_history = H_GETTING_MOVES;
3541                     started = STARTED_MOVES_NOHIDE;
3542                     parse_pos = 0;
3543                     break;
3544                   case H_GOT_UNWANTED_HEADER:
3545                     ics_getting_history = H_FALSE;
3546                     break;
3547                 }
3548                 continue;
3549             }
3550
3551             if (looking_at(buf, &i, "% ") ||
3552                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3553                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3554                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3555                     soughtPending = FALSE;
3556                     seekGraphUp = TRUE;
3557                     DrawSeekGraph();
3558                 }
3559                 if(suppressKibitz) next_out = i;
3560                 savingComment = FALSE;
3561                 suppressKibitz = 0;
3562                 switch (started) {
3563                   case STARTED_MOVES:
3564                   case STARTED_MOVES_NOHIDE:
3565                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3566                     parse[parse_pos + i - oldi] = NULLCHAR;
3567                     ParseGameHistory(parse);
3568 #if ZIPPY
3569                     if (appData.zippyPlay && first.initDone) {
3570                         FeedMovesToProgram(&first, forwardMostMove);
3571                         if (gameMode == IcsPlayingWhite) {
3572                             if (WhiteOnMove(forwardMostMove)) {
3573                                 if (first.sendTime) {
3574                                   if (first.useColors) {
3575                                     SendToProgram("black\n", &first);
3576                                   }
3577                                   SendTimeRemaining(&first, TRUE);
3578                                 }
3579                                 if (first.useColors) {
3580                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3581                                 }
3582                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3583                                 first.maybeThinking = TRUE;
3584                             } else {
3585                                 if (first.usePlayother) {
3586                                   if (first.sendTime) {
3587                                     SendTimeRemaining(&first, TRUE);
3588                                   }
3589                                   SendToProgram("playother\n", &first);
3590                                   firstMove = FALSE;
3591                                 } else {
3592                                   firstMove = TRUE;
3593                                 }
3594                             }
3595                         } else if (gameMode == IcsPlayingBlack) {
3596                             if (!WhiteOnMove(forwardMostMove)) {
3597                                 if (first.sendTime) {
3598                                   if (first.useColors) {
3599                                     SendToProgram("white\n", &first);
3600                                   }
3601                                   SendTimeRemaining(&first, FALSE);
3602                                 }
3603                                 if (first.useColors) {
3604                                   SendToProgram("black\n", &first);
3605                                 }
3606                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3607                                 first.maybeThinking = TRUE;
3608                             } else {
3609                                 if (first.usePlayother) {
3610                                   if (first.sendTime) {
3611                                     SendTimeRemaining(&first, FALSE);
3612                                   }
3613                                   SendToProgram("playother\n", &first);
3614                                   firstMove = FALSE;
3615                                 } else {
3616                                   firstMove = TRUE;
3617                                 }
3618                             }
3619                         }
3620                     }
3621 #endif
3622                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3623                         /* Moves came from oldmoves or moves command
3624                            while we weren't doing anything else.
3625                            */
3626                         currentMove = forwardMostMove;
3627                         ClearHighlights();/*!!could figure this out*/
3628                         flipView = appData.flipView;
3629                         DrawPosition(TRUE, boards[currentMove]);
3630                         DisplayBothClocks();
3631                         snprintf(str, MSG_SIZ, _("%s vs. %s"),
3632                                 gameInfo.white, gameInfo.black);
3633                         DisplayTitle(str);
3634                         gameMode = IcsIdle;
3635                     } else {
3636                         /* Moves were history of an active game */
3637                         if (gameInfo.resultDetails != NULL) {
3638                             free(gameInfo.resultDetails);
3639                             gameInfo.resultDetails = NULL;
3640                         }
3641                     }
3642                     HistorySet(parseList, backwardMostMove,
3643                                forwardMostMove, currentMove-1);
3644                     DisplayMove(currentMove - 1);
3645                     if (started == STARTED_MOVES) next_out = i;
3646                     started = STARTED_NONE;
3647                     ics_getting_history = H_FALSE;
3648                     break;
3649
3650                   case STARTED_OBSERVE:
3651                     started = STARTED_NONE;
3652                     SendToICS(ics_prefix);
3653                     SendToICS("refresh\n");
3654                     break;
3655
3656                   default:
3657                     break;
3658                 }
3659                 if(bookHit) { // [HGM] book: simulate book reply
3660                     static char bookMove[MSG_SIZ]; // a bit generous?
3661
3662                     programStats.nodes = programStats.depth = programStats.time =
3663                     programStats.score = programStats.got_only_move = 0;
3664                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3665
3666                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3667                     strcat(bookMove, bookHit);
3668                     HandleMachineMove(bookMove, &first);
3669                 }
3670                 continue;
3671             }
3672
3673             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3674                  started == STARTED_HOLDINGS ||
3675                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3676                 /* Accumulate characters in move list or board */
3677                 parse[parse_pos++] = buf[i];
3678             }
3679
3680             /* Start of game messages.  Mostly we detect start of game
3681                when the first board image arrives.  On some versions
3682                of the ICS, though, we need to do a "refresh" after starting
3683                to observe in order to get the current board right away. */
3684             if (looking_at(buf, &i, "Adding game * to observation list")) {
3685                 started = STARTED_OBSERVE;
3686                 continue;
3687             }
3688
3689             /* Handle auto-observe */
3690             if (appData.autoObserve &&
3691                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3692                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3693                 char *player;
3694                 /* Choose the player that was highlighted, if any. */
3695                 if (star_match[0][0] == '\033' ||
3696                     star_match[1][0] != '\033') {
3697                     player = star_match[0];
3698                 } else {
3699                     player = star_match[2];
3700                 }
3701                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3702                         ics_prefix, StripHighlightAndTitle(player));
3703                 SendToICS(str);
3704
3705                 /* Save ratings from notify string */
3706                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3707                 player1Rating = string_to_rating(star_match[1]);
3708                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3709                 player2Rating = string_to_rating(star_match[3]);
3710
3711                 if (appData.debugMode)
3712                   fprintf(debugFP,
3713                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3714                           player1Name, player1Rating,
3715                           player2Name, player2Rating);
3716
3717                 continue;
3718             }
3719
3720             /* Deal with automatic examine mode after a game,
3721                and with IcsObserving -> IcsExamining transition */
3722             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3723                 looking_at(buf, &i, "has made you an examiner of game *")) {
3724
3725                 int gamenum = atoi(star_match[0]);
3726                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3727                     gamenum == ics_gamenum) {
3728                     /* We were already playing or observing this game;
3729                        no need to refetch history */
3730                     gameMode = IcsExamining;
3731                     if (pausing) {
3732                         pauseExamForwardMostMove = forwardMostMove;
3733                     } else if (currentMove < forwardMostMove) {
3734                         ForwardInner(forwardMostMove);
3735                     }
3736                 } else {
3737                     /* I don't think this case really can happen */
3738                     SendToICS(ics_prefix);
3739                     SendToICS("refresh\n");
3740                 }
3741                 continue;
3742             }
3743
3744             /* Error messages */
3745 //          if (ics_user_moved) {
3746             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3747                 if (looking_at(buf, &i, "Illegal move") ||
3748                     looking_at(buf, &i, "Not a legal move") ||
3749                     looking_at(buf, &i, "Your king is in check") ||
3750                     looking_at(buf, &i, "It isn't your turn") ||
3751                     looking_at(buf, &i, "It is not your move")) {
3752                     /* Illegal move */
3753                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3754                         currentMove = forwardMostMove-1;
3755                         DisplayMove(currentMove - 1); /* before DMError */
3756                         DrawPosition(FALSE, boards[currentMove]);
3757                         SwitchClocks(forwardMostMove-1); // [HGM] race
3758                         DisplayBothClocks();
3759                     }
3760                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3761                     ics_user_moved = 0;
3762                     continue;
3763                 }
3764             }
3765
3766             if (looking_at(buf, &i, "still have time") ||
3767                 looking_at(buf, &i, "not out of time") ||
3768                 looking_at(buf, &i, "either player is out of time") ||
3769                 looking_at(buf, &i, "has timeseal; checking")) {
3770                 /* We must have called his flag a little too soon */
3771                 whiteFlag = blackFlag = FALSE;
3772                 continue;
3773             }
3774
3775             if (looking_at(buf, &i, "added * seconds to") ||
3776                 looking_at(buf, &i, "seconds were added to")) {
3777                 /* Update the clocks */
3778                 SendToICS(ics_prefix);
3779                 SendToICS("refresh\n");
3780                 continue;
3781             }
3782
3783             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3784                 ics_clock_paused = TRUE;
3785                 StopClocks();
3786                 continue;
3787             }
3788
3789             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3790                 ics_clock_paused = FALSE;
3791                 StartClocks();
3792                 continue;
3793             }
3794
3795             /* Grab player ratings from the Creating: message.
3796                Note we have to check for the special case when
3797                the ICS inserts things like [white] or [black]. */
3798             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3799                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3800                 /* star_matches:
3801                    0    player 1 name (not necessarily white)
3802                    1    player 1 rating
3803                    2    empty, white, or black (IGNORED)
3804                    3    player 2 name (not necessarily black)
3805                    4    player 2 rating
3806
3807                    The names/ratings are sorted out when the game
3808                    actually starts (below).
3809                 */
3810                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3811                 player1Rating = string_to_rating(star_match[1]);
3812                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3813                 player2Rating = string_to_rating(star_match[4]);
3814
3815                 if (appData.debugMode)
3816                   fprintf(debugFP,
3817                           "Ratings from 'Creating:' %s %d, %s %d\n",
3818                           player1Name, player1Rating,
3819                           player2Name, player2Rating);
3820
3821                 continue;
3822             }
3823
3824             /* Improved generic start/end-of-game messages */
3825             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3826                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3827                 /* If tkind == 0: */
3828                 /* star_match[0] is the game number */
3829                 /*           [1] is the white player's name */
3830                 /*           [2] is the black player's name */
3831                 /* For end-of-game: */
3832                 /*           [3] is the reason for the game end */
3833                 /*           [4] is a PGN end game-token, preceded by " " */
3834                 /* For start-of-game: */
3835                 /*           [3] begins with "Creating" or "Continuing" */
3836                 /*           [4] is " *" or empty (don't care). */
3837                 int gamenum = atoi(star_match[0]);
3838                 char *whitename, *blackname, *why, *endtoken;
3839                 ChessMove endtype = EndOfFile;
3840
3841                 if (tkind == 0) {
3842                   whitename = star_match[1];
3843                   blackname = star_match[2];
3844                   why = star_match[3];
3845                   endtoken = star_match[4];
3846                 } else {
3847                   whitename = star_match[1];
3848                   blackname = star_match[3];
3849                   why = star_match[5];
3850                   endtoken = star_match[6];
3851                 }
3852
3853                 /* Game start messages */
3854                 if (strncmp(why, "Creating ", 9) == 0 ||
3855                     strncmp(why, "Continuing ", 11) == 0) {
3856                     gs_gamenum = gamenum;
3857                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3858                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3859                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3860 #if ZIPPY
3861                     if (appData.zippyPlay) {
3862                         ZippyGameStart(whitename, blackname);
3863                     }
3864 #endif /*ZIPPY*/
3865                     partnerBoardValid = FALSE; // [HGM] bughouse
3866                     continue;
3867                 }
3868
3869                 /* Game end messages */
3870                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3871                     ics_gamenum != gamenum) {
3872                     continue;
3873                 }
3874                 while (endtoken[0] == ' ') endtoken++;
3875                 switch (endtoken[0]) {
3876                   case '*':
3877                   default:
3878                     endtype = GameUnfinished;
3879                     break;
3880                   case '0':
3881                     endtype = BlackWins;
3882                     break;
3883                   case '1':
3884                     if (endtoken[1] == '/')
3885                       endtype = GameIsDrawn;
3886                     else
3887                       endtype = WhiteWins;
3888                     break;
3889                 }
3890                 GameEnds(endtype, why, GE_ICS);
3891 #if ZIPPY
3892                 if (appData.zippyPlay && first.initDone) {
3893                     ZippyGameEnd(endtype, why);
3894                     if (first.pr == NoProc) {
3895                       /* Start the next process early so that we'll
3896                          be ready for the next challenge */
3897                       StartChessProgram(&first);
3898                     }
3899                     /* Send "new" early, in case this command takes
3900                        a long time to finish, so that we'll be ready
3901                        for the next challenge. */
3902                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3903                     Reset(TRUE, TRUE);
3904                 }
3905 #endif /*ZIPPY*/
3906                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3907                 continue;
3908             }
3909
3910             if (looking_at(buf, &i, "Removing game * from observation") ||
3911                 looking_at(buf, &i, "no longer observing game *") ||
3912                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3913                 if (gameMode == IcsObserving &&
3914                     atoi(star_match[0]) == ics_gamenum)
3915                   {
3916                       /* icsEngineAnalyze */
3917                       if (appData.icsEngineAnalyze) {
3918                             ExitAnalyzeMode();
3919                             ModeHighlight();
3920                       }
3921                       StopClocks();
3922                       gameMode = IcsIdle;
3923                       ics_gamenum = -1;
3924                       ics_user_moved = FALSE;
3925                   }
3926                 continue;
3927             }
3928
3929             if (looking_at(buf, &i, "no longer examining game *")) {
3930                 if (gameMode == IcsExamining &&
3931                     atoi(star_match[0]) == ics_gamenum)
3932                   {
3933                       gameMode = IcsIdle;
3934                       ics_gamenum = -1;
3935                       ics_user_moved = FALSE;
3936                   }
3937                 continue;
3938             }
3939
3940             /* Advance leftover_start past any newlines we find,
3941                so only partial lines can get reparsed */
3942             if (looking_at(buf, &i, "\n")) {
3943                 prevColor = curColor;
3944                 if (curColor != ColorNormal) {
3945                     if (oldi > next_out) {
3946                         SendToPlayer(&buf[next_out], oldi - next_out);
3947                         next_out = oldi;
3948                     }
3949                     Colorize(ColorNormal, FALSE);
3950                     curColor = ColorNormal;
3951                 }
3952                 if (started == STARTED_BOARD) {
3953                     started = STARTED_NONE;
3954                     parse[parse_pos] = NULLCHAR;
3955                     ParseBoard12(parse);
3956                     ics_user_moved = 0;
3957
3958                     /* Send premove here */
3959                     if (appData.premove) {
3960                       char str[MSG_SIZ];
3961                       if (currentMove == 0 &&
3962                           gameMode == IcsPlayingWhite &&
3963                           appData.premoveWhite) {
3964                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3965                         if (appData.debugMode)
3966                           fprintf(debugFP, "Sending premove:\n");
3967                         SendToICS(str);
3968                       } else if (currentMove == 1 &&
3969                                  gameMode == IcsPlayingBlack &&
3970                                  appData.premoveBlack) {
3971                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3972                         if (appData.debugMode)
3973                           fprintf(debugFP, "Sending premove:\n");
3974                         SendToICS(str);
3975                       } else if (gotPremove) {
3976                         gotPremove = 0;
3977                         ClearPremoveHighlights();
3978                         if (appData.debugMode)
3979                           fprintf(debugFP, "Sending premove:\n");
3980                           UserMoveEvent(premoveFromX, premoveFromY,
3981                                         premoveToX, premoveToY,
3982                                         premovePromoChar);
3983                       }
3984                     }
3985
3986                     /* Usually suppress following prompt */
3987                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3988                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3989                         if (looking_at(buf, &i, "*% ")) {
3990                             savingComment = FALSE;
3991                             suppressKibitz = 0;
3992                         }
3993                     }
3994                     next_out = i;
3995                 } else if (started == STARTED_HOLDINGS) {
3996                     int gamenum;
3997                     char new_piece[MSG_SIZ];
3998                     started = STARTED_NONE;
3999                     parse[parse_pos] = NULLCHAR;
4000                     if (appData.debugMode)
4001                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
4002                                                         parse, currentMove);
4003                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4004                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4005                         if (gameInfo.variant == VariantNormal) {
4006                           /* [HGM] We seem to switch variant during a game!
4007                            * Presumably no holdings were displayed, so we have
4008                            * to move the position two files to the right to
4009                            * create room for them!
4010                            */
4011                           VariantClass newVariant;
4012                           switch(gameInfo.boardWidth) { // base guess on board width
4013                                 case 9:  newVariant = VariantShogi; break;
4014                                 case 10: newVariant = VariantGreat; break;
4015                                 default: newVariant = VariantCrazyhouse; break;
4016                           }
4017                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4018                           /* Get a move list just to see the header, which
4019                              will tell us whether this is really bug or zh */
4020                           if (ics_getting_history == H_FALSE) {
4021                             ics_getting_history = H_REQUESTED;
4022                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4023                             SendToICS(str);
4024                           }
4025                         }
4026                         new_piece[0] = NULLCHAR;
4027                         sscanf(parse, "game %d white [%s black [%s <- %s",
4028                                &gamenum, white_holding, black_holding,
4029                                new_piece);
4030                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4031                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4032                         /* [HGM] copy holdings to board holdings area */
4033                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4034                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4035                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4036 #if ZIPPY
4037                         if (appData.zippyPlay && first.initDone) {
4038                             ZippyHoldings(white_holding, black_holding,
4039                                           new_piece);
4040                         }
4041 #endif /*ZIPPY*/
4042                         if (tinyLayout || smallLayout) {
4043                             char wh[16], bh[16];
4044                             PackHolding(wh, white_holding);
4045                             PackHolding(bh, black_holding);
4046                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4047                                     gameInfo.white, gameInfo.black);
4048                         } else {
4049                           snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"),
4050                                     gameInfo.white, white_holding,
4051                                     gameInfo.black, black_holding);
4052                         }
4053                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4054                         DrawPosition(FALSE, boards[currentMove]);
4055                         DisplayTitle(str);
4056                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4057                         sscanf(parse, "game %d white [%s black [%s <- %s",
4058                                &gamenum, white_holding, black_holding,
4059                                new_piece);
4060                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4061                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4062                         /* [HGM] copy holdings to partner-board holdings area */
4063                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4064                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4065                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4066                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4067                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4068                       }
4069                     }
4070                     /* Suppress following prompt */
4071                     if (looking_at(buf, &i, "*% ")) {
4072                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4073                         savingComment = FALSE;
4074                         suppressKibitz = 0;
4075                     }
4076                     next_out = i;
4077                 }
4078                 continue;
4079             }
4080
4081             i++;                /* skip unparsed character and loop back */
4082         }
4083
4084         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4085 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4086 //          SendToPlayer(&buf[next_out], i - next_out);
4087             started != STARTED_HOLDINGS && leftover_start > next_out) {
4088             SendToPlayer(&buf[next_out], leftover_start - next_out);
4089             next_out = i;
4090         }
4091
4092         leftover_len = buf_len - leftover_start;
4093         /* if buffer ends with something we couldn't parse,
4094            reparse it after appending the next read */
4095
4096     } else if (count == 0) {
4097         RemoveInputSource(isr);
4098         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4099     } else {
4100         DisplayFatalError(_("Error reading from ICS"), error, 1);
4101     }
4102 }
4103
4104
4105 /* Board style 12 looks like this:
4106
4107    <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
4108
4109  * The "<12> " is stripped before it gets to this routine.  The two
4110  * trailing 0's (flip state and clock ticking) are later addition, and
4111  * some chess servers may not have them, or may have only the first.
4112  * Additional trailing fields may be added in the future.
4113  */
4114
4115 #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"
4116
4117 #define RELATION_OBSERVING_PLAYED    0
4118 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4119 #define RELATION_PLAYING_MYMOVE      1
4120 #define RELATION_PLAYING_NOTMYMOVE  -1
4121 #define RELATION_EXAMINING           2
4122 #define RELATION_ISOLATED_BOARD     -3
4123 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4124
4125 void
4126 ParseBoard12(string)
4127      char *string;
4128 {
4129     GameMode newGameMode;
4130     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4131     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4132     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4133     char to_play, board_chars[200];
4134     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4135     char black[32], white[32];
4136     Board board;
4137     int prevMove = currentMove;
4138     int ticking = 2;
4139     ChessMove moveType;
4140     int fromX, fromY, toX, toY;
4141     char promoChar;
4142     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4143     char *bookHit = NULL; // [HGM] book
4144     Boolean weird = FALSE, reqFlag = FALSE;
4145
4146     fromX = fromY = toX = toY = -1;
4147
4148     newGame = FALSE;
4149
4150     if (appData.debugMode)
4151       fprintf(debugFP, _("Parsing board: %s\n"), string);
4152
4153     move_str[0] = NULLCHAR;
4154     elapsed_time[0] = NULLCHAR;
4155     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4156         int  i = 0, j;
4157         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4158             if(string[i] == ' ') { ranks++; files = 0; }
4159             else files++;
4160             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4161             i++;
4162         }
4163         for(j = 0; j <i; j++) board_chars[j] = string[j];
4164         board_chars[i] = '\0';
4165         string += i + 1;
4166     }
4167     n = sscanf(string, PATTERN, &to_play, &double_push,
4168                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4169                &gamenum, white, black, &relation, &basetime, &increment,
4170                &white_stren, &black_stren, &white_time, &black_time,
4171                &moveNum, str, elapsed_time, move_str, &ics_flip,
4172                &ticking);
4173
4174     if (n < 21) {
4175         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4176         DisplayError(str, 0);
4177         return;
4178     }
4179
4180     /* Convert the move number to internal form */
4181     moveNum = (moveNum - 1) * 2;
4182     if (to_play == 'B') moveNum++;
4183     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4184       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4185                         0, 1);
4186       return;
4187     }
4188
4189     switch (relation) {
4190       case RELATION_OBSERVING_PLAYED:
4191       case RELATION_OBSERVING_STATIC:
4192         if (gamenum == -1) {
4193             /* Old ICC buglet */
4194             relation = RELATION_OBSERVING_STATIC;
4195         }
4196         newGameMode = IcsObserving;
4197         break;
4198       case RELATION_PLAYING_MYMOVE:
4199       case RELATION_PLAYING_NOTMYMOVE:
4200         newGameMode =
4201           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4202             IcsPlayingWhite : IcsPlayingBlack;
4203         break;
4204       case RELATION_EXAMINING:
4205         newGameMode = IcsExamining;
4206         break;
4207       case RELATION_ISOLATED_BOARD:
4208       default:
4209         /* Just display this board.  If user was doing something else,
4210            we will forget about it until the next board comes. */
4211         newGameMode = IcsIdle;
4212         break;
4213       case RELATION_STARTING_POSITION:
4214         newGameMode = gameMode;
4215         break;
4216     }
4217
4218     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4219          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4220       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4221       char *toSqr;
4222       for (k = 0; k < ranks; k++) {
4223         for (j = 0; j < files; j++)
4224           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4225         if(gameInfo.holdingsWidth > 1) {
4226              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4227              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4228         }
4229       }
4230       CopyBoard(partnerBoard, board);
4231       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4232         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4233         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4234       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4235       if(toSqr = strchr(str, '-')) {
4236         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4237         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4238       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4239       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4240       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4241       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4242       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4243       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4244                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4245       DisplayMessage(partnerStatus, "");
4246         partnerBoardValid = TRUE;
4247       return;
4248     }
4249
4250     /* Modify behavior for initial board display on move listing
4251        of wild games.
4252        */
4253     switch (ics_getting_history) {
4254       case H_FALSE:
4255       case H_REQUESTED:
4256         break;
4257       case H_GOT_REQ_HEADER:
4258       case H_GOT_UNREQ_HEADER:
4259         /* This is the initial position of the current game */
4260         gamenum = ics_gamenum;
4261         moveNum = 0;            /* old ICS bug workaround */
4262         if (to_play == 'B') {
4263           startedFromSetupPosition = TRUE;
4264           blackPlaysFirst = TRUE;
4265           moveNum = 1;
4266           if (forwardMostMove == 0) forwardMostMove = 1;
4267           if (backwardMostMove == 0) backwardMostMove = 1;
4268           if (currentMove == 0) currentMove = 1;
4269         }
4270         newGameMode = gameMode;
4271         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4272         break;
4273       case H_GOT_UNWANTED_HEADER:
4274         /* This is an initial board that we don't want */
4275         return;
4276       case H_GETTING_MOVES:
4277         /* Should not happen */
4278         DisplayError(_("Error gathering move list: extra board"), 0);
4279         ics_getting_history = H_FALSE;
4280         return;
4281     }
4282
4283    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4284                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4285      /* [HGM] We seem to have switched variant unexpectedly
4286       * Try to guess new variant from board size
4287       */
4288           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4289           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4290           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4291           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4292           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4293           if(!weird) newVariant = VariantNormal;
4294           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4295           /* Get a move list just to see the header, which
4296              will tell us whether this is really bug or zh */
4297           if (ics_getting_history == H_FALSE) {
4298             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4299             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4300             SendToICS(str);
4301           }
4302     }
4303
4304     /* Take action if this is the first board of a new game, or of a
4305        different game than is currently being displayed.  */
4306     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4307         relation == RELATION_ISOLATED_BOARD) {
4308
4309         /* Forget the old game and get the history (if any) of the new one */
4310         if (gameMode != BeginningOfGame) {
4311           Reset(TRUE, TRUE);
4312         }
4313         newGame = TRUE;
4314         if (appData.autoRaiseBoard) BoardToTop();
4315         prevMove = -3;
4316         if (gamenum == -1) {
4317             newGameMode = IcsIdle;
4318         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4319                    appData.getMoveList && !reqFlag) {
4320             /* Need to get game history */
4321             ics_getting_history = H_REQUESTED;
4322             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4323             SendToICS(str);
4324         }
4325
4326         /* Initially flip the board to have black on the bottom if playing
4327            black or if the ICS flip flag is set, but let the user change
4328            it with the Flip View button. */
4329         flipView = appData.autoFlipView ?
4330           (newGameMode == IcsPlayingBlack) || ics_flip :
4331           appData.flipView;
4332
4333         /* Done with values from previous mode; copy in new ones */
4334         gameMode = newGameMode;
4335         ModeHighlight();
4336         ics_gamenum = gamenum;
4337         if (gamenum == gs_gamenum) {
4338             int klen = strlen(gs_kind);
4339             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4340             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4341             gameInfo.event = StrSave(str);
4342         } else {
4343             gameInfo.event = StrSave("ICS game");
4344         }
4345         gameInfo.site = StrSave(appData.icsHost);
4346         gameInfo.date = PGNDate();
4347         gameInfo.round = StrSave("-");
4348         gameInfo.white = StrSave(white);
4349         gameInfo.black = StrSave(black);
4350         timeControl = basetime * 60 * 1000;
4351         timeControl_2 = 0;
4352         timeIncrement = increment * 1000;
4353         movesPerSession = 0;
4354         gameInfo.timeControl = TimeControlTagValue();
4355         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4356   if (appData.debugMode) {
4357     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4358     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4359     setbuf(debugFP, NULL);
4360   }
4361
4362         gameInfo.outOfBook = NULL;
4363
4364         /* Do we have the ratings? */
4365         if (strcmp(player1Name, white) == 0 &&
4366             strcmp(player2Name, black) == 0) {
4367             if (appData.debugMode)
4368               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4369                       player1Rating, player2Rating);
4370             gameInfo.whiteRating = player1Rating;
4371             gameInfo.blackRating = player2Rating;
4372         } else if (strcmp(player2Name, white) == 0 &&
4373                    strcmp(player1Name, black) == 0) {
4374             if (appData.debugMode)
4375               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4376                       player2Rating, player1Rating);
4377             gameInfo.whiteRating = player2Rating;
4378             gameInfo.blackRating = player1Rating;
4379         }
4380         player1Name[0] = player2Name[0] = NULLCHAR;
4381
4382         /* Silence shouts if requested */
4383         if (appData.quietPlay &&
4384             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4385             SendToICS(ics_prefix);
4386             SendToICS("set shout 0\n");
4387         }
4388     }
4389
4390     /* Deal with midgame name changes */
4391     if (!newGame) {
4392         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4393             if (gameInfo.white) free(gameInfo.white);
4394             gameInfo.white = StrSave(white);
4395         }
4396         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4397             if (gameInfo.black) free(gameInfo.black);
4398             gameInfo.black = StrSave(black);
4399         }
4400     }
4401
4402     /* Throw away game result if anything actually changes in examine mode */
4403     if (gameMode == IcsExamining && !newGame) {
4404         gameInfo.result = GameUnfinished;
4405         if (gameInfo.resultDetails != NULL) {
4406             free(gameInfo.resultDetails);
4407             gameInfo.resultDetails = NULL;
4408         }
4409     }
4410
4411     /* In pausing && IcsExamining mode, we ignore boards coming
4412        in if they are in a different variation than we are. */
4413     if (pauseExamInvalid) return;
4414     if (pausing && gameMode == IcsExamining) {
4415         if (moveNum <= pauseExamForwardMostMove) {
4416             pauseExamInvalid = TRUE;
4417             forwardMostMove = pauseExamForwardMostMove;
4418             return;
4419         }
4420     }
4421
4422   if (appData.debugMode) {
4423     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4424   }
4425     /* Parse the board */
4426     for (k = 0; k < ranks; k++) {
4427       for (j = 0; j < files; j++)
4428         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4429       if(gameInfo.holdingsWidth > 1) {
4430            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4431            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4432       }
4433     }
4434     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4435       board[5][BOARD_RGHT+1] = WhiteAngel;
4436       board[6][BOARD_RGHT+1] = WhiteMarshall;
4437       board[1][0] = BlackMarshall;
4438       board[2][0] = BlackAngel;
4439       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4440     }
4441     CopyBoard(boards[moveNum], board);
4442     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4443     if (moveNum == 0) {
4444         startedFromSetupPosition =
4445           !CompareBoards(board, initialPosition);
4446         if(startedFromSetupPosition)
4447             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4448     }
4449
4450     /* [HGM] Set castling rights. Take the outermost Rooks,
4451        to make it also work for FRC opening positions. Note that board12
4452        is really defective for later FRC positions, as it has no way to
4453        indicate which Rook can castle if they are on the same side of King.
4454        For the initial position we grant rights to the outermost Rooks,
4455        and remember thos rights, and we then copy them on positions
4456        later in an FRC game. This means WB might not recognize castlings with
4457        Rooks that have moved back to their original position as illegal,
4458        but in ICS mode that is not its job anyway.
4459     */
4460     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4461     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4462
4463         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4464             if(board[0][i] == WhiteRook) j = i;
4465         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4467             if(board[0][i] == WhiteRook) j = i;
4468         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4469         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4470             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4471         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4472         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4473             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4474         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4475
4476         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4477         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4478             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4479         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4480             if(board[BOARD_HEIGHT-1][k] == bKing)
4481                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4482         if(gameInfo.variant == VariantTwoKings) {
4483             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4484             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4485             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4486         }
4487     } else { int r;
4488         r = boards[moveNum][CASTLING][0] = initialRights[0];
4489         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4490         r = boards[moveNum][CASTLING][1] = initialRights[1];
4491         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4492         r = boards[moveNum][CASTLING][3] = initialRights[3];
4493         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4494         r = boards[moveNum][CASTLING][4] = initialRights[4];
4495         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4496         /* wildcastle kludge: always assume King has rights */
4497         r = boards[moveNum][CASTLING][2] = initialRights[2];
4498         r = boards[moveNum][CASTLING][5] = initialRights[5];
4499     }
4500     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4501     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4502
4503
4504     if (ics_getting_history == H_GOT_REQ_HEADER ||
4505         ics_getting_history == H_GOT_UNREQ_HEADER) {
4506         /* This was an initial position from a move list, not
4507            the current position */
4508         return;
4509     }
4510
4511     /* Update currentMove and known move number limits */
4512     newMove = newGame || moveNum > forwardMostMove;
4513
4514     if (newGame) {
4515         forwardMostMove = backwardMostMove = currentMove = moveNum;
4516         if (gameMode == IcsExamining && moveNum == 0) {
4517           /* Workaround for ICS limitation: we are not told the wild
4518              type when starting to examine a game.  But if we ask for
4519              the move list, the move list header will tell us */
4520             ics_getting_history = H_REQUESTED;
4521             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4522             SendToICS(str);
4523         }
4524     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4525                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4526 #if ZIPPY
4527         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4528         /* [HGM] applied this also to an engine that is silently watching        */
4529         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4530             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4531             gameInfo.variant == currentlyInitializedVariant) {
4532           takeback = forwardMostMove - moveNum;
4533           for (i = 0; i < takeback; i++) {
4534             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4535             SendToProgram("undo\n", &first);
4536           }
4537         }
4538 #endif
4539
4540         forwardMostMove = moveNum;
4541         if (!pausing || currentMove > forwardMostMove)
4542           currentMove = forwardMostMove;
4543     } else {
4544         /* New part of history that is not contiguous with old part */
4545         if (pausing && gameMode == IcsExamining) {
4546             pauseExamInvalid = TRUE;
4547             forwardMostMove = pauseExamForwardMostMove;
4548             return;
4549         }
4550         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4551 #if ZIPPY
4552             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4553                 // [HGM] when we will receive the move list we now request, it will be
4554                 // fed to the engine from the first move on. So if the engine is not
4555                 // in the initial position now, bring it there.
4556                 InitChessProgram(&first, 0);
4557             }
4558 #endif
4559             ics_getting_history = H_REQUESTED;
4560             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4561             SendToICS(str);
4562         }
4563         forwardMostMove = backwardMostMove = currentMove = moveNum;
4564     }
4565
4566     /* Update the clocks */
4567     if (strchr(elapsed_time, '.')) {
4568       /* Time is in ms */
4569       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4570       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4571     } else {
4572       /* Time is in seconds */
4573       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4574       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4575     }
4576
4577
4578 #if ZIPPY
4579     if (appData.zippyPlay && newGame &&
4580         gameMode != IcsObserving && gameMode != IcsIdle &&
4581         gameMode != IcsExamining)
4582       ZippyFirstBoard(moveNum, basetime, increment);
4583 #endif
4584
4585     /* Put the move on the move list, first converting
4586        to canonical algebraic form. */
4587     if (moveNum > 0) {
4588   if (appData.debugMode) {
4589     if (appData.debugMode) { int f = forwardMostMove;
4590         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4591                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4592                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4593     }
4594     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4595     fprintf(debugFP, "moveNum = %d\n", moveNum);
4596     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4597     setbuf(debugFP, NULL);
4598   }
4599         if (moveNum <= backwardMostMove) {
4600             /* We don't know what the board looked like before
4601                this move.  Punt. */
4602           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4603             strcat(parseList[moveNum - 1], " ");
4604             strcat(parseList[moveNum - 1], elapsed_time);
4605             moveList[moveNum - 1][0] = NULLCHAR;
4606         } else if (strcmp(move_str, "none") == 0) {
4607             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4608             /* Again, we don't know what the board looked like;
4609                this is really the start of the game. */
4610             parseList[moveNum - 1][0] = NULLCHAR;
4611             moveList[moveNum - 1][0] = NULLCHAR;
4612             backwardMostMove = moveNum;
4613             startedFromSetupPosition = TRUE;
4614             fromX = fromY = toX = toY = -1;
4615         } else {
4616           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4617           //                 So we parse the long-algebraic move string in stead of the SAN move
4618           int valid; char buf[MSG_SIZ], *prom;
4619
4620           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4621                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4622           // str looks something like "Q/a1-a2"; kill the slash
4623           if(str[1] == '/')
4624             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4625           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4626           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4627                 strcat(buf, prom); // long move lacks promo specification!
4628           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4629                 if(appData.debugMode)
4630                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4631                 safeStrCpy(move_str, buf, MSG_SIZ);
4632           }
4633           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4634                                 &fromX, &fromY, &toX, &toY, &promoChar)
4635                || ParseOneMove(buf, moveNum - 1, &moveType,
4636                                 &fromX, &fromY, &toX, &toY, &promoChar);
4637           // end of long SAN patch
4638           if (valid) {
4639             (void) CoordsToAlgebraic(boards[moveNum - 1],
4640                                      PosFlags(moveNum - 1),
4641                                      fromY, fromX, toY, toX, promoChar,
4642                                      parseList[moveNum-1]);
4643             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4644               case MT_NONE:
4645               case MT_STALEMATE:
4646               default:
4647                 break;
4648               case MT_CHECK:
4649                 if(gameInfo.variant != VariantShogi)
4650                     strcat(parseList[moveNum - 1], "+");
4651                 break;
4652               case MT_CHECKMATE:
4653               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4654                 strcat(parseList[moveNum - 1], "#");
4655                 break;
4656             }
4657             strcat(parseList[moveNum - 1], " ");
4658             strcat(parseList[moveNum - 1], elapsed_time);
4659             /* currentMoveString is set as a side-effect of ParseOneMove */
4660             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4661             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4662             strcat(moveList[moveNum - 1], "\n");
4663
4664             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4665                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4666               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4667                 ChessSquare old, new = boards[moveNum][k][j];
4668                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4669                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4670                   if(old == new) continue;
4671                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4672                   else if(new == WhiteWazir || new == BlackWazir) {
4673                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4674                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4675                       else boards[moveNum][k][j] = old; // preserve type of Gold
4676                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4677                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4678               }
4679           } else {
4680             /* Move from ICS was illegal!?  Punt. */
4681             if (appData.debugMode) {
4682               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4683               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4684             }
4685             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4686             strcat(parseList[moveNum - 1], " ");
4687             strcat(parseList[moveNum - 1], elapsed_time);
4688             moveList[moveNum - 1][0] = NULLCHAR;
4689             fromX = fromY = toX = toY = -1;
4690           }
4691         }
4692   if (appData.debugMode) {
4693     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4694     setbuf(debugFP, NULL);
4695   }
4696
4697 #if ZIPPY
4698         /* Send move to chess program (BEFORE animating it). */
4699         if (appData.zippyPlay && !newGame && newMove &&
4700            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4701
4702             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4703                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4704                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4705                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4706                             move_str);
4707                     DisplayError(str, 0);
4708                 } else {
4709                     if (first.sendTime) {
4710                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4711                     }
4712                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4713                     if (firstMove && !bookHit) {
4714                         firstMove = FALSE;
4715                         if (first.useColors) {
4716                           SendToProgram(gameMode == IcsPlayingWhite ?
4717                                         "white\ngo\n" :
4718                                         "black\ngo\n", &first);
4719                         } else {
4720                           SendToProgram("go\n", &first);
4721                         }
4722                         first.maybeThinking = TRUE;
4723                     }
4724                 }
4725             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4726               if (moveList[moveNum - 1][0] == NULLCHAR) {
4727                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4728                 DisplayError(str, 0);
4729               } else {
4730                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4731                 SendMoveToProgram(moveNum - 1, &first);
4732               }
4733             }
4734         }
4735 #endif
4736     }
4737
4738     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4739         /* If move comes from a remote source, animate it.  If it
4740            isn't remote, it will have already been animated. */
4741         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4742             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4743         }
4744         if (!pausing && appData.highlightLastMove) {
4745             SetHighlights(fromX, fromY, toX, toY);
4746         }
4747     }
4748
4749     /* Start the clocks */
4750     whiteFlag = blackFlag = FALSE;
4751     appData.clockMode = !(basetime == 0 && increment == 0);
4752     if (ticking == 0) {
4753       ics_clock_paused = TRUE;
4754       StopClocks();
4755     } else if (ticking == 1) {
4756       ics_clock_paused = FALSE;
4757     }
4758     if (gameMode == IcsIdle ||
4759         relation == RELATION_OBSERVING_STATIC ||
4760         relation == RELATION_EXAMINING ||
4761         ics_clock_paused)
4762       DisplayBothClocks();
4763     else
4764       StartClocks();
4765
4766     /* Display opponents and material strengths */
4767     if (gameInfo.variant != VariantBughouse &&
4768         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4769         if (tinyLayout || smallLayout) {
4770             if(gameInfo.variant == VariantNormal)
4771               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4772                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4773                     basetime, increment);
4774             else
4775               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4776                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4777                     basetime, increment, (int) gameInfo.variant);
4778         } else {
4779             if(gameInfo.variant == VariantNormal)
4780               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"),
4781                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4782                     basetime, increment);
4783             else
4784               snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"),
4785                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4786                     basetime, increment, VariantName(gameInfo.variant));
4787         }
4788         DisplayTitle(str);
4789   if (appData.debugMode) {
4790     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4791   }
4792     }
4793
4794
4795     /* Display the board */
4796     if (!pausing && !appData.noGUI) {
4797
4798       if (appData.premove)
4799           if (!gotPremove ||
4800              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4801              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4802               ClearPremoveHighlights();
4803
4804       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4805         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4806       DrawPosition(j, boards[currentMove]);
4807
4808       DisplayMove(moveNum - 1);
4809       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4810             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4811               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4812         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4813       }
4814     }
4815
4816     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4817 #if ZIPPY
4818     if(bookHit) { // [HGM] book: simulate book reply
4819         static char bookMove[MSG_SIZ]; // a bit generous?
4820
4821         programStats.nodes = programStats.depth = programStats.time =
4822         programStats.score = programStats.got_only_move = 0;
4823         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4824
4825         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4826         strcat(bookMove, bookHit);
4827         HandleMachineMove(bookMove, &first);
4828     }
4829 #endif
4830 }
4831
4832 void
4833 GetMoveListEvent()
4834 {
4835     char buf[MSG_SIZ];
4836     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4837         ics_getting_history = H_REQUESTED;
4838         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4839         SendToICS(buf);
4840     }
4841 }
4842
4843 void
4844 AnalysisPeriodicEvent(force)
4845      int force;
4846 {
4847     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4848          && !force) || !appData.periodicUpdates)
4849       return;
4850
4851     /* Send . command to Crafty to collect stats */
4852     SendToProgram(".\n", &first);
4853
4854     /* Don't send another until we get a response (this makes
4855        us stop sending to old Crafty's which don't understand
4856        the "." command (sending illegal cmds resets node count & time,
4857        which looks bad)) */
4858     programStats.ok_to_send = 0;
4859 }
4860
4861 void ics_update_width(new_width)
4862         int new_width;
4863 {
4864         ics_printf("set width %d\n", new_width);
4865 }
4866
4867 void
4868 SendMoveToProgram(moveNum, cps)
4869      int moveNum;
4870      ChessProgramState *cps;
4871 {
4872     char buf[MSG_SIZ];
4873
4874     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4875         // null move in variant where engine does not understand it (for analysis purposes)
4876         SendBoard(cps, moveNum + 1); // send position after move in stead.
4877         return;
4878     }
4879     if (cps->useUsermove) {
4880       SendToProgram("usermove ", cps);
4881     }
4882     if (cps->useSAN) {
4883       char *space;
4884       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4885         int len = space - parseList[moveNum];
4886         memcpy(buf, parseList[moveNum], len);
4887         buf[len++] = '\n';
4888         buf[len] = NULLCHAR;
4889       } else {
4890         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4891       }
4892       SendToProgram(buf, cps);
4893     } else {
4894       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4895         AlphaRank(moveList[moveNum], 4);
4896         SendToProgram(moveList[moveNum], cps);
4897         AlphaRank(moveList[moveNum], 4); // and back
4898       } else
4899       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4900        * the engine. It would be nice to have a better way to identify castle
4901        * moves here. */
4902       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4903                                                                          && cps->useOOCastle) {
4904         int fromX = moveList[moveNum][0] - AAA;
4905         int fromY = moveList[moveNum][1] - ONE;
4906         int toX = moveList[moveNum][2] - AAA;
4907         int toY = moveList[moveNum][3] - ONE;
4908         if((boards[moveNum][fromY][fromX] == WhiteKing
4909             && boards[moveNum][toY][toX] == WhiteRook)
4910            || (boards[moveNum][fromY][fromX] == BlackKing
4911                && boards[moveNum][toY][toX] == BlackRook)) {
4912           if(toX > fromX) SendToProgram("O-O\n", cps);
4913           else SendToProgram("O-O-O\n", cps);
4914         }
4915         else SendToProgram(moveList[moveNum], cps);
4916       } else
4917       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4918         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4919           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4920           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4921                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4922         } else
4923           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4924                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4925         SendToProgram(buf, cps);
4926       }
4927       else SendToProgram(moveList[moveNum], cps);
4928       /* End of additions by Tord */
4929     }
4930
4931     /* [HGM] setting up the opening has brought engine in force mode! */
4932     /*       Send 'go' if we are in a mode where machine should play. */
4933     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4934         (gameMode == TwoMachinesPlay   ||
4935 #if ZIPPY
4936          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4937 #endif
4938          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4939         SendToProgram("go\n", cps);
4940   if (appData.debugMode) {
4941     fprintf(debugFP, "(extra)\n");
4942   }
4943     }
4944     setboardSpoiledMachineBlack = 0;
4945 }
4946
4947 void
4948 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4949      ChessMove moveType;
4950      int fromX, fromY, toX, toY;
4951      char promoChar;
4952 {
4953     char user_move[MSG_SIZ];
4954     char suffix[4];
4955
4956     if(gameInfo.variant == VariantSChess && promoChar) {
4957         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4958         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4959     } else suffix[0] = NULLCHAR;
4960
4961     switch (moveType) {
4962       default:
4963         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4964                 (int)moveType, fromX, fromY, toX, toY);
4965         DisplayError(user_move + strlen("say "), 0);
4966         break;
4967       case WhiteKingSideCastle:
4968       case BlackKingSideCastle:
4969       case WhiteQueenSideCastleWild:
4970       case BlackQueenSideCastleWild:
4971       /* PUSH Fabien */
4972       case WhiteHSideCastleFR:
4973       case BlackHSideCastleFR:
4974       /* POP Fabien */
4975         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4976         break;
4977       case WhiteQueenSideCastle:
4978       case BlackQueenSideCastle:
4979       case WhiteKingSideCastleWild:
4980       case BlackKingSideCastleWild:
4981       /* PUSH Fabien */
4982       case WhiteASideCastleFR:
4983       case BlackASideCastleFR:
4984       /* POP Fabien */
4985         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4986         break;
4987       case WhiteNonPromotion:
4988       case BlackNonPromotion:
4989         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4990         break;
4991       case WhitePromotion:
4992       case BlackPromotion:
4993         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4994           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4995                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4996                 PieceToChar(WhiteFerz));
4997         else if(gameInfo.variant == VariantGreat)
4998           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4999                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5000                 PieceToChar(WhiteMan));
5001         else
5002           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
5003                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
5004                 promoChar);
5005         break;
5006       case WhiteDrop:
5007       case BlackDrop:
5008       drop:
5009         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
5010                  ToUpper(PieceToChar((ChessSquare) fromX)),
5011                  AAA + toX, ONE + toY);
5012         break;
5013       case IllegalMove:  /* could be a variant we don't quite understand */
5014         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
5015       case NormalMove:
5016       case WhiteCapturesEnPassant:
5017       case BlackCapturesEnPassant:
5018         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
5019                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5020         break;
5021     }
5022     SendToICS(user_move);
5023     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5024         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5025 }
5026
5027 void
5028 UploadGameEvent()
5029 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5030     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5031     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5032     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5033       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5034       return;
5035     }
5036     if(gameMode != IcsExamining) { // is this ever not the case?
5037         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5038
5039         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5040           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5041         } else { // on FICS we must first go to general examine mode
5042           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5043         }
5044         if(gameInfo.variant != VariantNormal) {
5045             // try figure out wild number, as xboard names are not always valid on ICS
5046             for(i=1; i<=36; i++) {
5047               snprintf(buf, MSG_SIZ, "wild/%d", i);
5048                 if(StringToVariant(buf) == gameInfo.variant) break;
5049             }
5050             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5051             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5052             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5053         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5054         SendToICS(ics_prefix);
5055         SendToICS(buf);
5056         if(startedFromSetupPosition || backwardMostMove != 0) {
5057           fen = PositionToFEN(backwardMostMove, NULL);
5058           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5059             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5060             SendToICS(buf);
5061           } else { // FICS: everything has to set by separate bsetup commands
5062             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5063             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5064             SendToICS(buf);
5065             if(!WhiteOnMove(backwardMostMove)) {
5066                 SendToICS("bsetup tomove black\n");
5067             }
5068             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5069             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5070             SendToICS(buf);
5071             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5072             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5073             SendToICS(buf);
5074             i = boards[backwardMostMove][EP_STATUS];
5075             if(i >= 0) { // set e.p.
5076               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5077                 SendToICS(buf);
5078             }
5079             bsetup++;
5080           }
5081         }
5082       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5083             SendToICS("bsetup done\n"); // switch to normal examining.
5084     }
5085     for(i = backwardMostMove; i<last; i++) {
5086         char buf[20];
5087         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5088         SendToICS(buf);
5089     }
5090     SendToICS(ics_prefix);
5091     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5092 }
5093
5094 void
5095 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5096      int rf, ff, rt, ft;
5097      char promoChar;
5098      char move[7];
5099 {
5100     if (rf == DROP_RANK) {
5101       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5102       sprintf(move, "%c@%c%c\n",
5103                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5104     } else {
5105         if (promoChar == 'x' || promoChar == NULLCHAR) {
5106           sprintf(move, "%c%c%c%c\n",
5107                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5108         } else {
5109             sprintf(move, "%c%c%c%c%c\n",
5110                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5111         }
5112     }
5113 }
5114
5115 void
5116 ProcessICSInitScript(f)
5117      FILE *f;
5118 {
5119     char buf[MSG_SIZ];
5120
5121     while (fgets(buf, MSG_SIZ, f)) {
5122         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5123     }
5124
5125     fclose(f);
5126 }
5127
5128
5129 static int lastX, lastY, selectFlag, dragging;
5130
5131 void
5132 Sweep(int step)
5133 {
5134     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5135     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5136     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5137     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5138     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5139     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5140     do {
5141         promoSweep -= step;
5142         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5143         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5144         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5145         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5146         if(!step) step = -1;
5147     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5148             appData.testLegality && (promoSweep == king ||
5149             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5150     ChangeDragPiece(promoSweep);
5151 }
5152
5153 int PromoScroll(int x, int y)
5154 {
5155   int step = 0;
5156
5157   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5158   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5159   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5160   if(!step) return FALSE;
5161   lastX = x; lastY = y;
5162   if((promoSweep < BlackPawn) == flipView) step = -step;
5163   if(step > 0) selectFlag = 1;
5164   if(!selectFlag) Sweep(step);
5165   return FALSE;
5166 }
5167
5168 void
5169 NextPiece(int step)
5170 {
5171     ChessSquare piece = boards[currentMove][toY][toX];
5172     do {
5173         pieceSweep -= step;
5174         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5175         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5176         if(!step) step = -1;
5177     } while(PieceToChar(pieceSweep) == '.');
5178     boards[currentMove][toY][toX] = pieceSweep;
5179     DrawPosition(FALSE, boards[currentMove]);
5180     boards[currentMove][toY][toX] = piece;
5181 }
5182 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5183 void
5184 AlphaRank(char *move, int n)
5185 {
5186 //    char *p = move, c; int x, y;
5187
5188     if (appData.debugMode) {
5189         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5190     }
5191
5192     if(move[1]=='*' &&
5193        move[2]>='0' && move[2]<='9' &&
5194        move[3]>='a' && move[3]<='x'    ) {
5195         move[1] = '@';
5196         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5197         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5198     } else
5199     if(move[0]>='0' && move[0]<='9' &&
5200        move[1]>='a' && move[1]<='x' &&
5201        move[2]>='0' && move[2]<='9' &&
5202        move[3]>='a' && move[3]<='x'    ) {
5203         /* input move, Shogi -> normal */
5204         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5205         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5206         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5207         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5208     } else
5209     if(move[1]=='@' &&
5210        move[3]>='0' && move[3]<='9' &&
5211        move[2]>='a' && move[2]<='x'    ) {
5212         move[1] = '*';
5213         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5214         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5215     } else
5216     if(
5217        move[0]>='a' && move[0]<='x' &&
5218        move[3]>='0' && move[3]<='9' &&
5219        move[2]>='a' && move[2]<='x'    ) {
5220          /* output move, normal -> Shogi */
5221         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5222         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5223         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5224         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5225         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5226     }
5227     if (appData.debugMode) {
5228         fprintf(debugFP, "   out = '%s'\n", move);
5229     }
5230 }
5231
5232 char yy_textstr[8000];
5233
5234 /* Parser for moves from gnuchess, ICS, or user typein box */
5235 Boolean
5236 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5237      char *move;
5238      int moveNum;
5239      ChessMove *moveType;
5240      int *fromX, *fromY, *toX, *toY;
5241      char *promoChar;
5242 {
5243     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5244
5245     switch (*moveType) {
5246       case WhitePromotion:
5247       case BlackPromotion:
5248       case WhiteNonPromotion:
5249       case BlackNonPromotion:
5250       case NormalMove:
5251       case WhiteCapturesEnPassant:
5252       case BlackCapturesEnPassant:
5253       case WhiteKingSideCastle:
5254       case WhiteQueenSideCastle:
5255       case BlackKingSideCastle:
5256       case BlackQueenSideCastle:
5257       case WhiteKingSideCastleWild:
5258       case WhiteQueenSideCastleWild:
5259       case BlackKingSideCastleWild:
5260       case BlackQueenSideCastleWild:
5261       /* Code added by Tord: */
5262       case WhiteHSideCastleFR:
5263       case WhiteASideCastleFR:
5264       case BlackHSideCastleFR:
5265       case BlackASideCastleFR:
5266       /* End of code added by Tord */
5267       case IllegalMove:         /* bug or odd chess variant */
5268         *fromX = currentMoveString[0] - AAA;
5269         *fromY = currentMoveString[1] - ONE;
5270         *toX = currentMoveString[2] - AAA;
5271         *toY = currentMoveString[3] - ONE;
5272         *promoChar = currentMoveString[4];
5273         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5274             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5275     if (appData.debugMode) {
5276         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5277     }
5278             *fromX = *fromY = *toX = *toY = 0;
5279             return FALSE;
5280         }
5281         if (appData.testLegality) {
5282           return (*moveType != IllegalMove);
5283         } else {
5284           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5285                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5286         }
5287
5288       case WhiteDrop:
5289       case BlackDrop:
5290         *fromX = *moveType == WhiteDrop ?
5291           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5292           (int) CharToPiece(ToLower(currentMoveString[0]));
5293         *fromY = DROP_RANK;
5294         *toX = currentMoveString[2] - AAA;
5295         *toY = currentMoveString[3] - ONE;
5296         *promoChar = NULLCHAR;
5297         return TRUE;
5298
5299       case AmbiguousMove:
5300       case ImpossibleMove:
5301       case EndOfFile:
5302       case ElapsedTime:
5303       case Comment:
5304       case PGNTag:
5305       case NAG:
5306       case WhiteWins:
5307       case BlackWins:
5308       case GameIsDrawn:
5309       default:
5310     if (appData.debugMode) {
5311         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5312     }
5313         /* bug? */
5314         *fromX = *fromY = *toX = *toY = 0;
5315         *promoChar = NULLCHAR;
5316         return FALSE;
5317     }
5318 }
5319
5320 Boolean pushed = FALSE;
5321 char *lastParseAttempt;
5322
5323 void
5324 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5325 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5326   int fromX, fromY, toX, toY; char promoChar;
5327   ChessMove moveType;
5328   Boolean valid;
5329   int nr = 0;
5330
5331   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5332     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5333     pushed = TRUE;
5334   }
5335   endPV = forwardMostMove;
5336   do {
5337     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5338     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5339     lastParseAttempt = pv;
5340     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5341 if(appData.debugMode){
5342 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5343 }
5344     if(!valid && nr == 0 &&
5345        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5346         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5347         // Hande case where played move is different from leading PV move
5348         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5349         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5350         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5351         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5352           endPV += 2; // if position different, keep this
5353           moveList[endPV-1][0] = fromX + AAA;
5354           moveList[endPV-1][1] = fromY + ONE;
5355           moveList[endPV-1][2] = toX + AAA;
5356           moveList[endPV-1][3] = toY + ONE;
5357           parseList[endPV-1][0] = NULLCHAR;
5358           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5359         }
5360       }
5361     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5362     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5363     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5364     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5365         valid++; // allow comments in PV
5366         continue;
5367     }
5368     nr++;
5369     if(endPV+1 > framePtr) break; // no space, truncate
5370     if(!valid) break;
5371     endPV++;
5372     CopyBoard(boards[endPV], boards[endPV-1]);
5373     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5374     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5375     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5376     CoordsToAlgebraic(boards[endPV - 1],
5377                              PosFlags(endPV - 1),
5378                              fromY, fromX, toY, toX, promoChar,
5379                              parseList[endPV - 1]);
5380   } while(valid);
5381   if(atEnd == 2) return; // used hidden, for PV conversion
5382   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5383   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5384   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5385                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5386   DrawPosition(TRUE, boards[currentMove]);
5387 }
5388
5389 int
5390 MultiPV(ChessProgramState *cps)
5391 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5392         int i;
5393         for(i=0; i<cps->nrOptions; i++)
5394             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5395                 return i;
5396         return -1;
5397 }
5398
5399 Boolean
5400 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5401 {
5402         int startPV, multi, lineStart, origIndex = index;
5403         char *p, buf2[MSG_SIZ];
5404
5405         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5406         lastX = x; lastY = y;
5407         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5408         lineStart = startPV = index;
5409         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5410         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5411         index = startPV;
5412         do{ while(buf[index] && buf[index] != '\n') index++;
5413         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5414         buf[index] = 0;
5415         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5416                 int n = first.option[multi].value;
5417                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5418                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5419                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5420                 first.option[multi].value = n;
5421                 *start = *end = 0;
5422                 return FALSE;
5423         }
5424         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5425         *start = startPV; *end = index-1;
5426         return TRUE;
5427 }
5428
5429 char *
5430 PvToSAN(char *pv)
5431 {
5432         static char buf[10*MSG_SIZ];
5433         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5434         *buf = NULLCHAR;
5435         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5436         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5437         for(i = forwardMostMove; i<endPV; i++){
5438             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5439             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5440             k += strlen(buf+k);
5441         }
5442         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5443         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5444         endPV = savedEnd;
5445         return buf;
5446 }
5447
5448 Boolean
5449 LoadPV(int x, int y)
5450 { // called on right mouse click to load PV
5451   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5452   lastX = x; lastY = y;
5453   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5454   return TRUE;
5455 }
5456
5457 void
5458 UnLoadPV()
5459 {
5460   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5461   if(endPV < 0) return;
5462   endPV = -1;
5463   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5464         Boolean saveAnimate = appData.animate;
5465         if(pushed) {
5466             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5467                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5468             } else storedGames--; // abandon shelved tail of original game
5469         }
5470         pushed = FALSE;
5471         forwardMostMove = currentMove;
5472         currentMove = oldFMM;
5473         appData.animate = FALSE;
5474         ToNrEvent(forwardMostMove);
5475         appData.animate = saveAnimate;
5476   }
5477   currentMove = forwardMostMove;
5478   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5479   ClearPremoveHighlights();
5480   DrawPosition(TRUE, boards[currentMove]);
5481 }
5482
5483 void
5484 MovePV(int x, int y, int h)
5485 { // step through PV based on mouse coordinates (called on mouse move)
5486   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5487
5488   // we must somehow check if right button is still down (might be released off board!)
5489   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5490   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5491   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5492   if(!step) return;
5493   lastX = x; lastY = y;
5494
5495   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5496   if(endPV < 0) return;
5497   if(y < margin) step = 1; else
5498   if(y > h - margin) step = -1;
5499   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5500   currentMove += step;
5501   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5502   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5503                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5504   DrawPosition(FALSE, boards[currentMove]);
5505 }
5506
5507
5508 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5509 // All positions will have equal probability, but the current method will not provide a unique
5510 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5511 #define DARK 1
5512 #define LITE 2
5513 #define ANY 3
5514
5515 int squaresLeft[4];
5516 int piecesLeft[(int)BlackPawn];
5517 int seed, nrOfShuffles;
5518
5519 void GetPositionNumber()
5520 {       // sets global variable seed
5521         int i;
5522
5523         seed = appData.defaultFrcPosition;
5524         if(seed < 0) { // randomize based on time for negative FRC position numbers
5525                 for(i=0; i<50; i++) seed += random();
5526                 seed = random() ^ random() >> 8 ^ random() << 8;
5527                 if(seed<0) seed = -seed;
5528         }
5529 }
5530
5531 int put(Board board, int pieceType, int rank, int n, int shade)
5532 // put the piece on the (n-1)-th empty squares of the given shade
5533 {
5534         int i;
5535
5536         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5537                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5538                         board[rank][i] = (ChessSquare) pieceType;
5539                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5540                         squaresLeft[ANY]--;
5541                         piecesLeft[pieceType]--;
5542                         return i;
5543                 }
5544         }
5545         return -1;
5546 }
5547
5548
5549 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5550 // calculate where the next piece goes, (any empty square), and put it there
5551 {
5552         int i;
5553
5554         i = seed % squaresLeft[shade];
5555         nrOfShuffles *= squaresLeft[shade];
5556         seed /= squaresLeft[shade];
5557         put(board, pieceType, rank, i, shade);
5558 }
5559
5560 void AddTwoPieces(Board board, int pieceType, int rank)
5561 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5562 {
5563         int i, n=squaresLeft[ANY], j=n-1, k;
5564
5565         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5566         i = seed % k;  // pick one
5567         nrOfShuffles *= k;
5568         seed /= k;
5569         while(i >= j) i -= j--;
5570         j = n - 1 - j; i += j;
5571         put(board, pieceType, rank, j, ANY);
5572         put(board, pieceType, rank, i, ANY);
5573 }
5574
5575 void SetUpShuffle(Board board, int number)
5576 {
5577         int i, p, first=1;
5578
5579         GetPositionNumber(); nrOfShuffles = 1;
5580
5581         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5582         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5583         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5584
5585         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5586
5587         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5588             p = (int) board[0][i];
5589             if(p < (int) BlackPawn) piecesLeft[p] ++;
5590             board[0][i] = EmptySquare;
5591         }
5592
5593         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5594             // shuffles restricted to allow normal castling put KRR first
5595             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5596                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5597             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5598                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5599             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5600                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5601             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5602                 put(board, WhiteRook, 0, 0, ANY);
5603             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5604         }
5605
5606         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5607             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5608             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5609                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5610                 while(piecesLeft[p] >= 2) {
5611                     AddOnePiece(board, p, 0, LITE);
5612                     AddOnePiece(board, p, 0, DARK);
5613                 }
5614                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5615             }
5616
5617         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5618             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5619             // but we leave King and Rooks for last, to possibly obey FRC restriction
5620             if(p == (int)WhiteRook) continue;
5621             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5622             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5623         }
5624
5625         // now everything is placed, except perhaps King (Unicorn) and Rooks
5626
5627         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5628             // Last King gets castling rights
5629             while(piecesLeft[(int)WhiteUnicorn]) {
5630                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5631                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5632             }
5633
5634             while(piecesLeft[(int)WhiteKing]) {
5635                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5636                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5637             }
5638
5639
5640         } else {
5641             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5642             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5643         }
5644
5645         // Only Rooks can be left; simply place them all
5646         while(piecesLeft[(int)WhiteRook]) {
5647                 i = put(board, WhiteRook, 0, 0, ANY);
5648                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5649                         if(first) {
5650                                 first=0;
5651                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5652                         }
5653                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5654                 }
5655         }
5656         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5657             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5658         }
5659
5660         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5661 }
5662
5663 int SetCharTable( char *table, const char * map )
5664 /* [HGM] moved here from winboard.c because of its general usefulness */
5665 /*       Basically a safe strcpy that uses the last character as King */
5666 {
5667     int result = FALSE; int NrPieces;
5668
5669     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5670                     && NrPieces >= 12 && !(NrPieces&1)) {
5671         int i; /* [HGM] Accept even length from 12 to 34 */
5672
5673         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5674         for( i=0; i<NrPieces/2-1; i++ ) {
5675             table[i] = map[i];
5676             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5677         }
5678         table[(int) WhiteKing]  = map[NrPieces/2-1];
5679         table[(int) BlackKing]  = map[NrPieces-1];
5680
5681         result = TRUE;
5682     }
5683
5684     return result;
5685 }
5686
5687 void Prelude(Board board)
5688 {       // [HGM] superchess: random selection of exo-pieces
5689         int i, j, k; ChessSquare p;
5690         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5691
5692         GetPositionNumber(); // use FRC position number
5693
5694         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5695             SetCharTable(pieceToChar, appData.pieceToCharTable);
5696             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5697                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5698         }
5699
5700         j = seed%4;                 seed /= 4;
5701         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5702         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5703         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5704         j = seed%3 + (seed%3 >= j); seed /= 3;
5705         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5706         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5707         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5708         j = seed%3;                 seed /= 3;
5709         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5710         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5711         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5712         j = seed%2 + (seed%2 >= j); seed /= 2;
5713         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5714         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5715         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5716         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5717         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5718         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5719         put(board, exoPieces[0],    0, 0, ANY);
5720         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5721 }
5722
5723 void
5724 InitPosition(redraw)
5725      int redraw;
5726 {
5727     ChessSquare (* pieces)[BOARD_FILES];
5728     int i, j, pawnRow, overrule,
5729     oldx = gameInfo.boardWidth,
5730     oldy = gameInfo.boardHeight,
5731     oldh = gameInfo.holdingsWidth;
5732     static int oldv;
5733
5734     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5735
5736     /* [AS] Initialize pv info list [HGM] and game status */
5737     {
5738         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5739             pvInfoList[i].depth = 0;
5740             boards[i][EP_STATUS] = EP_NONE;
5741             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5742         }
5743
5744         initialRulePlies = 0; /* 50-move counter start */
5745
5746         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5747         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5748     }
5749
5750
5751     /* [HGM] logic here is completely changed. In stead of full positions */
5752     /* the initialized data only consist of the two backranks. The switch */
5753     /* selects which one we will use, which is than copied to the Board   */
5754     /* initialPosition, which for the rest is initialized by Pawns and    */
5755     /* empty squares. This initial position is then copied to boards[0],  */
5756     /* possibly after shuffling, so that it remains available.            */
5757
5758     gameInfo.holdingsWidth = 0; /* default board sizes */
5759     gameInfo.boardWidth    = 8;
5760     gameInfo.boardHeight   = 8;
5761     gameInfo.holdingsSize  = 0;
5762     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5763     for(i=0; i<BOARD_FILES-2; i++)
5764       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5765     initialPosition[EP_STATUS] = EP_NONE;
5766     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5767     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5768          SetCharTable(pieceNickName, appData.pieceNickNames);
5769     else SetCharTable(pieceNickName, "............");
5770     pieces = FIDEArray;
5771
5772     switch (gameInfo.variant) {
5773     case VariantFischeRandom:
5774       shuffleOpenings = TRUE;
5775     default:
5776       break;
5777     case VariantShatranj:
5778       pieces = ShatranjArray;
5779       nrCastlingRights = 0;
5780       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5781       break;
5782     case VariantMakruk:
5783       pieces = makrukArray;
5784       nrCastlingRights = 0;
5785       startedFromSetupPosition = TRUE;
5786       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5787       break;
5788     case VariantTwoKings:
5789       pieces = twoKingsArray;
5790       break;
5791     case VariantGrand:
5792       pieces = GrandArray;
5793       nrCastlingRights = 0;
5794       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5795       gameInfo.boardWidth = 10;
5796       gameInfo.boardHeight = 10;
5797       gameInfo.holdingsSize = 7;
5798       break;
5799     case VariantCapaRandom:
5800       shuffleOpenings = TRUE;
5801     case VariantCapablanca:
5802       pieces = CapablancaArray;
5803       gameInfo.boardWidth = 10;
5804       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5805       break;
5806     case VariantGothic:
5807       pieces = GothicArray;
5808       gameInfo.boardWidth = 10;
5809       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5810       break;
5811     case VariantSChess:
5812       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5813       gameInfo.holdingsSize = 7;
5814       break;
5815     case VariantJanus:
5816       pieces = JanusArray;
5817       gameInfo.boardWidth = 10;
5818       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5819       nrCastlingRights = 6;
5820         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5821         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5822         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5823         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5824         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5825         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5826       break;
5827     case VariantFalcon:
5828       pieces = FalconArray;
5829       gameInfo.boardWidth = 10;
5830       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5831       break;
5832     case VariantXiangqi:
5833       pieces = XiangqiArray;
5834       gameInfo.boardWidth  = 9;
5835       gameInfo.boardHeight = 10;
5836       nrCastlingRights = 0;
5837       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5838       break;
5839     case VariantShogi:
5840       pieces = ShogiArray;
5841       gameInfo.boardWidth  = 9;
5842       gameInfo.boardHeight = 9;
5843       gameInfo.holdingsSize = 7;
5844       nrCastlingRights = 0;
5845       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5846       break;
5847     case VariantCourier:
5848       pieces = CourierArray;
5849       gameInfo.boardWidth  = 12;
5850       nrCastlingRights = 0;
5851       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5852       break;
5853     case VariantKnightmate:
5854       pieces = KnightmateArray;
5855       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5856       break;
5857     case VariantSpartan:
5858       pieces = SpartanArray;
5859       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5860       break;
5861     case VariantFairy:
5862       pieces = fairyArray;
5863       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5864       break;
5865     case VariantGreat:
5866       pieces = GreatArray;
5867       gameInfo.boardWidth = 10;
5868       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5869       gameInfo.holdingsSize = 8;
5870       break;
5871     case VariantSuper:
5872       pieces = FIDEArray;
5873       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5874       gameInfo.holdingsSize = 8;
5875       startedFromSetupPosition = TRUE;
5876       break;
5877     case VariantCrazyhouse:
5878     case VariantBughouse:
5879       pieces = FIDEArray;
5880       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5881       gameInfo.holdingsSize = 5;
5882       break;
5883     case VariantWildCastle:
5884       pieces = FIDEArray;
5885       /* !!?shuffle with kings guaranteed to be on d or e file */
5886       shuffleOpenings = 1;
5887       break;
5888     case VariantNoCastle:
5889       pieces = FIDEArray;
5890       nrCastlingRights = 0;
5891       /* !!?unconstrained back-rank shuffle */
5892       shuffleOpenings = 1;
5893       break;
5894     }
5895
5896     overrule = 0;
5897     if(appData.NrFiles >= 0) {
5898         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5899         gameInfo.boardWidth = appData.NrFiles;
5900     }
5901     if(appData.NrRanks >= 0) {
5902         gameInfo.boardHeight = appData.NrRanks;
5903     }
5904     if(appData.holdingsSize >= 0) {
5905         i = appData.holdingsSize;
5906         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5907         gameInfo.holdingsSize = i;
5908     }
5909     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5910     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5911         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5912
5913     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5914     if(pawnRow < 1) pawnRow = 1;
5915     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5916
5917     /* User pieceToChar list overrules defaults */
5918     if(appData.pieceToCharTable != NULL)
5919         SetCharTable(pieceToChar, appData.pieceToCharTable);
5920
5921     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5922
5923         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5924             s = (ChessSquare) 0; /* account holding counts in guard band */
5925         for( i=0; i<BOARD_HEIGHT; i++ )
5926             initialPosition[i][j] = s;
5927
5928         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5929         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5930         initialPosition[pawnRow][j] = WhitePawn;
5931         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5932         if(gameInfo.variant == VariantXiangqi) {
5933             if(j&1) {
5934                 initialPosition[pawnRow][j] =
5935                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5936                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5937                    initialPosition[2][j] = WhiteCannon;
5938                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5939                 }
5940             }
5941         }
5942         if(gameInfo.variant == VariantGrand) {
5943             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5944                initialPosition[0][j] = WhiteRook;
5945                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5946             }
5947         }
5948         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5949     }
5950     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5951
5952             j=BOARD_LEFT+1;
5953             initialPosition[1][j] = WhiteBishop;
5954             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5955             j=BOARD_RGHT-2;
5956             initialPosition[1][j] = WhiteRook;
5957             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5958     }
5959
5960     if( nrCastlingRights == -1) {
5961         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5962         /*       This sets default castling rights from none to normal corners   */
5963         /* Variants with other castling rights must set them themselves above    */
5964         nrCastlingRights = 6;
5965
5966         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5967         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5968         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5969         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5970         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5971         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5972      }
5973
5974      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5975      if(gameInfo.variant == VariantGreat) { // promotion commoners
5976         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5977         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5978         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5979         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5980      }
5981      if( gameInfo.variant == VariantSChess ) {
5982       initialPosition[1][0] = BlackMarshall;
5983       initialPosition[2][0] = BlackAngel;
5984       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5985       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5986       initialPosition[1][1] = initialPosition[2][1] = 
5987       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5988      }
5989   if (appData.debugMode) {
5990     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5991   }
5992     if(shuffleOpenings) {
5993         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5994         startedFromSetupPosition = TRUE;
5995     }
5996     if(startedFromPositionFile) {
5997       /* [HGM] loadPos: use PositionFile for every new game */
5998       CopyBoard(initialPosition, filePosition);
5999       for(i=0; i<nrCastlingRights; i++)
6000           initialRights[i] = filePosition[CASTLING][i];
6001       startedFromSetupPosition = TRUE;
6002     }
6003
6004     CopyBoard(boards[0], initialPosition);
6005
6006     if(oldx != gameInfo.boardWidth ||
6007        oldy != gameInfo.boardHeight ||
6008        oldv != gameInfo.variant ||
6009        oldh != gameInfo.holdingsWidth
6010                                          )
6011             InitDrawingSizes(-2 ,0);
6012
6013     oldv = gameInfo.variant;
6014     if (redraw)
6015       DrawPosition(TRUE, boards[currentMove]);
6016 }
6017
6018 void
6019 SendBoard(cps, moveNum)
6020      ChessProgramState *cps;
6021      int moveNum;
6022 {
6023     char message[MSG_SIZ];
6024
6025     if (cps->useSetboard) {
6026       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6027       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6028       SendToProgram(message, cps);
6029       free(fen);
6030
6031     } else {
6032       ChessSquare *bp;
6033       int i, j, left=0, right=BOARD_WIDTH;
6034       /* Kludge to set black to move, avoiding the troublesome and now
6035        * deprecated "black" command.
6036        */
6037       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6038         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6039
6040       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6041
6042       SendToProgram("edit\n", cps);
6043       SendToProgram("#\n", cps);
6044       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6045         bp = &boards[moveNum][i][left];
6046         for (j = left; j < right; j++, bp++) {
6047           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6048           if ((int) *bp < (int) BlackPawn) {
6049             if(j == BOARD_RGHT+1)
6050                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6051             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6052             if(message[0] == '+' || message[0] == '~') {
6053               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6054                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6055                         AAA + j, ONE + i);
6056             }
6057             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6058                 message[1] = BOARD_RGHT   - 1 - j + '1';
6059                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6060             }
6061             SendToProgram(message, cps);
6062           }
6063         }
6064       }
6065
6066       SendToProgram("c\n", cps);
6067       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6068         bp = &boards[moveNum][i][left];
6069         for (j = left; j < right; j++, bp++) {
6070           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6071           if (((int) *bp != (int) EmptySquare)
6072               && ((int) *bp >= (int) BlackPawn)) {
6073             if(j == BOARD_LEFT-2)
6074                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6075             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6076                     AAA + j, ONE + i);
6077             if(message[0] == '+' || message[0] == '~') {
6078               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6079                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6080                         AAA + j, ONE + i);
6081             }
6082             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6083                 message[1] = BOARD_RGHT   - 1 - j + '1';
6084                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6085             }
6086             SendToProgram(message, cps);
6087           }
6088         }
6089       }
6090
6091       SendToProgram(".\n", cps);
6092     }
6093     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6094 }
6095
6096 ChessSquare
6097 DefaultPromoChoice(int white)
6098 {
6099     ChessSquare result;
6100     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6101         result = WhiteFerz; // no choice
6102     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6103         result= WhiteKing; // in Suicide Q is the last thing we want
6104     else if(gameInfo.variant == VariantSpartan)
6105         result = white ? WhiteQueen : WhiteAngel;
6106     else result = WhiteQueen;
6107     if(!white) result = WHITE_TO_BLACK result;
6108     return result;
6109 }
6110
6111 static int autoQueen; // [HGM] oneclick
6112
6113 int
6114 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6115 {
6116     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6117     /* [HGM] add Shogi promotions */
6118     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6119     ChessSquare piece;
6120     ChessMove moveType;
6121     Boolean premove;
6122
6123     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6124     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6125
6126     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6127       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6128         return FALSE;
6129
6130     piece = boards[currentMove][fromY][fromX];
6131     if(gameInfo.variant == VariantShogi) {
6132         promotionZoneSize = BOARD_HEIGHT/3;
6133         highestPromotingPiece = (int)WhiteFerz;
6134     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6135         promotionZoneSize = 3;
6136     }
6137
6138     // Treat Lance as Pawn when it is not representing Amazon
6139     if(gameInfo.variant != VariantSuper) {
6140         if(piece == WhiteLance) piece = WhitePawn; else
6141         if(piece == BlackLance) piece = BlackPawn;
6142     }
6143
6144     // next weed out all moves that do not touch the promotion zone at all
6145     if((int)piece >= BlackPawn) {
6146         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6147              return FALSE;
6148         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6149     } else {
6150         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6151            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6152     }
6153
6154     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6155
6156     // weed out mandatory Shogi promotions
6157     if(gameInfo.variant == VariantShogi) {
6158         if(piece >= BlackPawn) {
6159             if(toY == 0 && piece == BlackPawn ||
6160                toY == 0 && piece == BlackQueen ||
6161                toY <= 1 && piece == BlackKnight) {
6162                 *promoChoice = '+';
6163                 return FALSE;
6164             }
6165         } else {
6166             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6167                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6168                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6169                 *promoChoice = '+';
6170                 return FALSE;
6171             }
6172         }
6173     }
6174
6175     // weed out obviously illegal Pawn moves
6176     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6177         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6178         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6179         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6180         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6181         // note we are not allowed to test for valid (non-)capture, due to premove
6182     }
6183
6184     // we either have a choice what to promote to, or (in Shogi) whether to promote
6185     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6186         *promoChoice = PieceToChar(BlackFerz);  // no choice
6187         return FALSE;
6188     }
6189     // no sense asking what we must promote to if it is going to explode...
6190     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6191         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6192         return FALSE;
6193     }
6194     // give caller the default choice even if we will not make it
6195     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6196     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6197     if(        sweepSelect && gameInfo.variant != VariantGreat
6198                            && gameInfo.variant != VariantGrand
6199                            && gameInfo.variant != VariantSuper) return FALSE;
6200     if(autoQueen) return FALSE; // predetermined
6201
6202     // suppress promotion popup on illegal moves that are not premoves
6203     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6204               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6205     if(appData.testLegality && !premove) {
6206         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6207                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6208         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6209             return FALSE;
6210     }
6211
6212     return TRUE;
6213 }
6214
6215 int
6216 InPalace(row, column)
6217      int row, column;
6218 {   /* [HGM] for Xiangqi */
6219     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6220          column < (BOARD_WIDTH + 4)/2 &&
6221          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6222     return FALSE;
6223 }
6224
6225 int
6226 PieceForSquare (x, y)
6227      int x;
6228      int y;
6229 {
6230   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6231      return -1;
6232   else
6233      return boards[currentMove][y][x];
6234 }
6235
6236 int
6237 OKToStartUserMove(x, y)
6238      int x, y;
6239 {
6240     ChessSquare from_piece;
6241     int white_piece;
6242
6243     if (matchMode) return FALSE;
6244     if (gameMode == EditPosition) return TRUE;
6245
6246     if (x >= 0 && y >= 0)
6247       from_piece = boards[currentMove][y][x];
6248     else
6249       from_piece = EmptySquare;
6250
6251     if (from_piece == EmptySquare) return FALSE;
6252
6253     white_piece = (int)from_piece >= (int)WhitePawn &&
6254       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6255
6256     switch (gameMode) {
6257       case AnalyzeFile:
6258       case TwoMachinesPlay:
6259       case EndOfGame:
6260         return FALSE;
6261
6262       case IcsObserving:
6263       case IcsIdle:
6264         return FALSE;
6265
6266       case MachinePlaysWhite:
6267       case IcsPlayingBlack:
6268         if (appData.zippyPlay) return FALSE;
6269         if (white_piece) {
6270             DisplayMoveError(_("You are playing Black"));
6271             return FALSE;
6272         }
6273         break;
6274
6275       case MachinePlaysBlack:
6276       case IcsPlayingWhite:
6277         if (appData.zippyPlay) return FALSE;
6278         if (!white_piece) {
6279             DisplayMoveError(_("You are playing White"));
6280             return FALSE;
6281         }
6282         break;
6283
6284       case PlayFromGameFile:
6285             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6286       case EditGame:
6287         if (!white_piece && WhiteOnMove(currentMove)) {
6288             DisplayMoveError(_("It is White's turn"));
6289             return FALSE;
6290         }
6291         if (white_piece && !WhiteOnMove(currentMove)) {
6292             DisplayMoveError(_("It is Black's turn"));
6293             return FALSE;
6294         }
6295         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6296             /* Editing correspondence game history */
6297             /* Could disallow this or prompt for confirmation */
6298             cmailOldMove = -1;
6299         }
6300         break;
6301
6302       case BeginningOfGame:
6303         if (appData.icsActive) return FALSE;
6304         if (!appData.noChessProgram) {
6305             if (!white_piece) {
6306                 DisplayMoveError(_("You are playing White"));
6307                 return FALSE;
6308             }
6309         }
6310         break;
6311
6312       case Training:
6313         if (!white_piece && WhiteOnMove(currentMove)) {
6314             DisplayMoveError(_("It is White's turn"));
6315             return FALSE;
6316         }
6317         if (white_piece && !WhiteOnMove(currentMove)) {
6318             DisplayMoveError(_("It is Black's turn"));
6319             return FALSE;
6320         }
6321         break;
6322
6323       default:
6324       case IcsExamining:
6325         break;
6326     }
6327     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6328         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6329         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6330         && gameMode != AnalyzeFile && gameMode != Training) {
6331         DisplayMoveError(_("Displayed position is not current"));
6332         return FALSE;
6333     }
6334     return TRUE;
6335 }
6336
6337 Boolean
6338 OnlyMove(int *x, int *y, Boolean captures) {
6339     DisambiguateClosure cl;
6340     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6341     switch(gameMode) {
6342       case MachinePlaysBlack:
6343       case IcsPlayingWhite:
6344       case BeginningOfGame:
6345         if(!WhiteOnMove(currentMove)) return FALSE;
6346         break;
6347       case MachinePlaysWhite:
6348       case IcsPlayingBlack:
6349         if(WhiteOnMove(currentMove)) return FALSE;
6350         break;
6351       case EditGame:
6352         break;
6353       default:
6354         return FALSE;
6355     }
6356     cl.pieceIn = EmptySquare;
6357     cl.rfIn = *y;
6358     cl.ffIn = *x;
6359     cl.rtIn = -1;
6360     cl.ftIn = -1;
6361     cl.promoCharIn = NULLCHAR;
6362     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6363     if( cl.kind == NormalMove ||
6364         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6365         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6366         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6367       fromX = cl.ff;
6368       fromY = cl.rf;
6369       *x = cl.ft;
6370       *y = cl.rt;
6371       return TRUE;
6372     }
6373     if(cl.kind != ImpossibleMove) return FALSE;
6374     cl.pieceIn = EmptySquare;
6375     cl.rfIn = -1;
6376     cl.ffIn = -1;
6377     cl.rtIn = *y;
6378     cl.ftIn = *x;
6379     cl.promoCharIn = NULLCHAR;
6380     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6381     if( cl.kind == NormalMove ||
6382         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6383         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6384         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6385       fromX = cl.ff;
6386       fromY = cl.rf;
6387       *x = cl.ft;
6388       *y = cl.rt;
6389       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6390       return TRUE;
6391     }
6392     return FALSE;
6393 }
6394
6395 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6396 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6397 int lastLoadGameUseList = FALSE;
6398 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6399 ChessMove lastLoadGameStart = EndOfFile;
6400
6401 void
6402 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6403      int fromX, fromY, toX, toY;
6404      int promoChar;
6405 {
6406     ChessMove moveType;
6407     ChessSquare pdown, pup;
6408
6409     /* Check if the user is playing in turn.  This is complicated because we
6410        let the user "pick up" a piece before it is his turn.  So the piece he
6411        tried to pick up may have been captured by the time he puts it down!
6412        Therefore we use the color the user is supposed to be playing in this
6413        test, not the color of the piece that is currently on the starting
6414        square---except in EditGame mode, where the user is playing both
6415        sides; fortunately there the capture race can't happen.  (It can
6416        now happen in IcsExamining mode, but that's just too bad.  The user
6417        will get a somewhat confusing message in that case.)
6418        */
6419
6420     switch (gameMode) {
6421       case AnalyzeFile:
6422       case TwoMachinesPlay:
6423       case EndOfGame:
6424       case IcsObserving:
6425       case IcsIdle:
6426         /* We switched into a game mode where moves are not accepted,
6427            perhaps while the mouse button was down. */
6428         return;
6429
6430       case MachinePlaysWhite:
6431         /* User is moving for Black */
6432         if (WhiteOnMove(currentMove)) {
6433             DisplayMoveError(_("It is White's turn"));
6434             return;
6435         }
6436         break;
6437
6438       case MachinePlaysBlack:
6439         /* User is moving for White */
6440         if (!WhiteOnMove(currentMove)) {
6441             DisplayMoveError(_("It is Black's turn"));
6442             return;
6443         }
6444         break;
6445
6446       case PlayFromGameFile:
6447             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6448       case EditGame:
6449       case IcsExamining:
6450       case BeginningOfGame:
6451       case AnalyzeMode:
6452       case Training:
6453         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6454         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6455             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6456             /* User is moving for Black */
6457             if (WhiteOnMove(currentMove)) {
6458                 DisplayMoveError(_("It is White's turn"));
6459                 return;
6460             }
6461         } else {
6462             /* User is moving for White */
6463             if (!WhiteOnMove(currentMove)) {
6464                 DisplayMoveError(_("It is Black's turn"));
6465                 return;
6466             }
6467         }
6468         break;
6469
6470       case IcsPlayingBlack:
6471         /* User is moving for Black */
6472         if (WhiteOnMove(currentMove)) {
6473             if (!appData.premove) {
6474                 DisplayMoveError(_("It is White's turn"));
6475             } else if (toX >= 0 && toY >= 0) {
6476                 premoveToX = toX;
6477                 premoveToY = toY;
6478                 premoveFromX = fromX;
6479                 premoveFromY = fromY;
6480                 premovePromoChar = promoChar;
6481                 gotPremove = 1;
6482                 if (appData.debugMode)
6483                     fprintf(debugFP, "Got premove: fromX %d,"
6484                             "fromY %d, toX %d, toY %d\n",
6485                             fromX, fromY, toX, toY);
6486             }
6487             return;
6488         }
6489         break;
6490
6491       case IcsPlayingWhite:
6492         /* User is moving for White */
6493         if (!WhiteOnMove(currentMove)) {
6494             if (!appData.premove) {
6495                 DisplayMoveError(_("It is Black's turn"));
6496             } else if (toX >= 0 && toY >= 0) {
6497                 premoveToX = toX;
6498                 premoveToY = toY;
6499                 premoveFromX = fromX;
6500                 premoveFromY = fromY;
6501                 premovePromoChar = promoChar;
6502                 gotPremove = 1;
6503                 if (appData.debugMode)
6504                     fprintf(debugFP, "Got premove: fromX %d,"
6505                             "fromY %d, toX %d, toY %d\n",
6506                             fromX, fromY, toX, toY);
6507             }
6508             return;
6509         }
6510         break;
6511
6512       default:
6513         break;
6514
6515       case EditPosition:
6516         /* EditPosition, empty square, or different color piece;
6517            click-click move is possible */
6518         if (toX == -2 || toY == -2) {
6519             boards[0][fromY][fromX] = EmptySquare;
6520             DrawPosition(FALSE, boards[currentMove]);
6521             return;
6522         } else if (toX >= 0 && toY >= 0) {
6523             boards[0][toY][toX] = boards[0][fromY][fromX];
6524             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6525                 if(boards[0][fromY][0] != EmptySquare) {
6526                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6527                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6528                 }
6529             } else
6530             if(fromX == BOARD_RGHT+1) {
6531                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6532                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6533                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6534                 }
6535             } else
6536             boards[0][fromY][fromX] = EmptySquare;
6537             DrawPosition(FALSE, boards[currentMove]);
6538             return;
6539         }
6540         return;
6541     }
6542
6543     if(toX < 0 || toY < 0) return;
6544     pdown = boards[currentMove][fromY][fromX];
6545     pup = boards[currentMove][toY][toX];
6546
6547     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6548     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6549          if( pup != EmptySquare ) return;
6550          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6551            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6552                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6553            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6554            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6555            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6556            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6557          fromY = DROP_RANK;
6558     }
6559
6560     /* [HGM] always test for legality, to get promotion info */
6561     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6562                                          fromY, fromX, toY, toX, promoChar);
6563
6564     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6565
6566     /* [HGM] but possibly ignore an IllegalMove result */
6567     if (appData.testLegality) {
6568         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6569             DisplayMoveError(_("Illegal move"));
6570             return;
6571         }
6572     }
6573
6574     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6575 }
6576
6577 /* Common tail of UserMoveEvent and DropMenuEvent */
6578 int
6579 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6580      ChessMove moveType;
6581      int fromX, fromY, toX, toY;
6582      /*char*/int promoChar;
6583 {
6584     char *bookHit = 0;
6585
6586     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6587         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6588         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6589         if(WhiteOnMove(currentMove)) {
6590             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6591         } else {
6592             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6593         }
6594     }
6595
6596     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6597        move type in caller when we know the move is a legal promotion */
6598     if(moveType == NormalMove && promoChar)
6599         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6600
6601     /* [HGM] <popupFix> The following if has been moved here from
6602        UserMoveEvent(). Because it seemed to belong here (why not allow
6603        piece drops in training games?), and because it can only be
6604        performed after it is known to what we promote. */
6605     if (gameMode == Training) {
6606       /* compare the move played on the board to the next move in the
6607        * game. If they match, display the move and the opponent's response.
6608        * If they don't match, display an error message.
6609        */
6610       int saveAnimate;
6611       Board testBoard;
6612       CopyBoard(testBoard, boards[currentMove]);
6613       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6614
6615       if (CompareBoards(testBoard, boards[currentMove+1])) {
6616         ForwardInner(currentMove+1);
6617
6618         /* Autoplay the opponent's response.
6619          * if appData.animate was TRUE when Training mode was entered,
6620          * the response will be animated.
6621          */
6622         saveAnimate = appData.animate;
6623         appData.animate = animateTraining;
6624         ForwardInner(currentMove+1);
6625         appData.animate = saveAnimate;
6626
6627         /* check for the end of the game */
6628         if (currentMove >= forwardMostMove) {
6629           gameMode = PlayFromGameFile;
6630           ModeHighlight();
6631           SetTrainingModeOff();
6632           DisplayInformation(_("End of game"));
6633         }
6634       } else {
6635         DisplayError(_("Incorrect move"), 0);
6636       }
6637       return 1;
6638     }
6639
6640   /* Ok, now we know that the move is good, so we can kill
6641      the previous line in Analysis Mode */
6642   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6643                                 && currentMove < forwardMostMove) {
6644     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6645     else forwardMostMove = currentMove;
6646   }
6647
6648   /* If we need the chess program but it's dead, restart it */
6649   ResurrectChessProgram();
6650
6651   /* A user move restarts a paused game*/
6652   if (pausing)
6653     PauseEvent();
6654
6655   thinkOutput[0] = NULLCHAR;
6656
6657   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6658
6659   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6660     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6661     return 1;
6662   }
6663
6664   if (gameMode == BeginningOfGame) {
6665     if (appData.noChessProgram) {
6666       gameMode = EditGame;
6667       SetGameInfo();
6668     } else {
6669       char buf[MSG_SIZ];
6670       gameMode = MachinePlaysBlack;
6671       StartClocks();
6672       SetGameInfo();
6673       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
6674       DisplayTitle(buf);
6675       if (first.sendName) {
6676         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6677         SendToProgram(buf, &first);
6678       }
6679       StartClocks();
6680     }
6681     ModeHighlight();
6682   }
6683
6684   /* Relay move to ICS or chess engine */
6685   if (appData.icsActive) {
6686     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6687         gameMode == IcsExamining) {
6688       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6689         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6690         SendToICS("draw ");
6691         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6692       }
6693       // also send plain move, in case ICS does not understand atomic claims
6694       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6695       ics_user_moved = 1;
6696     }
6697   } else {
6698     if (first.sendTime && (gameMode == BeginningOfGame ||
6699                            gameMode == MachinePlaysWhite ||
6700                            gameMode == MachinePlaysBlack)) {
6701       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6702     }
6703     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6704          // [HGM] book: if program might be playing, let it use book
6705         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6706         first.maybeThinking = TRUE;
6707     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6708         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6709         SendBoard(&first, currentMove+1);
6710     } else SendMoveToProgram(forwardMostMove-1, &first);
6711     if (currentMove == cmailOldMove + 1) {
6712       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6713     }
6714   }
6715
6716   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6717
6718   switch (gameMode) {
6719   case EditGame:
6720     if(appData.testLegality)
6721     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6722     case MT_NONE:
6723     case MT_CHECK:
6724       break;
6725     case MT_CHECKMATE:
6726     case MT_STAINMATE:
6727       if (WhiteOnMove(currentMove)) {
6728         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6729       } else {
6730         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6731       }
6732       break;
6733     case MT_STALEMATE:
6734       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6735       break;
6736     }
6737     break;
6738
6739   case MachinePlaysBlack:
6740   case MachinePlaysWhite:
6741     /* disable certain menu options while machine is thinking */
6742     SetMachineThinkingEnables();
6743     break;
6744
6745   default:
6746     break;
6747   }
6748
6749   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6750   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6751
6752   if(bookHit) { // [HGM] book: simulate book reply
6753         static char bookMove[MSG_SIZ]; // a bit generous?
6754
6755         programStats.nodes = programStats.depth = programStats.time =
6756         programStats.score = programStats.got_only_move = 0;
6757         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6758
6759         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6760         strcat(bookMove, bookHit);
6761         HandleMachineMove(bookMove, &first);
6762   }
6763   return 1;
6764 }
6765
6766 void
6767 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6768      Board board;
6769      int flags;
6770      ChessMove kind;
6771      int rf, ff, rt, ft;
6772      VOIDSTAR closure;
6773 {
6774     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6775     Markers *m = (Markers *) closure;
6776     if(rf == fromY && ff == fromX)
6777         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6778                          || kind == WhiteCapturesEnPassant
6779                          || kind == BlackCapturesEnPassant);
6780     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6781 }
6782
6783 void
6784 MarkTargetSquares(int clear)
6785 {
6786   int x, y;
6787   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6788      !appData.testLegality || gameMode == EditPosition) return;
6789   if(clear) {
6790     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6791   } else {
6792     int capt = 0;
6793     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6794     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6795       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6796       if(capt)
6797       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6798     }
6799   }
6800   DrawPosition(TRUE, NULL);
6801 }
6802
6803 int
6804 Explode(Board board, int fromX, int fromY, int toX, int toY)
6805 {
6806     if(gameInfo.variant == VariantAtomic &&
6807        (board[toY][toX] != EmptySquare ||                     // capture?
6808         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6809                          board[fromY][fromX] == BlackPawn   )
6810       )) {
6811         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6812         return TRUE;
6813     }
6814     return FALSE;
6815 }
6816
6817 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6818
6819 int CanPromote(ChessSquare piece, int y)
6820 {
6821         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6822         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6823         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6824            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6825            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6826                                                   gameInfo.variant == VariantMakruk) return FALSE;
6827         return (piece == BlackPawn && y == 1 ||
6828                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6829                 piece == BlackLance && y == 1 ||
6830                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6831 }
6832
6833 void LeftClick(ClickType clickType, int xPix, int yPix)
6834 {
6835     int x, y;
6836     Boolean saveAnimate;
6837     static int second = 0, promotionChoice = 0, clearFlag = 0;
6838     char promoChoice = NULLCHAR;
6839     ChessSquare piece;
6840
6841     if(appData.seekGraph && appData.icsActive && loggedOn &&
6842         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6843         SeekGraphClick(clickType, xPix, yPix, 0);
6844         return;
6845     }
6846
6847     if (clickType == Press) ErrorPopDown();
6848
6849     x = EventToSquare(xPix, BOARD_WIDTH);
6850     y = EventToSquare(yPix, BOARD_HEIGHT);
6851     if (!flipView && y >= 0) {
6852         y = BOARD_HEIGHT - 1 - y;
6853     }
6854     if (flipView && x >= 0) {
6855         x = BOARD_WIDTH - 1 - x;
6856     }
6857
6858     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6859         defaultPromoChoice = promoSweep;
6860         promoSweep = EmptySquare;   // terminate sweep
6861         promoDefaultAltered = TRUE;
6862         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6863     }
6864
6865     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6866         if(clickType == Release) return; // ignore upclick of click-click destination
6867         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6868         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6869         if(gameInfo.holdingsWidth &&
6870                 (WhiteOnMove(currentMove)
6871                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6872                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6873             // click in right holdings, for determining promotion piece
6874             ChessSquare p = boards[currentMove][y][x];
6875             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6876             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6877             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6878                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6879                 fromX = fromY = -1;
6880                 return;
6881             }
6882         }
6883         DrawPosition(FALSE, boards[currentMove]);
6884         return;
6885     }
6886
6887     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6888     if(clickType == Press
6889             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6890               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6891               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6892         return;
6893
6894     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6895         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6896
6897     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6898         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6899                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6900         defaultPromoChoice = DefaultPromoChoice(side);
6901     }
6902
6903     autoQueen = appData.alwaysPromoteToQueen;
6904
6905     if (fromX == -1) {
6906       int originalY = y;
6907       gatingPiece = EmptySquare;
6908       if (clickType != Press) {
6909         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6910             DragPieceEnd(xPix, yPix); dragging = 0;
6911             DrawPosition(FALSE, NULL);
6912         }
6913         return;
6914       }
6915       fromX = x; fromY = y; toX = toY = -1;
6916       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6917          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6918          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6919             /* First square */
6920             if (OKToStartUserMove(fromX, fromY)) {
6921                 second = 0;
6922                 MarkTargetSquares(0);
6923                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6924                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6925                     promoSweep = defaultPromoChoice;
6926                     selectFlag = 0; lastX = xPix; lastY = yPix;
6927                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6928                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6929                 }
6930                 if (appData.highlightDragging) {
6931                     SetHighlights(fromX, fromY, -1, -1);
6932                 }
6933             } else fromX = fromY = -1;
6934             return;
6935         }
6936     }
6937
6938     /* fromX != -1 */
6939     if (clickType == Press && gameMode != EditPosition) {
6940         ChessSquare fromP;
6941         ChessSquare toP;
6942         int frc;
6943
6944         // ignore off-board to clicks
6945         if(y < 0 || x < 0) return;
6946
6947         /* Check if clicking again on the same color piece */
6948         fromP = boards[currentMove][fromY][fromX];
6949         toP = boards[currentMove][y][x];
6950         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6951         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6952              WhitePawn <= toP && toP <= WhiteKing &&
6953              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6954              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6955             (BlackPawn <= fromP && fromP <= BlackKing &&
6956              BlackPawn <= toP && toP <= BlackKing &&
6957              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6958              !(fromP == BlackKing && toP == BlackRook && frc))) {
6959             /* Clicked again on same color piece -- changed his mind */
6960             second = (x == fromX && y == fromY);
6961             promoDefaultAltered = FALSE;
6962             MarkTargetSquares(1);
6963            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6964             if (appData.highlightDragging) {
6965                 SetHighlights(x, y, -1, -1);
6966             } else {
6967                 ClearHighlights();
6968             }
6969             if (OKToStartUserMove(x, y)) {
6970                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6971                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6972                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6973                  gatingPiece = boards[currentMove][fromY][fromX];
6974                 else gatingPiece = EmptySquare;
6975                 fromX = x;
6976                 fromY = y; dragging = 1;
6977                 MarkTargetSquares(0);
6978                 DragPieceBegin(xPix, yPix, FALSE);
6979                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6980                     promoSweep = defaultPromoChoice;
6981                     selectFlag = 0; lastX = xPix; lastY = yPix;
6982                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6983                 }
6984             }
6985            }
6986            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6987            second = FALSE; 
6988         }
6989         // ignore clicks on holdings
6990         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6991     }
6992
6993     if (clickType == Release && x == fromX && y == fromY) {
6994         DragPieceEnd(xPix, yPix); dragging = 0;
6995         if(clearFlag) {
6996             // a deferred attempt to click-click move an empty square on top of a piece
6997             boards[currentMove][y][x] = EmptySquare;
6998             ClearHighlights();
6999             DrawPosition(FALSE, boards[currentMove]);
7000             fromX = fromY = -1; clearFlag = 0;
7001             return;
7002         }
7003         if (appData.animateDragging) {
7004             /* Undo animation damage if any */
7005             DrawPosition(FALSE, NULL);
7006         }
7007         if (second) {
7008             /* Second up/down in same square; just abort move */
7009             second = 0;
7010             fromX = fromY = -1;
7011             gatingPiece = EmptySquare;
7012             ClearHighlights();
7013             gotPremove = 0;
7014             ClearPremoveHighlights();
7015         } else {
7016             /* First upclick in same square; start click-click mode */
7017             SetHighlights(x, y, -1, -1);
7018         }
7019         return;
7020     }
7021
7022     clearFlag = 0;
7023
7024     /* we now have a different from- and (possibly off-board) to-square */
7025     /* Completed move */
7026     toX = x;
7027     toY = y;
7028     saveAnimate = appData.animate;
7029     MarkTargetSquares(1);
7030     if (clickType == Press) {
7031         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
7032             // must be Edit Position mode with empty-square selected
7033             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
7034             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
7035             return;
7036         }
7037         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7038             ChessSquare piece = boards[currentMove][fromY][fromX];
7039             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7040             promoSweep = defaultPromoChoice;
7041             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7042             selectFlag = 0; lastX = xPix; lastY = yPix;
7043             Sweep(0); // Pawn that is going to promote: preview promotion piece
7044             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7045             DrawPosition(FALSE, boards[currentMove]);
7046             return;
7047         }
7048         /* Finish clickclick move */
7049         if (appData.animate || appData.highlightLastMove) {
7050             SetHighlights(fromX, fromY, toX, toY);
7051         } else {
7052             ClearHighlights();
7053         }
7054     } else {
7055         /* Finish drag move */
7056         if (appData.highlightLastMove) {
7057             SetHighlights(fromX, fromY, toX, toY);
7058         } else {
7059             ClearHighlights();
7060         }
7061         DragPieceEnd(xPix, yPix); dragging = 0;
7062         /* Don't animate move and drag both */
7063         appData.animate = FALSE;
7064     }
7065
7066     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7067     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7068         ChessSquare piece = boards[currentMove][fromY][fromX];
7069         if(gameMode == EditPosition && piece != EmptySquare &&
7070            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7071             int n;
7072
7073             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7074                 n = PieceToNumber(piece - (int)BlackPawn);
7075                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7076                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7077                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7078             } else
7079             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7080                 n = PieceToNumber(piece);
7081                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7082                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7083                 boards[currentMove][n][BOARD_WIDTH-2]++;
7084             }
7085             boards[currentMove][fromY][fromX] = EmptySquare;
7086         }
7087         ClearHighlights();
7088         fromX = fromY = -1;
7089         DrawPosition(TRUE, boards[currentMove]);
7090         return;
7091     }
7092
7093     // off-board moves should not be highlighted
7094     if(x < 0 || y < 0) ClearHighlights();
7095
7096     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7097
7098     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7099         SetHighlights(fromX, fromY, toX, toY);
7100         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7101             // [HGM] super: promotion to captured piece selected from holdings
7102             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7103             promotionChoice = TRUE;
7104             // kludge follows to temporarily execute move on display, without promoting yet
7105             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7106             boards[currentMove][toY][toX] = p;
7107             DrawPosition(FALSE, boards[currentMove]);
7108             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7109             boards[currentMove][toY][toX] = q;
7110             DisplayMessage("Click in holdings to choose piece", "");
7111             return;
7112         }
7113         PromotionPopUp();
7114     } else {
7115         int oldMove = currentMove;
7116         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7117         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7118         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7119         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7120            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7121             DrawPosition(TRUE, boards[currentMove]);
7122         fromX = fromY = -1;
7123     }
7124     appData.animate = saveAnimate;
7125     if (appData.animate || appData.animateDragging) {
7126         /* Undo animation damage if needed */
7127         DrawPosition(FALSE, NULL);
7128     }
7129 }
7130
7131 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7132 {   // front-end-free part taken out of PieceMenuPopup
7133     int whichMenu; int xSqr, ySqr;
7134
7135     if(seekGraphUp) { // [HGM] seekgraph
7136         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7137         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7138         return -2;
7139     }
7140
7141     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7142          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7143         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7144         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7145         if(action == Press)   {
7146             originalFlip = flipView;
7147             flipView = !flipView; // temporarily flip board to see game from partners perspective
7148             DrawPosition(TRUE, partnerBoard);
7149             DisplayMessage(partnerStatus, "");
7150             partnerUp = TRUE;
7151         } else if(action == Release) {
7152             flipView = originalFlip;
7153             DrawPosition(TRUE, boards[currentMove]);
7154             partnerUp = FALSE;
7155         }
7156         return -2;
7157     }
7158
7159     xSqr = EventToSquare(x, BOARD_WIDTH);
7160     ySqr = EventToSquare(y, BOARD_HEIGHT);
7161     if (action == Release) {
7162         if(pieceSweep != EmptySquare) {
7163             EditPositionMenuEvent(pieceSweep, toX, toY);
7164             pieceSweep = EmptySquare;
7165         } else UnLoadPV(); // [HGM] pv
7166     }
7167     if (action != Press) return -2; // return code to be ignored
7168     switch (gameMode) {
7169       case IcsExamining:
7170         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7171       case EditPosition:
7172         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7173         if (xSqr < 0 || ySqr < 0) return -1;
7174         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7175         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7176         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7177         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7178         NextPiece(0);
7179         return 2; // grab
7180       case IcsObserving:
7181         if(!appData.icsEngineAnalyze) return -1;
7182       case IcsPlayingWhite:
7183       case IcsPlayingBlack:
7184         if(!appData.zippyPlay) goto noZip;
7185       case AnalyzeMode:
7186       case AnalyzeFile:
7187       case MachinePlaysWhite:
7188       case MachinePlaysBlack:
7189       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7190         if (!appData.dropMenu) {
7191           LoadPV(x, y);
7192           return 2; // flag front-end to grab mouse events
7193         }
7194         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7195            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7196       case EditGame:
7197       noZip:
7198         if (xSqr < 0 || ySqr < 0) return -1;
7199         if (!appData.dropMenu || appData.testLegality &&
7200             gameInfo.variant != VariantBughouse &&
7201             gameInfo.variant != VariantCrazyhouse) return -1;
7202         whichMenu = 1; // drop menu
7203         break;
7204       default:
7205         return -1;
7206     }
7207
7208     if (((*fromX = xSqr) < 0) ||
7209         ((*fromY = ySqr) < 0)) {
7210         *fromX = *fromY = -1;
7211         return -1;
7212     }
7213     if (flipView)
7214       *fromX = BOARD_WIDTH - 1 - *fromX;
7215     else
7216       *fromY = BOARD_HEIGHT - 1 - *fromY;
7217
7218     return whichMenu;
7219 }
7220
7221 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7222 {
7223 //    char * hint = lastHint;
7224     FrontEndProgramStats stats;
7225
7226     stats.which = cps == &first ? 0 : 1;
7227     stats.depth = cpstats->depth;
7228     stats.nodes = cpstats->nodes;
7229     stats.score = cpstats->score;
7230     stats.time = cpstats->time;
7231     stats.pv = cpstats->movelist;
7232     stats.hint = lastHint;
7233     stats.an_move_index = 0;
7234     stats.an_move_count = 0;
7235
7236     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7237         stats.hint = cpstats->move_name;
7238         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7239         stats.an_move_count = cpstats->nr_moves;
7240     }
7241
7242     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
7243
7244     SetProgramStats( &stats );
7245 }
7246
7247 void
7248 ClearEngineOutputPane(int which)
7249 {
7250     static FrontEndProgramStats dummyStats;
7251     dummyStats.which = which;
7252     dummyStats.pv = "#";
7253     SetProgramStats( &dummyStats );
7254 }
7255
7256 #define MAXPLAYERS 500
7257
7258 char *
7259 TourneyStandings(int display)
7260 {
7261     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7262     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7263     char result, *p, *names[MAXPLAYERS];
7264
7265     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7266         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7267     names[0] = p = strdup(appData.participants);
7268     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7269
7270     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7271
7272     while(result = appData.results[nr]) {
7273         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7274         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7275         wScore = bScore = 0;
7276         switch(result) {
7277           case '+': wScore = 2; break;
7278           case '-': bScore = 2; break;
7279           case '=': wScore = bScore = 1; break;
7280           case ' ':
7281           case '*': return strdup("busy"); // tourney not finished
7282         }
7283         score[w] += wScore;
7284         score[b] += bScore;
7285         games[w]++;
7286         games[b]++;
7287         nr++;
7288     }
7289     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7290     for(w=0; w<nPlayers; w++) {
7291         bScore = -1;
7292         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7293         ranking[w] = b; points[w] = bScore; score[b] = -2;
7294     }
7295     p = malloc(nPlayers*34+1);
7296     for(w=0; w<nPlayers && w<display; w++)
7297         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7298     free(names[0]);
7299     return p;
7300 }
7301
7302 void
7303 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7304 {       // count all piece types
7305         int p, f, r;
7306         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7307         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7308         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7309                 p = board[r][f];
7310                 pCnt[p]++;
7311                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7312                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7313                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7314                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7315                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7316                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7317         }
7318 }
7319
7320 int
7321 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7322 {
7323         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7324         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7325
7326         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7327         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7328         if(myPawns == 2 && nMine == 3) // KPP
7329             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7330         if(myPawns == 1 && nMine == 2) // KP
7331             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7332         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7333             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7334         if(myPawns) return FALSE;
7335         if(pCnt[WhiteRook+side])
7336             return pCnt[BlackRook-side] ||
7337                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7338                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7339                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7340         if(pCnt[WhiteCannon+side]) {
7341             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7342             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7343         }
7344         if(pCnt[WhiteKnight+side])
7345             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7346         return FALSE;
7347 }
7348
7349 int
7350 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7351 {
7352         VariantClass v = gameInfo.variant;
7353
7354         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7355         if(v == VariantShatranj) return TRUE; // always winnable through baring
7356         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7357         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7358
7359         if(v == VariantXiangqi) {
7360                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7361
7362                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7363                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7364                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7365                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7366                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7367                 if(stale) // we have at least one last-rank P plus perhaps C
7368                     return majors // KPKX
7369                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7370                 else // KCA*E*
7371                     return pCnt[WhiteFerz+side] // KCAK
7372                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7373                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7374                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7375
7376         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7377                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7378
7379                 if(nMine == 1) return FALSE; // bare King
7380                 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
7381                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7382                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7383                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7384                 if(pCnt[WhiteKnight+side])
7385                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7386                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7387                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7388                 if(nBishops)
7389                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7390                 if(pCnt[WhiteAlfil+side])
7391                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7392                 if(pCnt[WhiteWazir+side])
7393                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7394         }
7395
7396         return TRUE;
7397 }
7398
7399 int
7400 CompareWithRights(Board b1, Board b2)
7401 {
7402     int rights = 0;
7403     if(!CompareBoards(b1, b2)) return FALSE;
7404     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7405     /* compare castling rights */
7406     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7407            rights++; /* King lost rights, while rook still had them */
7408     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7409         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7410            rights++; /* but at least one rook lost them */
7411     }
7412     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7413            rights++;
7414     if( b1[CASTLING][5] != NoRights ) {
7415         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7416            rights++;
7417     }
7418     return rights == 0;
7419 }
7420
7421 int
7422 Adjudicate(ChessProgramState *cps)
7423 {       // [HGM] some adjudications useful with buggy engines
7424         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7425         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7426         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7427         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7428         int k, count = 0; static int bare = 1;
7429         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7430         Boolean canAdjudicate = !appData.icsActive;
7431
7432         // most tests only when we understand the game, i.e. legality-checking on
7433             if( appData.testLegality )
7434             {   /* [HGM] Some more adjudications for obstinate engines */
7435                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7436                 static int moveCount = 6;
7437                 ChessMove result;
7438                 char *reason = NULL;
7439
7440                 /* Count what is on board. */
7441                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7442
7443                 /* Some material-based adjudications that have to be made before stalemate test */
7444                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7445                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7446                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7447                      if(canAdjudicate && appData.checkMates) {
7448                          if(engineOpponent)
7449                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7450                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7451                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7452                          return 1;
7453                      }
7454                 }
7455
7456                 /* Bare King in Shatranj (loses) or Losers (wins) */
7457                 if( nrW == 1 || nrB == 1) {
7458                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7459                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7460                      if(canAdjudicate && appData.checkMates) {
7461                          if(engineOpponent)
7462                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7463                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7464                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7465                          return 1;
7466                      }
7467                   } else
7468                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7469                   {    /* bare King */
7470                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7471                         if(canAdjudicate && appData.checkMates) {
7472                             /* but only adjudicate if adjudication enabled */
7473                             if(engineOpponent)
7474                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7475                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7476                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7477                             return 1;
7478                         }
7479                   }
7480                 } else bare = 1;
7481
7482
7483             // don't wait for engine to announce game end if we can judge ourselves
7484             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7485               case MT_CHECK:
7486                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7487                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7488                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7489                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7490                             checkCnt++;
7491                         if(checkCnt >= 2) {
7492                             reason = "Xboard adjudication: 3rd check";
7493                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7494                             break;
7495                         }
7496                     }
7497                 }
7498               case MT_NONE:
7499               default:
7500                 break;
7501               case MT_STALEMATE:
7502               case MT_STAINMATE:
7503                 reason = "Xboard adjudication: Stalemate";
7504                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7505                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7506                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7507                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7508                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7509                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7510                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7511                                                                         EP_CHECKMATE : EP_WINS);
7512                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7513                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7514                 }
7515                 break;
7516               case MT_CHECKMATE:
7517                 reason = "Xboard adjudication: Checkmate";
7518                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7519                 break;
7520             }
7521
7522                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7523                     case EP_STALEMATE:
7524                         result = GameIsDrawn; break;
7525                     case EP_CHECKMATE:
7526                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7527                     case EP_WINS:
7528                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7529                     default:
7530                         result = EndOfFile;
7531                 }
7532                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7533                     if(engineOpponent)
7534                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7535                     GameEnds( result, reason, GE_XBOARD );
7536                     return 1;
7537                 }
7538
7539                 /* Next absolutely insufficient mating material. */
7540                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7541                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7542                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7543
7544                      /* always flag draws, for judging claims */
7545                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7546
7547                      if(canAdjudicate && appData.materialDraws) {
7548                          /* but only adjudicate them if adjudication enabled */
7549                          if(engineOpponent) {
7550                            SendToProgram("force\n", engineOpponent); // suppress reply
7551                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7552                          }
7553                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7554                          return 1;
7555                      }
7556                 }
7557
7558                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7559                 if(gameInfo.variant == VariantXiangqi ?
7560                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7561                  : nrW + nrB == 4 &&
7562                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7563                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7564                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7565                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7566                    ) ) {
7567                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7568                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7569                           if(engineOpponent) {
7570                             SendToProgram("force\n", engineOpponent); // suppress reply
7571                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7572                           }
7573                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7574                           return 1;
7575                      }
7576                 } else moveCount = 6;
7577             }
7578         if (appData.debugMode) { int i;
7579             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7580                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7581                     appData.drawRepeats);
7582             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7583               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7584
7585         }
7586
7587         // Repetition draws and 50-move rule can be applied independently of legality testing
7588
7589                 /* Check for rep-draws */
7590                 count = 0;
7591                 for(k = forwardMostMove-2;
7592                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7593                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7594                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7595                     k-=2)
7596                 {   int rights=0;
7597                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7598                         /* compare castling rights */
7599                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7600                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7601                                 rights++; /* King lost rights, while rook still had them */
7602                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7603                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7604                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7605                                    rights++; /* but at least one rook lost them */
7606                         }
7607                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7608                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7609                                 rights++;
7610                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7611                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7612                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7613                                    rights++;
7614                         }
7615                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7616                             && appData.drawRepeats > 1) {
7617                              /* adjudicate after user-specified nr of repeats */
7618                              int result = GameIsDrawn;
7619                              char *details = "XBoard adjudication: repetition draw";
7620                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7621                                 // [HGM] xiangqi: check for forbidden perpetuals
7622                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7623                                 for(m=forwardMostMove; m>k; m-=2) {
7624                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7625                                         ourPerpetual = 0; // the current mover did not always check
7626                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7627                                         hisPerpetual = 0; // the opponent did not always check
7628                                 }
7629                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7630                                                                         ourPerpetual, hisPerpetual);
7631                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7632                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7633                                     details = "Xboard adjudication: perpetual checking";
7634                                 } else
7635                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7636                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7637                                 } else
7638                                 // Now check for perpetual chases
7639                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7640                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7641                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7642                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7643                                         static char resdet[MSG_SIZ];
7644                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7645                                         details = resdet;
7646                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7647                                     } else
7648                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7649                                         break; // Abort repetition-checking loop.
7650                                 }
7651                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7652                              }
7653                              if(engineOpponent) {
7654                                SendToProgram("force\n", engineOpponent); // suppress reply
7655                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7656                              }
7657                              GameEnds( result, details, GE_XBOARD );
7658                              return 1;
7659                         }
7660                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7661                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7662                     }
7663                 }
7664
7665                 /* Now we test for 50-move draws. Determine ply count */
7666                 count = forwardMostMove;
7667                 /* look for last irreversble move */
7668                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7669                     count--;
7670                 /* if we hit starting position, add initial plies */
7671                 if( count == backwardMostMove )
7672                     count -= initialRulePlies;
7673                 count = forwardMostMove - count;
7674                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7675                         // adjust reversible move counter for checks in Xiangqi
7676                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7677                         if(i < backwardMostMove) i = backwardMostMove;
7678                         while(i <= forwardMostMove) {
7679                                 lastCheck = inCheck; // check evasion does not count
7680                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7681                                 if(inCheck || lastCheck) count--; // check does not count
7682                                 i++;
7683                         }
7684                 }
7685                 if( count >= 100)
7686                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7687                          /* this is used to judge if draw claims are legal */
7688                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7689                          if(engineOpponent) {
7690                            SendToProgram("force\n", engineOpponent); // suppress reply
7691                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7692                          }
7693                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7694                          return 1;
7695                 }
7696
7697                 /* if draw offer is pending, treat it as a draw claim
7698                  * when draw condition present, to allow engines a way to
7699                  * claim draws before making their move to avoid a race
7700                  * condition occurring after their move
7701                  */
7702                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7703                          char *p = NULL;
7704                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7705                              p = "Draw claim: 50-move rule";
7706                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7707                              p = "Draw claim: 3-fold repetition";
7708                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7709                              p = "Draw claim: insufficient mating material";
7710                          if( p != NULL && canAdjudicate) {
7711                              if(engineOpponent) {
7712                                SendToProgram("force\n", engineOpponent); // suppress reply
7713                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7714                              }
7715                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7716                              return 1;
7717                          }
7718                 }
7719
7720                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7721                     if(engineOpponent) {
7722                       SendToProgram("force\n", engineOpponent); // suppress reply
7723                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7724                     }
7725                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7726                     return 1;
7727                 }
7728         return 0;
7729 }
7730
7731 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7732 {   // [HGM] book: this routine intercepts moves to simulate book replies
7733     char *bookHit = NULL;
7734
7735     //first determine if the incoming move brings opponent into his book
7736     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7737         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7738     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7739     if(bookHit != NULL && !cps->bookSuspend) {
7740         // make sure opponent is not going to reply after receiving move to book position
7741         SendToProgram("force\n", cps);
7742         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7743     }
7744     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7745     // now arrange restart after book miss
7746     if(bookHit) {
7747         // after a book hit we never send 'go', and the code after the call to this routine
7748         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7749         char buf[MSG_SIZ], *move = bookHit;
7750         if(cps->useSAN) {
7751             int fromX, fromY, toX, toY;
7752             char promoChar;
7753             ChessMove moveType;
7754             move = buf + 30;
7755             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7756                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7757                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7758                                     PosFlags(forwardMostMove),
7759                                     fromY, fromX, toY, toX, promoChar, move);
7760             } else {
7761                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7762                 bookHit = NULL;
7763             }
7764         }
7765         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7766         SendToProgram(buf, cps);
7767         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7768     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7769         SendToProgram("go\n", cps);
7770         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7771     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7772         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7773             SendToProgram("go\n", cps);
7774         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7775     }
7776     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7777 }
7778
7779 char *savedMessage;
7780 ChessProgramState *savedState;
7781 void DeferredBookMove(void)
7782 {
7783         if(savedState->lastPing != savedState->lastPong)
7784                     ScheduleDelayedEvent(DeferredBookMove, 10);
7785         else
7786         HandleMachineMove(savedMessage, savedState);
7787 }
7788
7789 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7790
7791 void
7792 HandleMachineMove(message, cps)
7793      char *message;
7794      ChessProgramState *cps;
7795 {
7796     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7797     char realname[MSG_SIZ];
7798     int fromX, fromY, toX, toY;
7799     ChessMove moveType;
7800     char promoChar;
7801     char *p, *pv=buf1;
7802     int machineWhite;
7803     char *bookHit;
7804
7805     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7806         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7807         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7808             DisplayError(_("Invalid pairing from pairing engine"), 0);
7809             return;
7810         }
7811         pairingReceived = 1;
7812         NextMatchGame();
7813         return; // Skim the pairing messages here.
7814     }
7815
7816     cps->userError = 0;
7817
7818 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7819     /*
7820      * Kludge to ignore BEL characters
7821      */
7822     while (*message == '\007') message++;
7823
7824     /*
7825      * [HGM] engine debug message: ignore lines starting with '#' character
7826      */
7827     if(cps->debug && *message == '#') return;
7828
7829     /*
7830      * Look for book output
7831      */
7832     if (cps == &first && bookRequested) {
7833         if (message[0] == '\t' || message[0] == ' ') {
7834             /* Part of the book output is here; append it */
7835             strcat(bookOutput, message);
7836             strcat(bookOutput, "  \n");
7837             return;
7838         } else if (bookOutput[0] != NULLCHAR) {
7839             /* All of book output has arrived; display it */
7840             char *p = bookOutput;
7841             while (*p != NULLCHAR) {
7842                 if (*p == '\t') *p = ' ';
7843                 p++;
7844             }
7845             DisplayInformation(bookOutput);
7846             bookRequested = FALSE;
7847             /* Fall through to parse the current output */
7848         }
7849     }
7850
7851     /*
7852      * Look for machine move.
7853      */
7854     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7855         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7856     {
7857         /* This method is only useful on engines that support ping */
7858         if (cps->lastPing != cps->lastPong) {
7859           if (gameMode == BeginningOfGame) {
7860             /* Extra move from before last new; ignore */
7861             if (appData.debugMode) {
7862                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7863             }
7864           } else {
7865             if (appData.debugMode) {
7866                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7867                         cps->which, gameMode);
7868             }
7869
7870             SendToProgram("undo\n", cps);
7871           }
7872           return;
7873         }
7874
7875         switch (gameMode) {
7876           case BeginningOfGame:
7877             /* Extra move from before last reset; ignore */
7878             if (appData.debugMode) {
7879                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7880             }
7881             return;
7882
7883           case EndOfGame:
7884           case IcsIdle:
7885           default:
7886             /* Extra move after we tried to stop.  The mode test is
7887                not a reliable way of detecting this problem, but it's
7888                the best we can do on engines that don't support ping.
7889             */
7890             if (appData.debugMode) {
7891                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7892                         cps->which, gameMode);
7893             }
7894             SendToProgram("undo\n", cps);
7895             return;
7896
7897           case MachinePlaysWhite:
7898           case IcsPlayingWhite:
7899             machineWhite = TRUE;
7900             break;
7901
7902           case MachinePlaysBlack:
7903           case IcsPlayingBlack:
7904             machineWhite = FALSE;
7905             break;
7906
7907           case TwoMachinesPlay:
7908             machineWhite = (cps->twoMachinesColor[0] == 'w');
7909             break;
7910         }
7911         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7912             if (appData.debugMode) {
7913                 fprintf(debugFP,
7914                         "Ignoring move out of turn by %s, gameMode %d"
7915                         ", forwardMost %d\n",
7916                         cps->which, gameMode, forwardMostMove);
7917             }
7918             return;
7919         }
7920
7921     if (appData.debugMode) { int f = forwardMostMove;
7922         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7923                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7924                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7925     }
7926         if(cps->alphaRank) AlphaRank(machineMove, 4);
7927         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7928                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7929             /* Machine move could not be parsed; ignore it. */
7930           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7931                     machineMove, _(cps->which));
7932             DisplayError(buf1, 0);
7933             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7934                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7935             if (gameMode == TwoMachinesPlay) {
7936               GameEnds(machineWhite ? BlackWins : WhiteWins,
7937                        buf1, GE_XBOARD);
7938             }
7939             return;
7940         }
7941
7942         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7943         /* So we have to redo legality test with true e.p. status here,  */
7944         /* to make sure an illegal e.p. capture does not slip through,   */
7945         /* to cause a forfeit on a justified illegal-move complaint      */
7946         /* of the opponent.                                              */
7947         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7948            ChessMove moveType;
7949            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7950                              fromY, fromX, toY, toX, promoChar);
7951             if (appData.debugMode) {
7952                 int i;
7953                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7954                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7955                 fprintf(debugFP, "castling rights\n");
7956             }
7957             if(moveType == IllegalMove) {
7958               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7959                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7960                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7961                            buf1, GE_XBOARD);
7962                 return;
7963            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7964            /* [HGM] Kludge to handle engines that send FRC-style castling
7965               when they shouldn't (like TSCP-Gothic) */
7966            switch(moveType) {
7967              case WhiteASideCastleFR:
7968              case BlackASideCastleFR:
7969                toX+=2;
7970                currentMoveString[2]++;
7971                break;
7972              case WhiteHSideCastleFR:
7973              case BlackHSideCastleFR:
7974                toX--;
7975                currentMoveString[2]--;
7976                break;
7977              default: ; // nothing to do, but suppresses warning of pedantic compilers
7978            }
7979         }
7980         hintRequested = FALSE;
7981         lastHint[0] = NULLCHAR;
7982         bookRequested = FALSE;
7983         /* Program may be pondering now */
7984         cps->maybeThinking = TRUE;
7985         if (cps->sendTime == 2) cps->sendTime = 1;
7986         if (cps->offeredDraw) cps->offeredDraw--;
7987
7988         /* [AS] Save move info*/
7989         pvInfoList[ forwardMostMove ].score = programStats.score;
7990         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7991         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7992
7993         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7994
7995         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7996         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7997             int count = 0;
7998
7999             while( count < adjudicateLossPlies ) {
8000                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
8001
8002                 if( count & 1 ) {
8003                     score = -score; /* Flip score for winning side */
8004                 }
8005
8006                 if( score > adjudicateLossThreshold ) {
8007                     break;
8008                 }
8009
8010                 count++;
8011             }
8012
8013             if( count >= adjudicateLossPlies ) {
8014                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8015
8016                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
8017                     "Xboard adjudication",
8018                     GE_XBOARD );
8019
8020                 return;
8021             }
8022         }
8023
8024         if(Adjudicate(cps)) {
8025             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8026             return; // [HGM] adjudicate: for all automatic game ends
8027         }
8028
8029 #if ZIPPY
8030         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
8031             first.initDone) {
8032           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
8033                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
8034                 SendToICS("draw ");
8035                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8036           }
8037           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8038           ics_user_moved = 1;
8039           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8040                 char buf[3*MSG_SIZ];
8041
8042                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8043                         programStats.score / 100.,
8044                         programStats.depth,
8045                         programStats.time / 100.,
8046                         (unsigned int)programStats.nodes,
8047                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8048                         programStats.movelist);
8049                 SendToICS(buf);
8050 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8051           }
8052         }
8053 #endif
8054
8055         /* [AS] Clear stats for next move */
8056         ClearProgramStats();
8057         thinkOutput[0] = NULLCHAR;
8058         hiddenThinkOutputState = 0;
8059
8060         bookHit = NULL;
8061         if (gameMode == TwoMachinesPlay) {
8062             /* [HGM] relaying draw offers moved to after reception of move */
8063             /* and interpreting offer as claim if it brings draw condition */
8064             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8065                 SendToProgram("draw\n", cps->other);
8066             }
8067             if (cps->other->sendTime) {
8068                 SendTimeRemaining(cps->other,
8069                                   cps->other->twoMachinesColor[0] == 'w');
8070             }
8071             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8072             if (firstMove && !bookHit) {
8073                 firstMove = FALSE;
8074                 if (cps->other->useColors) {
8075                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8076                 }
8077                 SendToProgram("go\n", cps->other);
8078             }
8079             cps->other->maybeThinking = TRUE;
8080         }
8081
8082         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8083
8084         if (!pausing && appData.ringBellAfterMoves) {
8085             RingBell();
8086         }
8087
8088         /*
8089          * Reenable menu items that were disabled while
8090          * machine was thinking
8091          */
8092         if (gameMode != TwoMachinesPlay)
8093             SetUserThinkingEnables();
8094
8095         // [HGM] book: after book hit opponent has received move and is now in force mode
8096         // force the book reply into it, and then fake that it outputted this move by jumping
8097         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8098         if(bookHit) {
8099                 static char bookMove[MSG_SIZ]; // a bit generous?
8100
8101                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8102                 strcat(bookMove, bookHit);
8103                 message = bookMove;
8104                 cps = cps->other;
8105                 programStats.nodes = programStats.depth = programStats.time =
8106                 programStats.score = programStats.got_only_move = 0;
8107                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8108
8109                 if(cps->lastPing != cps->lastPong) {
8110                     savedMessage = message; // args for deferred call
8111                     savedState = cps;
8112                     ScheduleDelayedEvent(DeferredBookMove, 10);
8113                     return;
8114                 }
8115                 goto FakeBookMove;
8116         }
8117
8118         return;
8119     }
8120
8121     /* Set special modes for chess engines.  Later something general
8122      *  could be added here; for now there is just one kludge feature,
8123      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8124      *  when "xboard" is given as an interactive command.
8125      */
8126     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8127         cps->useSigint = FALSE;
8128         cps->useSigterm = FALSE;
8129     }
8130     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8131       ParseFeatures(message+8, cps);
8132       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8133     }
8134
8135     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8136                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8137       int dummy, s=6; char buf[MSG_SIZ];
8138       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8139       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8140       if(startedFromSetupPosition) return;
8141       ParseFEN(boards[0], &dummy, message+s);
8142       DrawPosition(TRUE, boards[0]);
8143       startedFromSetupPosition = TRUE;
8144       return;
8145     }
8146     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8147      * want this, I was asked to put it in, and obliged.
8148      */
8149     if (!strncmp(message, "setboard ", 9)) {
8150         Board initial_position;
8151
8152         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8153
8154         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8155             DisplayError(_("Bad FEN received from engine"), 0);
8156             return ;
8157         } else {
8158            Reset(TRUE, FALSE);
8159            CopyBoard(boards[0], initial_position);
8160            initialRulePlies = FENrulePlies;
8161            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8162            else gameMode = MachinePlaysBlack;
8163            DrawPosition(FALSE, boards[currentMove]);
8164         }
8165         return;
8166     }
8167
8168     /*
8169      * Look for communication commands
8170      */
8171     if (!strncmp(message, "telluser ", 9)) {
8172         if(message[9] == '\\' && message[10] == '\\')
8173             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8174         PlayTellSound();
8175         DisplayNote(message + 9);
8176         return;
8177     }
8178     if (!strncmp(message, "tellusererror ", 14)) {
8179         cps->userError = 1;
8180         if(message[14] == '\\' && message[15] == '\\')
8181             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8182         PlayTellSound();
8183         DisplayError(message + 14, 0);
8184         return;
8185     }
8186     if (!strncmp(message, "tellopponent ", 13)) {
8187       if (appData.icsActive) {
8188         if (loggedOn) {
8189           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8190           SendToICS(buf1);
8191         }
8192       } else {
8193         DisplayNote(message + 13);
8194       }
8195       return;
8196     }
8197     if (!strncmp(message, "tellothers ", 11)) {
8198       if (appData.icsActive) {
8199         if (loggedOn) {
8200           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8201           SendToICS(buf1);
8202         }
8203       }
8204       return;
8205     }
8206     if (!strncmp(message, "tellall ", 8)) {
8207       if (appData.icsActive) {
8208         if (loggedOn) {
8209           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8210           SendToICS(buf1);
8211         }
8212       } else {
8213         DisplayNote(message + 8);
8214       }
8215       return;
8216     }
8217     if (strncmp(message, "warning", 7) == 0) {
8218         /* Undocumented feature, use tellusererror in new code */
8219         DisplayError(message, 0);
8220         return;
8221     }
8222     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8223         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8224         strcat(realname, " query");
8225         AskQuestion(realname, buf2, buf1, cps->pr);
8226         return;
8227     }
8228     /* Commands from the engine directly to ICS.  We don't allow these to be
8229      *  sent until we are logged on. Crafty kibitzes have been known to
8230      *  interfere with the login process.
8231      */
8232     if (loggedOn) {
8233         if (!strncmp(message, "tellics ", 8)) {
8234             SendToICS(message + 8);
8235             SendToICS("\n");
8236             return;
8237         }
8238         if (!strncmp(message, "tellicsnoalias ", 15)) {
8239             SendToICS(ics_prefix);
8240             SendToICS(message + 15);
8241             SendToICS("\n");
8242             return;
8243         }
8244         /* The following are for backward compatibility only */
8245         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8246             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8247             SendToICS(ics_prefix);
8248             SendToICS(message);
8249             SendToICS("\n");
8250             return;
8251         }
8252     }
8253     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8254         return;
8255     }
8256     /*
8257      * If the move is illegal, cancel it and redraw the board.
8258      * Also deal with other error cases.  Matching is rather loose
8259      * here to accommodate engines written before the spec.
8260      */
8261     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8262         strncmp(message, "Error", 5) == 0) {
8263         if (StrStr(message, "name") ||
8264             StrStr(message, "rating") || StrStr(message, "?") ||
8265             StrStr(message, "result") || StrStr(message, "board") ||
8266             StrStr(message, "bk") || StrStr(message, "computer") ||
8267             StrStr(message, "variant") || StrStr(message, "hint") ||
8268             StrStr(message, "random") || StrStr(message, "depth") ||
8269             StrStr(message, "accepted")) {
8270             return;
8271         }
8272         if (StrStr(message, "protover")) {
8273           /* Program is responding to input, so it's apparently done
8274              initializing, and this error message indicates it is
8275              protocol version 1.  So we don't need to wait any longer
8276              for it to initialize and send feature commands. */
8277           FeatureDone(cps, 1);
8278           cps->protocolVersion = 1;
8279           return;
8280         }
8281         cps->maybeThinking = FALSE;
8282
8283         if (StrStr(message, "draw")) {
8284             /* Program doesn't have "draw" command */
8285             cps->sendDrawOffers = 0;
8286             return;
8287         }
8288         if (cps->sendTime != 1 &&
8289             (StrStr(message, "time") || StrStr(message, "otim"))) {
8290           /* Program apparently doesn't have "time" or "otim" command */
8291           cps->sendTime = 0;
8292           return;
8293         }
8294         if (StrStr(message, "analyze")) {
8295             cps->analysisSupport = FALSE;
8296             cps->analyzing = FALSE;
8297 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8298             EditGameEvent(); // [HGM] try to preserve loaded game
8299             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8300             DisplayError(buf2, 0);
8301             return;
8302         }
8303         if (StrStr(message, "(no matching move)st")) {
8304           /* Special kludge for GNU Chess 4 only */
8305           cps->stKludge = TRUE;
8306           SendTimeControl(cps, movesPerSession, timeControl,
8307                           timeIncrement, appData.searchDepth,
8308                           searchTime);
8309           return;
8310         }
8311         if (StrStr(message, "(no matching move)sd")) {
8312           /* Special kludge for GNU Chess 4 only */
8313           cps->sdKludge = TRUE;
8314           SendTimeControl(cps, movesPerSession, timeControl,
8315                           timeIncrement, appData.searchDepth,
8316                           searchTime);
8317           return;
8318         }
8319         if (!StrStr(message, "llegal")) {
8320             return;
8321         }
8322         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8323             gameMode == IcsIdle) return;
8324         if (forwardMostMove <= backwardMostMove) return;
8325         if (pausing) PauseEvent();
8326       if(appData.forceIllegal) {
8327             // [HGM] illegal: machine refused move; force position after move into it
8328           SendToProgram("force\n", cps);
8329           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8330                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8331                 // when black is to move, while there might be nothing on a2 or black
8332                 // might already have the move. So send the board as if white has the move.
8333                 // But first we must change the stm of the engine, as it refused the last move
8334                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8335                 if(WhiteOnMove(forwardMostMove)) {
8336                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8337                     SendBoard(cps, forwardMostMove); // kludgeless board
8338                 } else {
8339                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8340                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8341                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8342                 }
8343           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8344             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8345                  gameMode == TwoMachinesPlay)
8346               SendToProgram("go\n", cps);
8347             return;
8348       } else
8349         if (gameMode == PlayFromGameFile) {
8350             /* Stop reading this game file */
8351             gameMode = EditGame;
8352             ModeHighlight();
8353         }
8354         /* [HGM] illegal-move claim should forfeit game when Xboard */
8355         /* only passes fully legal moves                            */
8356         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8357             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8358                                 "False illegal-move claim", GE_XBOARD );
8359             return; // do not take back move we tested as valid
8360         }
8361         currentMove = forwardMostMove-1;
8362         DisplayMove(currentMove-1); /* before DisplayMoveError */
8363         SwitchClocks(forwardMostMove-1); // [HGM] race
8364         DisplayBothClocks();
8365         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8366                 parseList[currentMove], _(cps->which));
8367         DisplayMoveError(buf1);
8368         DrawPosition(FALSE, boards[currentMove]);
8369
8370         SetUserThinkingEnables();
8371         return;
8372     }
8373     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8374         /* Program has a broken "time" command that
8375            outputs a string not ending in newline.
8376            Don't use it. */
8377         cps->sendTime = 0;
8378     }
8379
8380     /*
8381      * If chess program startup fails, exit with an error message.
8382      * Attempts to recover here are futile.
8383      */
8384     if ((StrStr(message, "unknown host") != NULL)
8385         || (StrStr(message, "No remote directory") != NULL)
8386         || (StrStr(message, "not found") != NULL)
8387         || (StrStr(message, "No such file") != NULL)
8388         || (StrStr(message, "can't alloc") != NULL)
8389         || (StrStr(message, "Permission denied") != NULL)) {
8390
8391         cps->maybeThinking = FALSE;
8392         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8393                 _(cps->which), cps->program, cps->host, message);
8394         RemoveInputSource(cps->isr);
8395         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8396             if(cps == &first) appData.noChessProgram = TRUE;
8397             DisplayError(buf1, 0);
8398         }
8399         return;
8400     }
8401
8402     /*
8403      * Look for hint output
8404      */
8405     if (sscanf(message, "Hint: %s", buf1) == 1) {
8406         if (cps == &first && hintRequested) {
8407             hintRequested = FALSE;
8408             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8409                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8410                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8411                                     PosFlags(forwardMostMove),
8412                                     fromY, fromX, toY, toX, promoChar, buf1);
8413                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8414                 DisplayInformation(buf2);
8415             } else {
8416                 /* Hint move could not be parsed!? */
8417               snprintf(buf2, sizeof(buf2),
8418                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8419                         buf1, _(cps->which));
8420                 DisplayError(buf2, 0);
8421             }
8422         } else {
8423           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8424         }
8425         return;
8426     }
8427
8428     /*
8429      * Ignore other messages if game is not in progress
8430      */
8431     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8432         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8433
8434     /*
8435      * look for win, lose, draw, or draw offer
8436      */
8437     if (strncmp(message, "1-0", 3) == 0) {
8438         char *p, *q, *r = "";
8439         p = strchr(message, '{');
8440         if (p) {
8441             q = strchr(p, '}');
8442             if (q) {
8443                 *q = NULLCHAR;
8444                 r = p + 1;
8445             }
8446         }
8447         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8448         return;
8449     } else if (strncmp(message, "0-1", 3) == 0) {
8450         char *p, *q, *r = "";
8451         p = strchr(message, '{');
8452         if (p) {
8453             q = strchr(p, '}');
8454             if (q) {
8455                 *q = NULLCHAR;
8456                 r = p + 1;
8457             }
8458         }
8459         /* Kludge for Arasan 4.1 bug */
8460         if (strcmp(r, "Black resigns") == 0) {
8461             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8462             return;
8463         }
8464         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8465         return;
8466     } else if (strncmp(message, "1/2", 3) == 0) {
8467         char *p, *q, *r = "";
8468         p = strchr(message, '{');
8469         if (p) {
8470             q = strchr(p, '}');
8471             if (q) {
8472                 *q = NULLCHAR;
8473                 r = p + 1;
8474             }
8475         }
8476
8477         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8478         return;
8479
8480     } else if (strncmp(message, "White resign", 12) == 0) {
8481         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8482         return;
8483     } else if (strncmp(message, "Black resign", 12) == 0) {
8484         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8485         return;
8486     } else if (strncmp(message, "White matches", 13) == 0 ||
8487                strncmp(message, "Black matches", 13) == 0   ) {
8488         /* [HGM] ignore GNUShogi noises */
8489         return;
8490     } else if (strncmp(message, "White", 5) == 0 &&
8491                message[5] != '(' &&
8492                StrStr(message, "Black") == NULL) {
8493         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8494         return;
8495     } else if (strncmp(message, "Black", 5) == 0 &&
8496                message[5] != '(') {
8497         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8498         return;
8499     } else if (strcmp(message, "resign") == 0 ||
8500                strcmp(message, "computer resigns") == 0) {
8501         switch (gameMode) {
8502           case MachinePlaysBlack:
8503           case IcsPlayingBlack:
8504             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8505             break;
8506           case MachinePlaysWhite:
8507           case IcsPlayingWhite:
8508             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8509             break;
8510           case TwoMachinesPlay:
8511             if (cps->twoMachinesColor[0] == 'w')
8512               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8513             else
8514               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8515             break;
8516           default:
8517             /* can't happen */
8518             break;
8519         }
8520         return;
8521     } else if (strncmp(message, "opponent mates", 14) == 0) {
8522         switch (gameMode) {
8523           case MachinePlaysBlack:
8524           case IcsPlayingBlack:
8525             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8526             break;
8527           case MachinePlaysWhite:
8528           case IcsPlayingWhite:
8529             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8530             break;
8531           case TwoMachinesPlay:
8532             if (cps->twoMachinesColor[0] == 'w')
8533               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8534             else
8535               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8536             break;
8537           default:
8538             /* can't happen */
8539             break;
8540         }
8541         return;
8542     } else if (strncmp(message, "computer mates", 14) == 0) {
8543         switch (gameMode) {
8544           case MachinePlaysBlack:
8545           case IcsPlayingBlack:
8546             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8547             break;
8548           case MachinePlaysWhite:
8549           case IcsPlayingWhite:
8550             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8551             break;
8552           case TwoMachinesPlay:
8553             if (cps->twoMachinesColor[0] == 'w')
8554               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8555             else
8556               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8557             break;
8558           default:
8559             /* can't happen */
8560             break;
8561         }
8562         return;
8563     } else if (strncmp(message, "checkmate", 9) == 0) {
8564         if (WhiteOnMove(forwardMostMove)) {
8565             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8566         } else {
8567             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8568         }
8569         return;
8570     } else if (strstr(message, "Draw") != NULL ||
8571                strstr(message, "game is a draw") != NULL) {
8572         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8573         return;
8574     } else if (strstr(message, "offer") != NULL &&
8575                strstr(message, "draw") != NULL) {
8576 #if ZIPPY
8577         if (appData.zippyPlay && first.initDone) {
8578             /* Relay offer to ICS */
8579             SendToICS(ics_prefix);
8580             SendToICS("draw\n");
8581         }
8582 #endif
8583         cps->offeredDraw = 2; /* valid until this engine moves twice */
8584         if (gameMode == TwoMachinesPlay) {
8585             if (cps->other->offeredDraw) {
8586                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8587             /* [HGM] in two-machine mode we delay relaying draw offer      */
8588             /* until after we also have move, to see if it is really claim */
8589             }
8590         } else if (gameMode == MachinePlaysWhite ||
8591                    gameMode == MachinePlaysBlack) {
8592           if (userOfferedDraw) {
8593             DisplayInformation(_("Machine accepts your draw offer"));
8594             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8595           } else {
8596             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8597           }
8598         }
8599     }
8600
8601
8602     /*
8603      * Look for thinking output
8604      */
8605     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8606           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8607                                 ) {
8608         int plylev, mvleft, mvtot, curscore, time;
8609         char mvname[MOVE_LEN];
8610         u64 nodes; // [DM]
8611         char plyext;
8612         int ignore = FALSE;
8613         int prefixHint = FALSE;
8614         mvname[0] = NULLCHAR;
8615
8616         switch (gameMode) {
8617           case MachinePlaysBlack:
8618           case IcsPlayingBlack:
8619             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8620             break;
8621           case MachinePlaysWhite:
8622           case IcsPlayingWhite:
8623             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8624             break;
8625           case AnalyzeMode:
8626           case AnalyzeFile:
8627             break;
8628           case IcsObserving: /* [DM] icsEngineAnalyze */
8629             if (!appData.icsEngineAnalyze) ignore = TRUE;
8630             break;
8631           case TwoMachinesPlay:
8632             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8633                 ignore = TRUE;
8634             }
8635             break;
8636           default:
8637             ignore = TRUE;
8638             break;
8639         }
8640
8641         if (!ignore) {
8642             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8643             buf1[0] = NULLCHAR;
8644             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8645                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8646
8647                 if (plyext != ' ' && plyext != '\t') {
8648                     time *= 100;
8649                 }
8650
8651                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8652                 if( cps->scoreIsAbsolute &&
8653                     ( gameMode == MachinePlaysBlack ||
8654                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8655                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8656                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8657                      !WhiteOnMove(currentMove)
8658                     ) )
8659                 {
8660                     curscore = -curscore;
8661                 }
8662
8663                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8664
8665                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8666                         char buf[MSG_SIZ];
8667                         FILE *f;
8668                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8669                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8670                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8671                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8672                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8673                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8674                                 fclose(f);
8675                         } else DisplayError(_("failed writing PV"), 0);
8676                 }
8677
8678                 tempStats.depth = plylev;
8679                 tempStats.nodes = nodes;
8680                 tempStats.time = time;
8681                 tempStats.score = curscore;
8682                 tempStats.got_only_move = 0;
8683
8684                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8685                         int ticklen;
8686
8687                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8688                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8689                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8690                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8691                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8692                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8693                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8694                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8695                 }
8696
8697                 /* Buffer overflow protection */
8698                 if (pv[0] != NULLCHAR) {
8699                     if (strlen(pv) >= sizeof(tempStats.movelist)
8700                         && appData.debugMode) {
8701                         fprintf(debugFP,
8702                                 "PV is too long; using the first %u bytes.\n",
8703                                 (unsigned) sizeof(tempStats.movelist) - 1);
8704                     }
8705
8706                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8707                 } else {
8708                     sprintf(tempStats.movelist, " no PV\n");
8709                 }
8710
8711                 if (tempStats.seen_stat) {
8712                     tempStats.ok_to_send = 1;
8713                 }
8714
8715                 if (strchr(tempStats.movelist, '(') != NULL) {
8716                     tempStats.line_is_book = 1;
8717                     tempStats.nr_moves = 0;
8718                     tempStats.moves_left = 0;
8719                 } else {
8720                     tempStats.line_is_book = 0;
8721                 }
8722
8723                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8724                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8725
8726                 SendProgramStatsToFrontend( cps, &tempStats );
8727
8728                 /*
8729                     [AS] Protect the thinkOutput buffer from overflow... this
8730                     is only useful if buf1 hasn't overflowed first!
8731                 */
8732                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8733                          plylev,
8734                          (gameMode == TwoMachinesPlay ?
8735                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8736                          ((double) curscore) / 100.0,
8737                          prefixHint ? lastHint : "",
8738                          prefixHint ? " " : "" );
8739
8740                 if( buf1[0] != NULLCHAR ) {
8741                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8742
8743                     if( strlen(pv) > max_len ) {
8744                         if( appData.debugMode) {
8745                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8746                         }
8747                         pv[max_len+1] = '\0';
8748                     }
8749
8750                     strcat( thinkOutput, pv);
8751                 }
8752
8753                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8754                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8755                     DisplayMove(currentMove - 1);
8756                 }
8757                 return;
8758
8759             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8760                 /* crafty (9.25+) says "(only move) <move>"
8761                  * if there is only 1 legal move
8762                  */
8763                 sscanf(p, "(only move) %s", buf1);
8764                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8765                 sprintf(programStats.movelist, "%s (only move)", buf1);
8766                 programStats.depth = 1;
8767                 programStats.nr_moves = 1;
8768                 programStats.moves_left = 1;
8769                 programStats.nodes = 1;
8770                 programStats.time = 1;
8771                 programStats.got_only_move = 1;
8772
8773                 /* Not really, but we also use this member to
8774                    mean "line isn't going to change" (Crafty
8775                    isn't searching, so stats won't change) */
8776                 programStats.line_is_book = 1;
8777
8778                 SendProgramStatsToFrontend( cps, &programStats );
8779
8780                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8781                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8782                     DisplayMove(currentMove - 1);
8783                 }
8784                 return;
8785             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8786                               &time, &nodes, &plylev, &mvleft,
8787                               &mvtot, mvname) >= 5) {
8788                 /* The stat01: line is from Crafty (9.29+) in response
8789                    to the "." command */
8790                 programStats.seen_stat = 1;
8791                 cps->maybeThinking = TRUE;
8792
8793                 if (programStats.got_only_move || !appData.periodicUpdates)
8794                   return;
8795
8796                 programStats.depth = plylev;
8797                 programStats.time = time;
8798                 programStats.nodes = nodes;
8799                 programStats.moves_left = mvleft;
8800                 programStats.nr_moves = mvtot;
8801                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8802                 programStats.ok_to_send = 1;
8803                 programStats.movelist[0] = '\0';
8804
8805                 SendProgramStatsToFrontend( cps, &programStats );
8806
8807                 return;
8808
8809             } else if (strncmp(message,"++",2) == 0) {
8810                 /* Crafty 9.29+ outputs this */
8811                 programStats.got_fail = 2;
8812                 return;
8813
8814             } else if (strncmp(message,"--",2) == 0) {
8815                 /* Crafty 9.29+ outputs this */
8816                 programStats.got_fail = 1;
8817                 return;
8818
8819             } else if (thinkOutput[0] != NULLCHAR &&
8820                        strncmp(message, "    ", 4) == 0) {
8821                 unsigned message_len;
8822
8823                 p = message;
8824                 while (*p && *p == ' ') p++;
8825
8826                 message_len = strlen( p );
8827
8828                 /* [AS] Avoid buffer overflow */
8829                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8830                     strcat(thinkOutput, " ");
8831                     strcat(thinkOutput, p);
8832                 }
8833
8834                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8835                     strcat(programStats.movelist, " ");
8836                     strcat(programStats.movelist, p);
8837                 }
8838
8839                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8840                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8841                     DisplayMove(currentMove - 1);
8842                 }
8843                 return;
8844             }
8845         }
8846         else {
8847             buf1[0] = NULLCHAR;
8848
8849             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8850                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8851             {
8852                 ChessProgramStats cpstats;
8853
8854                 if (plyext != ' ' && plyext != '\t') {
8855                     time *= 100;
8856                 }
8857
8858                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8859                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8860                     curscore = -curscore;
8861                 }
8862
8863                 cpstats.depth = plylev;
8864                 cpstats.nodes = nodes;
8865                 cpstats.time = time;
8866                 cpstats.score = curscore;
8867                 cpstats.got_only_move = 0;
8868                 cpstats.movelist[0] = '\0';
8869
8870                 if (buf1[0] != NULLCHAR) {
8871                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8872                 }
8873
8874                 cpstats.ok_to_send = 0;
8875                 cpstats.line_is_book = 0;
8876                 cpstats.nr_moves = 0;
8877                 cpstats.moves_left = 0;
8878
8879                 SendProgramStatsToFrontend( cps, &cpstats );
8880             }
8881         }
8882     }
8883 }
8884
8885
8886 /* Parse a game score from the character string "game", and
8887    record it as the history of the current game.  The game
8888    score is NOT assumed to start from the standard position.
8889    The display is not updated in any way.
8890    */
8891 void
8892 ParseGameHistory(game)
8893      char *game;
8894 {
8895     ChessMove moveType;
8896     int fromX, fromY, toX, toY, boardIndex;
8897     char promoChar;
8898     char *p, *q;
8899     char buf[MSG_SIZ];
8900
8901     if (appData.debugMode)
8902       fprintf(debugFP, "Parsing game history: %s\n", game);
8903
8904     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8905     gameInfo.site = StrSave(appData.icsHost);
8906     gameInfo.date = PGNDate();
8907     gameInfo.round = StrSave("-");
8908
8909     /* Parse out names of players */
8910     while (*game == ' ') game++;
8911     p = buf;
8912     while (*game != ' ') *p++ = *game++;
8913     *p = NULLCHAR;
8914     gameInfo.white = StrSave(buf);
8915     while (*game == ' ') game++;
8916     p = buf;
8917     while (*game != ' ' && *game != '\n') *p++ = *game++;
8918     *p = NULLCHAR;
8919     gameInfo.black = StrSave(buf);
8920
8921     /* Parse moves */
8922     boardIndex = blackPlaysFirst ? 1 : 0;
8923     yynewstr(game);
8924     for (;;) {
8925         yyboardindex = boardIndex;
8926         moveType = (ChessMove) Myylex();
8927         switch (moveType) {
8928           case IllegalMove:             /* maybe suicide chess, etc. */
8929   if (appData.debugMode) {
8930     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8931     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8932     setbuf(debugFP, NULL);
8933   }
8934           case WhitePromotion:
8935           case BlackPromotion:
8936           case WhiteNonPromotion:
8937           case BlackNonPromotion:
8938           case NormalMove:
8939           case WhiteCapturesEnPassant:
8940           case BlackCapturesEnPassant:
8941           case WhiteKingSideCastle:
8942           case WhiteQueenSideCastle:
8943           case BlackKingSideCastle:
8944           case BlackQueenSideCastle:
8945           case WhiteKingSideCastleWild:
8946           case WhiteQueenSideCastleWild:
8947           case BlackKingSideCastleWild:
8948           case BlackQueenSideCastleWild:
8949           /* PUSH Fabien */
8950           case WhiteHSideCastleFR:
8951           case WhiteASideCastleFR:
8952           case BlackHSideCastleFR:
8953           case BlackASideCastleFR:
8954           /* POP Fabien */
8955             fromX = currentMoveString[0] - AAA;
8956             fromY = currentMoveString[1] - ONE;
8957             toX = currentMoveString[2] - AAA;
8958             toY = currentMoveString[3] - ONE;
8959             promoChar = currentMoveString[4];
8960             break;
8961           case WhiteDrop:
8962           case BlackDrop:
8963             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8964             fromX = moveType == WhiteDrop ?
8965               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8966             (int) CharToPiece(ToLower(currentMoveString[0]));
8967             fromY = DROP_RANK;
8968             toX = currentMoveString[2] - AAA;
8969             toY = currentMoveString[3] - ONE;
8970             promoChar = NULLCHAR;
8971             break;
8972           case AmbiguousMove:
8973             /* bug? */
8974             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8975   if (appData.debugMode) {
8976     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8977     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8978     setbuf(debugFP, NULL);
8979   }
8980             DisplayError(buf, 0);
8981             return;
8982           case ImpossibleMove:
8983             /* bug? */
8984             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8985   if (appData.debugMode) {
8986     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8987     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8988     setbuf(debugFP, NULL);
8989   }
8990             DisplayError(buf, 0);
8991             return;
8992           case EndOfFile:
8993             if (boardIndex < backwardMostMove) {
8994                 /* Oops, gap.  How did that happen? */
8995                 DisplayError(_("Gap in move list"), 0);
8996                 return;
8997             }
8998             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8999             if (boardIndex > forwardMostMove) {
9000                 forwardMostMove = boardIndex;
9001             }
9002             return;
9003           case ElapsedTime:
9004             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
9005                 strcat(parseList[boardIndex-1], " ");
9006                 strcat(parseList[boardIndex-1], yy_text);
9007             }
9008             continue;
9009           case Comment:
9010           case PGNTag:
9011           case NAG:
9012           default:
9013             /* ignore */
9014             continue;
9015           case WhiteWins:
9016           case BlackWins:
9017           case GameIsDrawn:
9018           case GameUnfinished:
9019             if (gameMode == IcsExamining) {
9020                 if (boardIndex < backwardMostMove) {
9021                     /* Oops, gap.  How did that happen? */
9022                     return;
9023                 }
9024                 backwardMostMove = blackPlaysFirst ? 1 : 0;
9025                 return;
9026             }
9027             gameInfo.result = moveType;
9028             p = strchr(yy_text, '{');
9029             if (p == NULL) p = strchr(yy_text, '(');
9030             if (p == NULL) {
9031                 p = yy_text;
9032                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9033             } else {
9034                 q = strchr(p, *p == '{' ? '}' : ')');
9035                 if (q != NULL) *q = NULLCHAR;
9036                 p++;
9037             }
9038             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9039             gameInfo.resultDetails = StrSave(p);
9040             continue;
9041         }
9042         if (boardIndex >= forwardMostMove &&
9043             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9044             backwardMostMove = blackPlaysFirst ? 1 : 0;
9045             return;
9046         }
9047         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9048                                  fromY, fromX, toY, toX, promoChar,
9049                                  parseList[boardIndex]);
9050         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9051         /* currentMoveString is set as a side-effect of yylex */
9052         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9053         strcat(moveList[boardIndex], "\n");
9054         boardIndex++;
9055         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9056         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9057           case MT_NONE:
9058           case MT_STALEMATE:
9059           default:
9060             break;
9061           case MT_CHECK:
9062             if(gameInfo.variant != VariantShogi)
9063                 strcat(parseList[boardIndex - 1], "+");
9064             break;
9065           case MT_CHECKMATE:
9066           case MT_STAINMATE:
9067             strcat(parseList[boardIndex - 1], "#");
9068             break;
9069         }
9070     }
9071 }
9072
9073
9074 /* Apply a move to the given board  */
9075 void
9076 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
9077      int fromX, fromY, toX, toY;
9078      int promoChar;
9079      Board board;
9080 {
9081   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9082   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9083
9084     /* [HGM] compute & store e.p. status and castling rights for new position */
9085     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9086
9087       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9088       oldEP = (signed char)board[EP_STATUS];
9089       board[EP_STATUS] = EP_NONE;
9090
9091   if (fromY == DROP_RANK) {
9092         /* must be first */
9093         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9094             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9095             return;
9096         }
9097         piece = board[toY][toX] = (ChessSquare) fromX;
9098   } else {
9099       int i;
9100
9101       if( board[toY][toX] != EmptySquare )
9102            board[EP_STATUS] = EP_CAPTURE;
9103
9104       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9105            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9106                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9107       } else
9108       if( board[fromY][fromX] == WhitePawn ) {
9109            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9110                board[EP_STATUS] = EP_PAWN_MOVE;
9111            if( toY-fromY==2) {
9112                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9113                         gameInfo.variant != VariantBerolina || toX < fromX)
9114                       board[EP_STATUS] = toX | berolina;
9115                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9116                         gameInfo.variant != VariantBerolina || toX > fromX)
9117                       board[EP_STATUS] = toX;
9118            }
9119       } else
9120       if( board[fromY][fromX] == BlackPawn ) {
9121            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9122                board[EP_STATUS] = EP_PAWN_MOVE;
9123            if( toY-fromY== -2) {
9124                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9125                         gameInfo.variant != VariantBerolina || toX < fromX)
9126                       board[EP_STATUS] = toX | berolina;
9127                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9128                         gameInfo.variant != VariantBerolina || toX > fromX)
9129                       board[EP_STATUS] = toX;
9130            }
9131        }
9132
9133        for(i=0; i<nrCastlingRights; i++) {
9134            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9135               board[CASTLING][i] == toX   && castlingRank[i] == toY
9136              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9137        }
9138
9139      if (fromX == toX && fromY == toY) return;
9140
9141      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9142      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9143      if(gameInfo.variant == VariantKnightmate)
9144          king += (int) WhiteUnicorn - (int) WhiteKing;
9145
9146     /* Code added by Tord: */
9147     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9148     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9149         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9150       board[fromY][fromX] = EmptySquare;
9151       board[toY][toX] = EmptySquare;
9152       if((toX > fromX) != (piece == WhiteRook)) {
9153         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9154       } else {
9155         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9156       }
9157     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9158                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9159       board[fromY][fromX] = EmptySquare;
9160       board[toY][toX] = EmptySquare;
9161       if((toX > fromX) != (piece == BlackRook)) {
9162         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9163       } else {
9164         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9165       }
9166     /* End of code added by Tord */
9167
9168     } else if (board[fromY][fromX] == king
9169         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9170         && toY == fromY && toX > fromX+1) {
9171         board[fromY][fromX] = EmptySquare;
9172         board[toY][toX] = king;
9173         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9174         board[fromY][BOARD_RGHT-1] = EmptySquare;
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_LEFT];
9181         board[fromY][BOARD_LEFT] = EmptySquare;
9182     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9183                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9184                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9185                ) {
9186         /* white pawn promotion */
9187         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9188         if(gameInfo.variant==VariantBughouse ||
9189            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9190             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9191         board[fromY][fromX] = EmptySquare;
9192     } else if ((fromY >= BOARD_HEIGHT>>1)
9193                && (toX != fromX)
9194                && gameInfo.variant != VariantXiangqi
9195                && gameInfo.variant != VariantBerolina
9196                && (board[fromY][fromX] == WhitePawn)
9197                && (board[toY][toX] == EmptySquare)) {
9198         board[fromY][fromX] = EmptySquare;
9199         board[toY][toX] = WhitePawn;
9200         captured = board[toY - 1][toX];
9201         board[toY - 1][toX] = EmptySquare;
9202     } else if ((fromY == BOARD_HEIGHT-4)
9203                && (toX == fromX)
9204                && gameInfo.variant == VariantBerolina
9205                && (board[fromY][fromX] == WhitePawn)
9206                && (board[toY][toX] == EmptySquare)) {
9207         board[fromY][fromX] = EmptySquare;
9208         board[toY][toX] = WhitePawn;
9209         if(oldEP & EP_BEROLIN_A) {
9210                 captured = board[fromY][fromX-1];
9211                 board[fromY][fromX-1] = EmptySquare;
9212         }else{  captured = board[fromY][fromX+1];
9213                 board[fromY][fromX+1] = EmptySquare;
9214         }
9215     } else if (board[fromY][fromX] == king
9216         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9217                && toY == fromY && toX > fromX+1) {
9218         board[fromY][fromX] = EmptySquare;
9219         board[toY][toX] = king;
9220         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9221         board[fromY][BOARD_RGHT-1] = EmptySquare;
9222     } else if (board[fromY][fromX] == king
9223         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9224                && toY == fromY && toX < fromX-1) {
9225         board[fromY][fromX] = EmptySquare;
9226         board[toY][toX] = king;
9227         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9228         board[fromY][BOARD_LEFT] = EmptySquare;
9229     } else if (fromY == 7 && fromX == 3
9230                && board[fromY][fromX] == BlackKing
9231                && toY == 7 && toX == 5) {
9232         board[fromY][fromX] = EmptySquare;
9233         board[toY][toX] = BlackKing;
9234         board[fromY][7] = EmptySquare;
9235         board[toY][4] = BlackRook;
9236     } else if (fromY == 7 && fromX == 3
9237                && board[fromY][fromX] == BlackKing
9238                && toY == 7 && toX == 1) {
9239         board[fromY][fromX] = EmptySquare;
9240         board[toY][toX] = BlackKing;
9241         board[fromY][0] = EmptySquare;
9242         board[toY][2] = BlackRook;
9243     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9244                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9245                && toY < promoRank && promoChar
9246                ) {
9247         /* black pawn promotion */
9248         board[toY][toX] = CharToPiece(ToLower(promoChar));
9249         if(gameInfo.variant==VariantBughouse ||
9250            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9251             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9252         board[fromY][fromX] = EmptySquare;
9253     } else if ((fromY < BOARD_HEIGHT>>1)
9254                && (toX != fromX)
9255                && gameInfo.variant != VariantXiangqi
9256                && gameInfo.variant != VariantBerolina
9257                && (board[fromY][fromX] == BlackPawn)
9258                && (board[toY][toX] == EmptySquare)) {
9259         board[fromY][fromX] = EmptySquare;
9260         board[toY][toX] = BlackPawn;
9261         captured = board[toY + 1][toX];
9262         board[toY + 1][toX] = EmptySquare;
9263     } else if ((fromY == 3)
9264                && (toX == fromX)
9265                && gameInfo.variant == VariantBerolina
9266                && (board[fromY][fromX] == BlackPawn)
9267                && (board[toY][toX] == EmptySquare)) {
9268         board[fromY][fromX] = EmptySquare;
9269         board[toY][toX] = BlackPawn;
9270         if(oldEP & EP_BEROLIN_A) {
9271                 captured = board[fromY][fromX-1];
9272                 board[fromY][fromX-1] = EmptySquare;
9273         }else{  captured = board[fromY][fromX+1];
9274                 board[fromY][fromX+1] = EmptySquare;
9275         }
9276     } else {
9277         board[toY][toX] = board[fromY][fromX];
9278         board[fromY][fromX] = EmptySquare;
9279     }
9280   }
9281
9282     if (gameInfo.holdingsWidth != 0) {
9283
9284       /* !!A lot more code needs to be written to support holdings  */
9285       /* [HGM] OK, so I have written it. Holdings are stored in the */
9286       /* penultimate board files, so they are automaticlly stored   */
9287       /* in the game history.                                       */
9288       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9289                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9290         /* Delete from holdings, by decreasing count */
9291         /* and erasing image if necessary            */
9292         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9293         if(p < (int) BlackPawn) { /* white drop */
9294              p -= (int)WhitePawn;
9295                  p = PieceToNumber((ChessSquare)p);
9296              if(p >= gameInfo.holdingsSize) p = 0;
9297              if(--board[p][BOARD_WIDTH-2] <= 0)
9298                   board[p][BOARD_WIDTH-1] = EmptySquare;
9299              if((int)board[p][BOARD_WIDTH-2] < 0)
9300                         board[p][BOARD_WIDTH-2] = 0;
9301         } else {                  /* black drop */
9302              p -= (int)BlackPawn;
9303                  p = PieceToNumber((ChessSquare)p);
9304              if(p >= gameInfo.holdingsSize) p = 0;
9305              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9306                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9307              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9308                         board[BOARD_HEIGHT-1-p][1] = 0;
9309         }
9310       }
9311       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9312           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9313         /* [HGM] holdings: Add to holdings, if holdings exist */
9314         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9315                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9316                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9317         }
9318         p = (int) captured;
9319         if (p >= (int) BlackPawn) {
9320           p -= (int)BlackPawn;
9321           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9322                   /* in Shogi restore piece to its original  first */
9323                   captured = (ChessSquare) (DEMOTED captured);
9324                   p = DEMOTED p;
9325           }
9326           p = PieceToNumber((ChessSquare)p);
9327           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9328           board[p][BOARD_WIDTH-2]++;
9329           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9330         } else {
9331           p -= (int)WhitePawn;
9332           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9333                   captured = (ChessSquare) (DEMOTED captured);
9334                   p = DEMOTED p;
9335           }
9336           p = PieceToNumber((ChessSquare)p);
9337           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9338           board[BOARD_HEIGHT-1-p][1]++;
9339           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9340         }
9341       }
9342     } else if (gameInfo.variant == VariantAtomic) {
9343       if (captured != EmptySquare) {
9344         int y, x;
9345         for (y = toY-1; y <= toY+1; y++) {
9346           for (x = toX-1; x <= toX+1; x++) {
9347             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9348                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9349               board[y][x] = EmptySquare;
9350             }
9351           }
9352         }
9353         board[toY][toX] = EmptySquare;
9354       }
9355     }
9356     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9357         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9358     } else
9359     if(promoChar == '+') {
9360         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9361         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9362     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9363         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9364     }
9365     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9366                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9367         // [HGM] superchess: take promotion piece out of holdings
9368         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9369         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9370             if(!--board[k][BOARD_WIDTH-2])
9371                 board[k][BOARD_WIDTH-1] = EmptySquare;
9372         } else {
9373             if(!--board[BOARD_HEIGHT-1-k][1])
9374                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9375         }
9376     }
9377
9378 }
9379
9380 /* Updates forwardMostMove */
9381 void
9382 MakeMove(fromX, fromY, toX, toY, promoChar)
9383      int fromX, fromY, toX, toY;
9384      int promoChar;
9385 {
9386 //    forwardMostMove++; // [HGM] bare: moved downstream
9387
9388     (void) CoordsToAlgebraic(boards[forwardMostMove],
9389                              PosFlags(forwardMostMove),
9390                              fromY, fromX, toY, toX, promoChar,
9391                              parseList[forwardMostMove]);
9392
9393     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9394         int timeLeft; static int lastLoadFlag=0; int king, piece;
9395         piece = boards[forwardMostMove][fromY][fromX];
9396         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9397         if(gameInfo.variant == VariantKnightmate)
9398             king += (int) WhiteUnicorn - (int) WhiteKing;
9399         if(forwardMostMove == 0) {
9400             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9401                 fprintf(serverMoves, "%s;", UserName());
9402             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9403                 fprintf(serverMoves, "%s;", second.tidy);
9404             fprintf(serverMoves, "%s;", first.tidy);
9405             if(gameMode == MachinePlaysWhite)
9406                 fprintf(serverMoves, "%s;", UserName());
9407             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9408                 fprintf(serverMoves, "%s;", second.tidy);
9409         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9410         lastLoadFlag = loadFlag;
9411         // print base move
9412         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9413         // print castling suffix
9414         if( toY == fromY && piece == king ) {
9415             if(toX-fromX > 1)
9416                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9417             if(fromX-toX >1)
9418                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9419         }
9420         // e.p. suffix
9421         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9422              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9423              boards[forwardMostMove][toY][toX] == EmptySquare
9424              && fromX != toX && fromY != toY)
9425                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9426         // promotion suffix
9427         if(promoChar != NULLCHAR)
9428                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9429         if(!loadFlag) {
9430                 char buf[MOVE_LEN*2], *p; int len;
9431             fprintf(serverMoves, "/%d/%d",
9432                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9433             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9434             else                      timeLeft = blackTimeRemaining/1000;
9435             fprintf(serverMoves, "/%d", timeLeft);
9436                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9437                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9438                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9439             fprintf(serverMoves, "/%s", buf);
9440         }
9441         fflush(serverMoves);
9442     }
9443
9444     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9445         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9446       return;
9447     }
9448     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9449     if (commentList[forwardMostMove+1] != NULL) {
9450         free(commentList[forwardMostMove+1]);
9451         commentList[forwardMostMove+1] = NULL;
9452     }
9453     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9454     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9455     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9456     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9457     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9458     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9459     adjustedClock = FALSE;
9460     gameInfo.result = GameUnfinished;
9461     if (gameInfo.resultDetails != NULL) {
9462         free(gameInfo.resultDetails);
9463         gameInfo.resultDetails = NULL;
9464     }
9465     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9466                               moveList[forwardMostMove - 1]);
9467     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9468       case MT_NONE:
9469       case MT_STALEMATE:
9470       default:
9471         break;
9472       case MT_CHECK:
9473         if(gameInfo.variant != VariantShogi)
9474             strcat(parseList[forwardMostMove - 1], "+");
9475         break;
9476       case MT_CHECKMATE:
9477       case MT_STAINMATE:
9478         strcat(parseList[forwardMostMove - 1], "#");
9479         break;
9480     }
9481     if (appData.debugMode) {
9482         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9483     }
9484
9485 }
9486
9487 /* Updates currentMove if not pausing */
9488 void
9489 ShowMove(fromX, fromY, toX, toY)
9490 {
9491     int instant = (gameMode == PlayFromGameFile) ?
9492         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9493     if(appData.noGUI) return;
9494     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9495         if (!instant) {
9496             if (forwardMostMove == currentMove + 1) {
9497                 AnimateMove(boards[forwardMostMove - 1],
9498                             fromX, fromY, toX, toY);
9499             }
9500             if (appData.highlightLastMove) {
9501                 SetHighlights(fromX, fromY, toX, toY);
9502             }
9503         }
9504         currentMove = forwardMostMove;
9505     }
9506
9507     if (instant) return;
9508
9509     DisplayMove(currentMove - 1);
9510     DrawPosition(FALSE, boards[currentMove]);
9511     DisplayBothClocks();
9512     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9513 }
9514
9515 void SendEgtPath(ChessProgramState *cps)
9516 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9517         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9518
9519         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9520
9521         while(*p) {
9522             char c, *q = name+1, *r, *s;
9523
9524             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9525             while(*p && *p != ',') *q++ = *p++;
9526             *q++ = ':'; *q = 0;
9527             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9528                 strcmp(name, ",nalimov:") == 0 ) {
9529                 // take nalimov path from the menu-changeable option first, if it is defined
9530               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9531                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9532             } else
9533             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9534                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9535                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9536                 s = r = StrStr(s, ":") + 1; // beginning of path info
9537                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9538                 c = *r; *r = 0;             // temporarily null-terminate path info
9539                     *--q = 0;               // strip of trailig ':' from name
9540                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9541                 *r = c;
9542                 SendToProgram(buf,cps);     // send egtbpath command for this format
9543             }
9544             if(*p == ',') p++; // read away comma to position for next format name
9545         }
9546 }
9547
9548 void
9549 InitChessProgram(cps, setup)
9550      ChessProgramState *cps;
9551      int setup; /* [HGM] needed to setup FRC opening position */
9552 {
9553     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9554     if (appData.noChessProgram) return;
9555     hintRequested = FALSE;
9556     bookRequested = FALSE;
9557
9558     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9559     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9560     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9561     if(cps->memSize) { /* [HGM] memory */
9562       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9563         SendToProgram(buf, cps);
9564     }
9565     SendEgtPath(cps); /* [HGM] EGT */
9566     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9567       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9568         SendToProgram(buf, cps);
9569     }
9570
9571     SendToProgram(cps->initString, cps);
9572     if (gameInfo.variant != VariantNormal &&
9573         gameInfo.variant != VariantLoadable
9574         /* [HGM] also send variant if board size non-standard */
9575         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9576                                             ) {
9577       char *v = VariantName(gameInfo.variant);
9578       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9579         /* [HGM] in protocol 1 we have to assume all variants valid */
9580         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9581         DisplayFatalError(buf, 0, 1);
9582         return;
9583       }
9584
9585       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9586       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9587       if( gameInfo.variant == VariantXiangqi )
9588            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9589       if( gameInfo.variant == VariantShogi )
9590            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9591       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9592            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9593       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9594           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9595            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9596       if( gameInfo.variant == VariantCourier )
9597            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9598       if( gameInfo.variant == VariantSuper )
9599            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9600       if( gameInfo.variant == VariantGreat )
9601            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9602       if( gameInfo.variant == VariantSChess )
9603            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9604       if( gameInfo.variant == VariantGrand )
9605            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9606
9607       if(overruled) {
9608         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9609                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9610            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9611            if(StrStr(cps->variants, b) == NULL) {
9612                // specific sized variant not known, check if general sizing allowed
9613                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9614                    if(StrStr(cps->variants, "boardsize") == NULL) {
9615                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9616                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9617                        DisplayFatalError(buf, 0, 1);
9618                        return;
9619                    }
9620                    /* [HGM] here we really should compare with the maximum supported board size */
9621                }
9622            }
9623       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9624       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9625       SendToProgram(buf, cps);
9626     }
9627     currentlyInitializedVariant = gameInfo.variant;
9628
9629     /* [HGM] send opening position in FRC to first engine */
9630     if(setup) {
9631           SendToProgram("force\n", cps);
9632           SendBoard(cps, 0);
9633           /* engine is now in force mode! Set flag to wake it up after first move. */
9634           setboardSpoiledMachineBlack = 1;
9635     }
9636
9637     if (cps->sendICS) {
9638       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9639       SendToProgram(buf, cps);
9640     }
9641     cps->maybeThinking = FALSE;
9642     cps->offeredDraw = 0;
9643     if (!appData.icsActive) {
9644         SendTimeControl(cps, movesPerSession, timeControl,
9645                         timeIncrement, appData.searchDepth,
9646                         searchTime);
9647     }
9648     if (appData.showThinking
9649         // [HGM] thinking: four options require thinking output to be sent
9650         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9651                                 ) {
9652         SendToProgram("post\n", cps);
9653     }
9654     SendToProgram("hard\n", cps);
9655     if (!appData.ponderNextMove) {
9656         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9657            it without being sure what state we are in first.  "hard"
9658            is not a toggle, so that one is OK.
9659          */
9660         SendToProgram("easy\n", cps);
9661     }
9662     if (cps->usePing) {
9663       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9664       SendToProgram(buf, cps);
9665     }
9666     cps->initDone = TRUE;
9667     ClearEngineOutputPane(cps == &second);
9668 }
9669
9670
9671 void
9672 StartChessProgram(cps)
9673      ChessProgramState *cps;
9674 {
9675     char buf[MSG_SIZ];
9676     int err;
9677
9678     if (appData.noChessProgram) return;
9679     cps->initDone = FALSE;
9680
9681     if (strcmp(cps->host, "localhost") == 0) {
9682         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9683     } else if (*appData.remoteShell == NULLCHAR) {
9684         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9685     } else {
9686         if (*appData.remoteUser == NULLCHAR) {
9687           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9688                     cps->program);
9689         } else {
9690           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9691                     cps->host, appData.remoteUser, cps->program);
9692         }
9693         err = StartChildProcess(buf, "", &cps->pr);
9694     }
9695
9696     if (err != 0) {
9697       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9698         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9699         if(cps != &first) return;
9700         appData.noChessProgram = TRUE;
9701         ThawUI();
9702         SetNCPMode();
9703 //      DisplayFatalError(buf, err, 1);
9704 //      cps->pr = NoProc;
9705 //      cps->isr = NULL;
9706         return;
9707     }
9708
9709     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9710     if (cps->protocolVersion > 1) {
9711       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9712       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9713       cps->comboCnt = 0;  //                and values of combo boxes
9714       SendToProgram(buf, cps);
9715     } else {
9716       SendToProgram("xboard\n", cps);
9717     }
9718 }
9719
9720 void
9721 TwoMachinesEventIfReady P((void))
9722 {
9723   static int curMess = 0;
9724   if (first.lastPing != first.lastPong) {
9725     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9726     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9727     return;
9728   }
9729   if (second.lastPing != second.lastPong) {
9730     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9731     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9732     return;
9733   }
9734   DisplayMessage("", ""); curMess = 0;
9735   ThawUI();
9736   TwoMachinesEvent();
9737 }
9738
9739 char *
9740 MakeName(char *template)
9741 {
9742     time_t clock;
9743     struct tm *tm;
9744     static char buf[MSG_SIZ];
9745     char *p = buf;
9746     int i;
9747
9748     clock = time((time_t *)NULL);
9749     tm = localtime(&clock);
9750
9751     while(*p++ = *template++) if(p[-1] == '%') {
9752         switch(*template++) {
9753           case 0:   *p = 0; return buf;
9754           case 'Y': i = tm->tm_year+1900; break;
9755           case 'y': i = tm->tm_year-100; break;
9756           case 'M': i = tm->tm_mon+1; break;
9757           case 'd': i = tm->tm_mday; break;
9758           case 'h': i = tm->tm_hour; break;
9759           case 'm': i = tm->tm_min; break;
9760           case 's': i = tm->tm_sec; break;
9761           default:  i = 0;
9762         }
9763         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9764     }
9765     return buf;
9766 }
9767
9768 int
9769 CountPlayers(char *p)
9770 {
9771     int n = 0;
9772     while(p = strchr(p, '\n')) p++, n++; // count participants
9773     return n;
9774 }
9775
9776 FILE *
9777 WriteTourneyFile(char *results, FILE *f)
9778 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9779     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9780     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9781         // create a file with tournament description
9782         fprintf(f, "-participants {%s}\n", appData.participants);
9783         fprintf(f, "-seedBase %d\n", appData.seedBase);
9784         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9785         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9786         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9787         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9788         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9789         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9790         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9791         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9792         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9793         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9794         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9795         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9796         if(searchTime > 0)
9797                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9798         else {
9799                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9800                 fprintf(f, "-tc %s\n", appData.timeControl);
9801                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9802         }
9803         fprintf(f, "-results \"%s\"\n", results);
9804     }
9805     return f;
9806 }
9807
9808 #define MAXENGINES 1000
9809 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9810
9811 void Substitute(char *participants, int expunge)
9812 {
9813     int i, changed, changes=0, nPlayers=0;
9814     char *p, *q, *r, buf[MSG_SIZ];
9815     if(participants == NULL) return;
9816     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9817     r = p = participants; q = appData.participants;
9818     while(*p && *p == *q) {
9819         if(*p == '\n') r = p+1, nPlayers++;
9820         p++; q++;
9821     }
9822     if(*p) { // difference
9823         while(*p && *p++ != '\n');
9824         while(*q && *q++ != '\n');
9825       changed = nPlayers;
9826         changes = 1 + (strcmp(p, q) != 0);
9827     }
9828     if(changes == 1) { // a single engine mnemonic was changed
9829         q = r; while(*q) nPlayers += (*q++ == '\n');
9830         p = buf; while(*r && (*p = *r++) != '\n') p++;
9831         *p = NULLCHAR;
9832         NamesToList(firstChessProgramNames, command, mnemonic);
9833         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9834         if(mnemonic[i]) { // The substitute is valid
9835             FILE *f;
9836             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9837                 flock(fileno(f), LOCK_EX);
9838                 ParseArgsFromFile(f);
9839                 fseek(f, 0, SEEK_SET);
9840                 FREE(appData.participants); appData.participants = participants;
9841                 if(expunge) { // erase results of replaced engine
9842                     int len = strlen(appData.results), w, b, dummy;
9843                     for(i=0; i<len; i++) {
9844                         Pairing(i, nPlayers, &w, &b, &dummy);
9845                         if((w == changed || b == changed) && appData.results[i] == '*') {
9846                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9847                             fclose(f);
9848                             return;
9849                         }
9850                     }
9851                     for(i=0; i<len; i++) {
9852                         Pairing(i, nPlayers, &w, &b, &dummy);
9853                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9854                     }
9855                 }
9856                 WriteTourneyFile(appData.results, f);
9857                 fclose(f); // release lock
9858                 return;
9859             }
9860         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9861     }
9862     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9863     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9864     free(participants);
9865     return;
9866 }
9867
9868 int
9869 CreateTourney(char *name)
9870 {
9871         FILE *f;
9872         if(matchMode && strcmp(name, appData.tourneyFile)) {
9873              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9874         }
9875         if(name[0] == NULLCHAR) {
9876             if(appData.participants[0])
9877                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9878             return 0;
9879         }
9880         f = fopen(name, "r");
9881         if(f) { // file exists
9882             ASSIGN(appData.tourneyFile, name);
9883             ParseArgsFromFile(f); // parse it
9884         } else {
9885             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9886             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9887                 DisplayError(_("Not enough participants"), 0);
9888                 return 0;
9889             }
9890             ASSIGN(appData.tourneyFile, name);
9891             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9892             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9893         }
9894         fclose(f);
9895         appData.noChessProgram = FALSE;
9896         appData.clockMode = TRUE;
9897         SetGNUMode();
9898         return 1;
9899 }
9900
9901 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9902 {
9903     char buf[MSG_SIZ], *p, *q;
9904     int i=1;
9905     while(*names) {
9906         p = names; q = buf;
9907         while(*p && *p != '\n') *q++ = *p++;
9908         *q = 0;
9909         if(engineList[i]) free(engineList[i]);
9910         engineList[i] = strdup(buf);
9911         if(*p == '\n') p++;
9912         TidyProgramName(engineList[i], "localhost", buf);
9913         if(engineMnemonic[i]) free(engineMnemonic[i]);
9914         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9915             strcat(buf, " (");
9916             sscanf(q + 8, "%s", buf + strlen(buf));
9917             strcat(buf, ")");
9918         }
9919         engineMnemonic[i] = strdup(buf);
9920         names = p; i++;
9921       if(i > MAXENGINES - 2) break;
9922     }
9923     engineList[i] = engineMnemonic[i] = NULL;
9924 }
9925
9926 // following implemented as macro to avoid type limitations
9927 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9928
9929 void SwapEngines(int n)
9930 {   // swap settings for first engine and other engine (so far only some selected options)
9931     int h;
9932     char *p;
9933     if(n == 0) return;
9934     SWAP(directory, p)
9935     SWAP(chessProgram, p)
9936     SWAP(isUCI, h)
9937     SWAP(hasOwnBookUCI, h)
9938     SWAP(protocolVersion, h)
9939     SWAP(reuse, h)
9940     SWAP(scoreIsAbsolute, h)
9941     SWAP(timeOdds, h)
9942     SWAP(logo, p)
9943     SWAP(pgnName, p)
9944     SWAP(pvSAN, h)
9945     SWAP(engOptions, p)
9946 }
9947
9948 void
9949 SetPlayer(int player)
9950 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9951     int i;
9952     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9953     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9954     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9955     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9956     if(mnemonic[i]) {
9957         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9958         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9959         appData.firstHasOwnBookUCI = !appData.defNoBook;
9960         ParseArgsFromString(buf);
9961     }
9962     free(engineName);
9963 }
9964
9965 int
9966 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9967 {   // determine players from game number
9968     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9969
9970     if(appData.tourneyType == 0) {
9971         roundsPerCycle = (nPlayers - 1) | 1;
9972         pairingsPerRound = nPlayers / 2;
9973     } else if(appData.tourneyType > 0) {
9974         roundsPerCycle = nPlayers - appData.tourneyType;
9975         pairingsPerRound = appData.tourneyType;
9976     }
9977     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9978     gamesPerCycle = gamesPerRound * roundsPerCycle;
9979     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9980     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9981     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9982     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9983     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9984     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9985
9986     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9987     if(appData.roundSync) *syncInterval = gamesPerRound;
9988
9989     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9990
9991     if(appData.tourneyType == 0) {
9992         if(curPairing == (nPlayers-1)/2 ) {
9993             *whitePlayer = curRound;
9994             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9995         } else {
9996             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9997             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9998             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9999             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
10000         }
10001     } else if(appData.tourneyType > 0) {
10002         *whitePlayer = curPairing;
10003         *blackPlayer = curRound + appData.tourneyType;
10004     }
10005
10006     // take care of white/black alternation per round. 
10007     // For cycles and games this is already taken care of by default, derived from matchGame!
10008     return curRound & 1;
10009 }
10010
10011 int
10012 NextTourneyGame(int nr, int *swapColors)
10013 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
10014     char *p, *q;
10015     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
10016     FILE *tf;
10017     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
10018     tf = fopen(appData.tourneyFile, "r");
10019     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
10020     ParseArgsFromFile(tf); fclose(tf);
10021     InitTimeControls(); // TC might be altered from tourney file
10022
10023     nPlayers = CountPlayers(appData.participants); // count participants
10024     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
10025     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
10026
10027     if(syncInterval) {
10028         p = q = appData.results;
10029         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
10030         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
10031             DisplayMessage(_("Waiting for other game(s)"),"");
10032             waitingForGame = TRUE;
10033             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
10034             return 0;
10035         }
10036         waitingForGame = FALSE;
10037     }
10038
10039     if(appData.tourneyType < 0) {
10040         if(nr>=0 && !pairingReceived) {
10041             char buf[1<<16];
10042             if(pairing.pr == NoProc) {
10043                 if(!appData.pairingEngine[0]) {
10044                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10045                     return 0;
10046                 }
10047                 StartChessProgram(&pairing); // starts the pairing engine
10048             }
10049             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10050             SendToProgram(buf, &pairing);
10051             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10052             SendToProgram(buf, &pairing);
10053             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10054         }
10055         pairingReceived = 0;                              // ... so we continue here 
10056         *swapColors = 0;
10057         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10058         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10059         matchGame = 1; roundNr = nr / syncInterval + 1;
10060     }
10061
10062     if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded
10063
10064     // redefine engines, engine dir, etc.
10065     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10066     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10067     SwapEngines(1);
10068     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10069     SwapEngines(1);         // and make that valid for second engine by swapping
10070     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10071     InitEngine(&second, 1);
10072     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10073     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10074     return 1;
10075 }
10076
10077 void
10078 NextMatchGame()
10079 {   // performs game initialization that does not invoke engines, and then tries to start the game
10080     int res, firstWhite, swapColors = 0;
10081     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10082     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10083     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10084     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10085     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10086     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10087     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10088     Reset(FALSE, first.pr != NoProc);
10089     res = LoadGameOrPosition(matchGame); // setup game
10090     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10091     if(!res) return; // abort when bad game/pos file
10092     TwoMachinesEvent();
10093 }
10094
10095 void UserAdjudicationEvent( int result )
10096 {
10097     ChessMove gameResult = GameIsDrawn;
10098
10099     if( result > 0 ) {
10100         gameResult = WhiteWins;
10101     }
10102     else if( result < 0 ) {
10103         gameResult = BlackWins;
10104     }
10105
10106     if( gameMode == TwoMachinesPlay ) {
10107         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10108     }
10109 }
10110
10111
10112 // [HGM] save: calculate checksum of game to make games easily identifiable
10113 int StringCheckSum(char *s)
10114 {
10115         int i = 0;
10116         if(s==NULL) return 0;
10117         while(*s) i = i*259 + *s++;
10118         return i;
10119 }
10120
10121 int GameCheckSum()
10122 {
10123         int i, sum=0;
10124         for(i=backwardMostMove; i<forwardMostMove; i++) {
10125                 sum += pvInfoList[i].depth;
10126                 sum += StringCheckSum(parseList[i]);
10127                 sum += StringCheckSum(commentList[i]);
10128                 sum *= 261;
10129         }
10130         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10131         return sum + StringCheckSum(commentList[i]);
10132 } // end of save patch
10133
10134 void
10135 GameEnds(result, resultDetails, whosays)
10136      ChessMove result;
10137      char *resultDetails;
10138      int whosays;
10139 {
10140     GameMode nextGameMode;
10141     int isIcsGame;
10142     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10143
10144     if(endingGame) return; /* [HGM] crash: forbid recursion */
10145     endingGame = 1;
10146     if(twoBoards) { // [HGM] dual: switch back to one board
10147         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10148         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10149     }
10150     if (appData.debugMode) {
10151       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10152               result, resultDetails ? resultDetails : "(null)", whosays);
10153     }
10154
10155     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10156
10157     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10158         /* If we are playing on ICS, the server decides when the
10159            game is over, but the engine can offer to draw, claim
10160            a draw, or resign.
10161          */
10162 #if ZIPPY
10163         if (appData.zippyPlay && first.initDone) {
10164             if (result == GameIsDrawn) {
10165                 /* In case draw still needs to be claimed */
10166                 SendToICS(ics_prefix);
10167                 SendToICS("draw\n");
10168             } else if (StrCaseStr(resultDetails, "resign")) {
10169                 SendToICS(ics_prefix);
10170                 SendToICS("resign\n");
10171             }
10172         }
10173 #endif
10174         endingGame = 0; /* [HGM] crash */
10175         return;
10176     }
10177
10178     /* If we're loading the game from a file, stop */
10179     if (whosays == GE_FILE) {
10180       (void) StopLoadGameTimer();
10181       gameFileFP = NULL;
10182     }
10183
10184     /* Cancel draw offers */
10185     first.offeredDraw = second.offeredDraw = 0;
10186
10187     /* If this is an ICS game, only ICS can really say it's done;
10188        if not, anyone can. */
10189     isIcsGame = (gameMode == IcsPlayingWhite ||
10190                  gameMode == IcsPlayingBlack ||
10191                  gameMode == IcsObserving    ||
10192                  gameMode == IcsExamining);
10193
10194     if (!isIcsGame || whosays == GE_ICS) {
10195         /* OK -- not an ICS game, or ICS said it was done */
10196         StopClocks();
10197         if (!isIcsGame && !appData.noChessProgram)
10198           SetUserThinkingEnables();
10199
10200         /* [HGM] if a machine claims the game end we verify this claim */
10201         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10202             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10203                 char claimer;
10204                 ChessMove trueResult = (ChessMove) -1;
10205
10206                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10207                                             first.twoMachinesColor[0] :
10208                                             second.twoMachinesColor[0] ;
10209
10210                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10211                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10212                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10213                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10214                 } else
10215                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10216                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10217                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10218                 } else
10219                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10220                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10221                 }
10222
10223                 // now verify win claims, but not in drop games, as we don't understand those yet
10224                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10225                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10226                     (result == WhiteWins && claimer == 'w' ||
10227                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10228                       if (appData.debugMode) {
10229                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10230                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10231                       }
10232                       if(result != trueResult) {
10233                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10234                               result = claimer == 'w' ? BlackWins : WhiteWins;
10235                               resultDetails = buf;
10236                       }
10237                 } else
10238                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10239                     && (forwardMostMove <= backwardMostMove ||
10240                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10241                         (claimer=='b')==(forwardMostMove&1))
10242                                                                                   ) {
10243                       /* [HGM] verify: draws that were not flagged are false claims */
10244                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10245                       result = claimer == 'w' ? BlackWins : WhiteWins;
10246                       resultDetails = buf;
10247                 }
10248                 /* (Claiming a loss is accepted no questions asked!) */
10249             }
10250             /* [HGM] bare: don't allow bare King to win */
10251             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10252                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10253                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10254                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10255                && result != GameIsDrawn)
10256             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10257                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10258                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10259                         if(p >= 0 && p <= (int)WhiteKing) k++;
10260                 }
10261                 if (appData.debugMode) {
10262                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10263                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10264                 }
10265                 if(k <= 1) {
10266                         result = GameIsDrawn;
10267                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10268                         resultDetails = buf;
10269                 }
10270             }
10271         }
10272
10273
10274         if(serverMoves != NULL && !loadFlag) { char c = '=';
10275             if(result==WhiteWins) c = '+';
10276             if(result==BlackWins) c = '-';
10277             if(resultDetails != NULL)
10278                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10279         }
10280         if (resultDetails != NULL) {
10281             gameInfo.result = result;
10282             gameInfo.resultDetails = StrSave(resultDetails);
10283
10284             /* display last move only if game was not loaded from file */
10285             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10286                 DisplayMove(currentMove - 1);
10287
10288             if (forwardMostMove != 0) {
10289                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10290                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10291                                                                 ) {
10292                     if (*appData.saveGameFile != NULLCHAR) {
10293                         SaveGameToFile(appData.saveGameFile, TRUE);
10294                     } else if (appData.autoSaveGames) {
10295                         AutoSaveGame();
10296                     }
10297                     if (*appData.savePositionFile != NULLCHAR) {
10298                         SavePositionToFile(appData.savePositionFile);
10299                     }
10300                 }
10301             }
10302
10303             /* Tell program how game ended in case it is learning */
10304             /* [HGM] Moved this to after saving the PGN, just in case */
10305             /* engine died and we got here through time loss. In that */
10306             /* case we will get a fatal error writing the pipe, which */
10307             /* would otherwise lose us the PGN.                       */
10308             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10309             /* output during GameEnds should never be fatal anymore   */
10310             if (gameMode == MachinePlaysWhite ||
10311                 gameMode == MachinePlaysBlack ||
10312                 gameMode == TwoMachinesPlay ||
10313                 gameMode == IcsPlayingWhite ||
10314                 gameMode == IcsPlayingBlack ||
10315                 gameMode == BeginningOfGame) {
10316                 char buf[MSG_SIZ];
10317                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10318                         resultDetails);
10319                 if (first.pr != NoProc) {
10320                     SendToProgram(buf, &first);
10321                 }
10322                 if (second.pr != NoProc &&
10323                     gameMode == TwoMachinesPlay) {
10324                     SendToProgram(buf, &second);
10325                 }
10326             }
10327         }
10328
10329         if (appData.icsActive) {
10330             if (appData.quietPlay &&
10331                 (gameMode == IcsPlayingWhite ||
10332                  gameMode == IcsPlayingBlack)) {
10333                 SendToICS(ics_prefix);
10334                 SendToICS("set shout 1\n");
10335             }
10336             nextGameMode = IcsIdle;
10337             ics_user_moved = FALSE;
10338             /* clean up premove.  It's ugly when the game has ended and the
10339              * premove highlights are still on the board.
10340              */
10341             if (gotPremove) {
10342               gotPremove = FALSE;
10343               ClearPremoveHighlights();
10344               DrawPosition(FALSE, boards[currentMove]);
10345             }
10346             if (whosays == GE_ICS) {
10347                 switch (result) {
10348                 case WhiteWins:
10349                     if (gameMode == IcsPlayingWhite)
10350                         PlayIcsWinSound();
10351                     else if(gameMode == IcsPlayingBlack)
10352                         PlayIcsLossSound();
10353                     break;
10354                 case BlackWins:
10355                     if (gameMode == IcsPlayingBlack)
10356                         PlayIcsWinSound();
10357                     else if(gameMode == IcsPlayingWhite)
10358                         PlayIcsLossSound();
10359                     break;
10360                 case GameIsDrawn:
10361                     PlayIcsDrawSound();
10362                     break;
10363                 default:
10364                     PlayIcsUnfinishedSound();
10365                 }
10366             }
10367         } else if (gameMode == EditGame ||
10368                    gameMode == PlayFromGameFile ||
10369                    gameMode == AnalyzeMode ||
10370                    gameMode == AnalyzeFile) {
10371             nextGameMode = gameMode;
10372         } else {
10373             nextGameMode = EndOfGame;
10374         }
10375         pausing = FALSE;
10376         ModeHighlight();
10377     } else {
10378         nextGameMode = gameMode;
10379     }
10380
10381     if (appData.noChessProgram) {
10382         gameMode = nextGameMode;
10383         ModeHighlight();
10384         endingGame = 0; /* [HGM] crash */
10385         return;
10386     }
10387
10388     if (first.reuse) {
10389         /* Put first chess program into idle state */
10390         if (first.pr != NoProc &&
10391             (gameMode == MachinePlaysWhite ||
10392              gameMode == MachinePlaysBlack ||
10393              gameMode == TwoMachinesPlay ||
10394              gameMode == IcsPlayingWhite ||
10395              gameMode == IcsPlayingBlack ||
10396              gameMode == BeginningOfGame)) {
10397             SendToProgram("force\n", &first);
10398             if (first.usePing) {
10399               char buf[MSG_SIZ];
10400               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10401               SendToProgram(buf, &first);
10402             }
10403         }
10404     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10405         /* Kill off first chess program */
10406         if (first.isr != NULL)
10407           RemoveInputSource(first.isr);
10408         first.isr = NULL;
10409
10410         if (first.pr != NoProc) {
10411             ExitAnalyzeMode();
10412             DoSleep( appData.delayBeforeQuit );
10413             SendToProgram("quit\n", &first);
10414             DoSleep( appData.delayAfterQuit );
10415             DestroyChildProcess(first.pr, first.useSigterm);
10416         }
10417         first.pr = NoProc;
10418     }
10419     if (second.reuse) {
10420         /* Put second chess program into idle state */
10421         if (second.pr != NoProc &&
10422             gameMode == TwoMachinesPlay) {
10423             SendToProgram("force\n", &second);
10424             if (second.usePing) {
10425               char buf[MSG_SIZ];
10426               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10427               SendToProgram(buf, &second);
10428             }
10429         }
10430     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10431         /* Kill off second chess program */
10432         if (second.isr != NULL)
10433           RemoveInputSource(second.isr);
10434         second.isr = NULL;
10435
10436         if (second.pr != NoProc) {
10437             DoSleep( appData.delayBeforeQuit );
10438             SendToProgram("quit\n", &second);
10439             DoSleep( appData.delayAfterQuit );
10440             DestroyChildProcess(second.pr, second.useSigterm);
10441         }
10442         second.pr = NoProc;
10443     }
10444
10445     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10446         char resChar = '=';
10447         switch (result) {
10448         case WhiteWins:
10449           resChar = '+';
10450           if (first.twoMachinesColor[0] == 'w') {
10451             first.matchWins++;
10452           } else {
10453             second.matchWins++;
10454           }
10455           break;
10456         case BlackWins:
10457           resChar = '-';
10458           if (first.twoMachinesColor[0] == 'b') {
10459             first.matchWins++;
10460           } else {
10461             second.matchWins++;
10462           }
10463           break;
10464         case GameUnfinished:
10465           resChar = ' ';
10466         default:
10467           break;
10468         }
10469
10470         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10471         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10472             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10473             ReserveGame(nextGame, resChar); // sets nextGame
10474             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10475             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10476         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10477
10478         if (nextGame <= appData.matchGames && !abortMatch) {
10479             gameMode = nextGameMode;
10480             matchGame = nextGame; // this will be overruled in tourney mode!
10481             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10482             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10483             endingGame = 0; /* [HGM] crash */
10484             return;
10485         } else {
10486             gameMode = nextGameMode;
10487             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10488                      first.tidy, second.tidy,
10489                      first.matchWins, second.matchWins,
10490                      appData.matchGames - (first.matchWins + second.matchWins));
10491             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10492             if(strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10493             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10494             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10495                 first.twoMachinesColor = "black\n";
10496                 second.twoMachinesColor = "white\n";
10497             } else {
10498                 first.twoMachinesColor = "white\n";
10499                 second.twoMachinesColor = "black\n";
10500             }
10501         }
10502     }
10503     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10504         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10505       ExitAnalyzeMode();
10506     gameMode = nextGameMode;
10507     ModeHighlight();
10508     endingGame = 0;  /* [HGM] crash */
10509     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10510         if(matchMode == TRUE) { // match through command line: exit with or without popup
10511             if(ranking) {
10512                 ToNrEvent(forwardMostMove);
10513                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10514                 else ExitEvent(0);
10515             } else DisplayFatalError(buf, 0, 0);
10516         } else { // match through menu; just stop, with or without popup
10517             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10518             ModeHighlight();
10519             if(ranking){
10520                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10521             } else DisplayNote(buf);
10522       }
10523       if(ranking) free(ranking);
10524     }
10525 }
10526
10527 /* Assumes program was just initialized (initString sent).
10528    Leaves program in force mode. */
10529 void
10530 FeedMovesToProgram(cps, upto)
10531      ChessProgramState *cps;
10532      int upto;
10533 {
10534     int i;
10535
10536     if (appData.debugMode)
10537       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10538               startedFromSetupPosition ? "position and " : "",
10539               backwardMostMove, upto, cps->which);
10540     if(currentlyInitializedVariant != gameInfo.variant) {
10541       char buf[MSG_SIZ];
10542         // [HGM] variantswitch: make engine aware of new variant
10543         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10544                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10545         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10546         SendToProgram(buf, cps);
10547         currentlyInitializedVariant = gameInfo.variant;
10548     }
10549     SendToProgram("force\n", cps);
10550     if (startedFromSetupPosition) {
10551         SendBoard(cps, backwardMostMove);
10552     if (appData.debugMode) {
10553         fprintf(debugFP, "feedMoves\n");
10554     }
10555     }
10556     for (i = backwardMostMove; i < upto; i++) {
10557         SendMoveToProgram(i, cps);
10558     }
10559 }
10560
10561
10562 int
10563 ResurrectChessProgram()
10564 {
10565      /* The chess program may have exited.
10566         If so, restart it and feed it all the moves made so far. */
10567     static int doInit = 0;
10568
10569     if (appData.noChessProgram) return 1;
10570
10571     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10572         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10573         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10574         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10575     } else {
10576         if (first.pr != NoProc) return 1;
10577         StartChessProgram(&first);
10578     }
10579     InitChessProgram(&first, FALSE);
10580     FeedMovesToProgram(&first, currentMove);
10581
10582     if (!first.sendTime) {
10583         /* can't tell gnuchess what its clock should read,
10584            so we bow to its notion. */
10585         ResetClocks();
10586         timeRemaining[0][currentMove] = whiteTimeRemaining;
10587         timeRemaining[1][currentMove] = blackTimeRemaining;
10588     }
10589
10590     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10591                 appData.icsEngineAnalyze) && first.analysisSupport) {
10592       SendToProgram("analyze\n", &first);
10593       first.analyzing = TRUE;
10594     }
10595     return 1;
10596 }
10597
10598 /*
10599  * Button procedures
10600  */
10601 void
10602 Reset(redraw, init)
10603      int redraw, init;
10604 {
10605     int i;
10606
10607     if (appData.debugMode) {
10608         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10609                 redraw, init, gameMode);
10610     }
10611     CleanupTail(); // [HGM] vari: delete any stored variations
10612     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10613     pausing = pauseExamInvalid = FALSE;
10614     startedFromSetupPosition = blackPlaysFirst = FALSE;
10615     firstMove = TRUE;
10616     whiteFlag = blackFlag = FALSE;
10617     userOfferedDraw = FALSE;
10618     hintRequested = bookRequested = FALSE;
10619     first.maybeThinking = FALSE;
10620     second.maybeThinking = FALSE;
10621     first.bookSuspend = FALSE; // [HGM] book
10622     second.bookSuspend = FALSE;
10623     thinkOutput[0] = NULLCHAR;
10624     lastHint[0] = NULLCHAR;
10625     ClearGameInfo(&gameInfo);
10626     gameInfo.variant = StringToVariant(appData.variant);
10627     ics_user_moved = ics_clock_paused = FALSE;
10628     ics_getting_history = H_FALSE;
10629     ics_gamenum = -1;
10630     white_holding[0] = black_holding[0] = NULLCHAR;
10631     ClearProgramStats();
10632     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10633
10634     ResetFrontEnd();
10635     ClearHighlights();
10636     flipView = appData.flipView;
10637     ClearPremoveHighlights();
10638     gotPremove = FALSE;
10639     alarmSounded = FALSE;
10640
10641     GameEnds(EndOfFile, NULL, GE_PLAYER);
10642     if(appData.serverMovesName != NULL) {
10643         /* [HGM] prepare to make moves file for broadcasting */
10644         clock_t t = clock();
10645         if(serverMoves != NULL) fclose(serverMoves);
10646         serverMoves = fopen(appData.serverMovesName, "r");
10647         if(serverMoves != NULL) {
10648             fclose(serverMoves);
10649             /* delay 15 sec before overwriting, so all clients can see end */
10650             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10651         }
10652         serverMoves = fopen(appData.serverMovesName, "w");
10653     }
10654
10655     ExitAnalyzeMode();
10656     gameMode = BeginningOfGame;
10657     ModeHighlight();
10658     if(appData.icsActive) gameInfo.variant = VariantNormal;
10659     currentMove = forwardMostMove = backwardMostMove = 0;
10660     InitPosition(redraw);
10661     for (i = 0; i < MAX_MOVES; i++) {
10662         if (commentList[i] != NULL) {
10663             free(commentList[i]);
10664             commentList[i] = NULL;
10665         }
10666     }
10667     ResetClocks();
10668     timeRemaining[0][0] = whiteTimeRemaining;
10669     timeRemaining[1][0] = blackTimeRemaining;
10670
10671     if (first.pr == NoProc) {
10672         StartChessProgram(&first);
10673     }
10674     if (init) {
10675             InitChessProgram(&first, startedFromSetupPosition);
10676     }
10677     DisplayTitle("");
10678     DisplayMessage("", "");
10679     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10680     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10681 }
10682
10683 void
10684 AutoPlayGameLoop()
10685 {
10686     for (;;) {
10687         if (!AutoPlayOneMove())
10688           return;
10689         if (matchMode || appData.timeDelay == 0)
10690           continue;
10691         if (appData.timeDelay < 0)
10692           return;
10693         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10694         break;
10695     }
10696 }
10697
10698
10699 int
10700 AutoPlayOneMove()
10701 {
10702     int fromX, fromY, toX, toY;
10703
10704     if (appData.debugMode) {
10705       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10706     }
10707
10708     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10709       return FALSE;
10710
10711     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10712       pvInfoList[currentMove].depth = programStats.depth;
10713       pvInfoList[currentMove].score = programStats.score;
10714       pvInfoList[currentMove].time  = 0;
10715       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10716     }
10717
10718     if (currentMove >= forwardMostMove) {
10719       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10720 //      gameMode = EndOfGame;
10721 //      ModeHighlight();
10722
10723       /* [AS] Clear current move marker at the end of a game */
10724       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10725
10726       return FALSE;
10727     }
10728
10729     toX = moveList[currentMove][2] - AAA;
10730     toY = moveList[currentMove][3] - ONE;
10731
10732     if (moveList[currentMove][1] == '@') {
10733         if (appData.highlightLastMove) {
10734             SetHighlights(-1, -1, toX, toY);
10735         }
10736     } else {
10737         fromX = moveList[currentMove][0] - AAA;
10738         fromY = moveList[currentMove][1] - ONE;
10739
10740         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10741
10742         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10743
10744         if (appData.highlightLastMove) {
10745             SetHighlights(fromX, fromY, toX, toY);
10746         }
10747     }
10748     DisplayMove(currentMove);
10749     SendMoveToProgram(currentMove++, &first);
10750     DisplayBothClocks();
10751     DrawPosition(FALSE, boards[currentMove]);
10752     // [HGM] PV info: always display, routine tests if empty
10753     DisplayComment(currentMove - 1, commentList[currentMove]);
10754     return TRUE;
10755 }
10756
10757
10758 int
10759 LoadGameOneMove(readAhead)
10760      ChessMove readAhead;
10761 {
10762     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10763     char promoChar = NULLCHAR;
10764     ChessMove moveType;
10765     char move[MSG_SIZ];
10766     char *p, *q;
10767
10768     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10769         gameMode != AnalyzeMode && gameMode != Training) {
10770         gameFileFP = NULL;
10771         return FALSE;
10772     }
10773
10774     yyboardindex = forwardMostMove;
10775     if (readAhead != EndOfFile) {
10776       moveType = readAhead;
10777     } else {
10778       if (gameFileFP == NULL)
10779           return FALSE;
10780       moveType = (ChessMove) Myylex();
10781     }
10782
10783     done = FALSE;
10784     switch (moveType) {
10785       case Comment:
10786         if (appData.debugMode)
10787           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10788         p = yy_text;
10789
10790         /* append the comment but don't display it */
10791         AppendComment(currentMove, p, FALSE);
10792         return TRUE;
10793
10794       case WhiteCapturesEnPassant:
10795       case BlackCapturesEnPassant:
10796       case WhitePromotion:
10797       case BlackPromotion:
10798       case WhiteNonPromotion:
10799       case BlackNonPromotion:
10800       case NormalMove:
10801       case WhiteKingSideCastle:
10802       case WhiteQueenSideCastle:
10803       case BlackKingSideCastle:
10804       case BlackQueenSideCastle:
10805       case WhiteKingSideCastleWild:
10806       case WhiteQueenSideCastleWild:
10807       case BlackKingSideCastleWild:
10808       case BlackQueenSideCastleWild:
10809       /* PUSH Fabien */
10810       case WhiteHSideCastleFR:
10811       case WhiteASideCastleFR:
10812       case BlackHSideCastleFR:
10813       case BlackASideCastleFR:
10814       /* POP Fabien */
10815         if (appData.debugMode)
10816           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10817         fromX = currentMoveString[0] - AAA;
10818         fromY = currentMoveString[1] - ONE;
10819         toX = currentMoveString[2] - AAA;
10820         toY = currentMoveString[3] - ONE;
10821         promoChar = currentMoveString[4];
10822         break;
10823
10824       case WhiteDrop:
10825       case BlackDrop:
10826         if (appData.debugMode)
10827           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10828         fromX = moveType == WhiteDrop ?
10829           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10830         (int) CharToPiece(ToLower(currentMoveString[0]));
10831         fromY = DROP_RANK;
10832         toX = currentMoveString[2] - AAA;
10833         toY = currentMoveString[3] - ONE;
10834         break;
10835
10836       case WhiteWins:
10837       case BlackWins:
10838       case GameIsDrawn:
10839       case GameUnfinished:
10840         if (appData.debugMode)
10841           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10842         p = strchr(yy_text, '{');
10843         if (p == NULL) p = strchr(yy_text, '(');
10844         if (p == NULL) {
10845             p = yy_text;
10846             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10847         } else {
10848             q = strchr(p, *p == '{' ? '}' : ')');
10849             if (q != NULL) *q = NULLCHAR;
10850             p++;
10851         }
10852         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10853         GameEnds(moveType, p, GE_FILE);
10854         done = TRUE;
10855         if (cmailMsgLoaded) {
10856             ClearHighlights();
10857             flipView = WhiteOnMove(currentMove);
10858             if (moveType == GameUnfinished) flipView = !flipView;
10859             if (appData.debugMode)
10860               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10861         }
10862         break;
10863
10864       case EndOfFile:
10865         if (appData.debugMode)
10866           fprintf(debugFP, "Parser hit end of file\n");
10867         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10868           case MT_NONE:
10869           case MT_CHECK:
10870             break;
10871           case MT_CHECKMATE:
10872           case MT_STAINMATE:
10873             if (WhiteOnMove(currentMove)) {
10874                 GameEnds(BlackWins, "Black mates", GE_FILE);
10875             } else {
10876                 GameEnds(WhiteWins, "White mates", GE_FILE);
10877             }
10878             break;
10879           case MT_STALEMATE:
10880             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10881             break;
10882         }
10883         done = TRUE;
10884         break;
10885
10886       case MoveNumberOne:
10887         if (lastLoadGameStart == GNUChessGame) {
10888             /* GNUChessGames have numbers, but they aren't move numbers */
10889             if (appData.debugMode)
10890               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10891                       yy_text, (int) moveType);
10892             return LoadGameOneMove(EndOfFile); /* tail recursion */
10893         }
10894         /* else fall thru */
10895
10896       case XBoardGame:
10897       case GNUChessGame:
10898       case PGNTag:
10899         /* Reached start of next game in file */
10900         if (appData.debugMode)
10901           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10902         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10903           case MT_NONE:
10904           case MT_CHECK:
10905             break;
10906           case MT_CHECKMATE:
10907           case MT_STAINMATE:
10908             if (WhiteOnMove(currentMove)) {
10909                 GameEnds(BlackWins, "Black mates", GE_FILE);
10910             } else {
10911                 GameEnds(WhiteWins, "White mates", GE_FILE);
10912             }
10913             break;
10914           case MT_STALEMATE:
10915             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10916             break;
10917         }
10918         done = TRUE;
10919         break;
10920
10921       case PositionDiagram:     /* should not happen; ignore */
10922       case ElapsedTime:         /* ignore */
10923       case NAG:                 /* ignore */
10924         if (appData.debugMode)
10925           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10926                   yy_text, (int) moveType);
10927         return LoadGameOneMove(EndOfFile); /* tail recursion */
10928
10929       case IllegalMove:
10930         if (appData.testLegality) {
10931             if (appData.debugMode)
10932               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10933             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10934                     (forwardMostMove / 2) + 1,
10935                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10936             DisplayError(move, 0);
10937             done = TRUE;
10938         } else {
10939             if (appData.debugMode)
10940               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10941                       yy_text, currentMoveString);
10942             fromX = currentMoveString[0] - AAA;
10943             fromY = currentMoveString[1] - ONE;
10944             toX = currentMoveString[2] - AAA;
10945             toY = currentMoveString[3] - ONE;
10946             promoChar = currentMoveString[4];
10947         }
10948         break;
10949
10950       case AmbiguousMove:
10951         if (appData.debugMode)
10952           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10953         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10954                 (forwardMostMove / 2) + 1,
10955                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10956         DisplayError(move, 0);
10957         done = TRUE;
10958         break;
10959
10960       default:
10961       case ImpossibleMove:
10962         if (appData.debugMode)
10963           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10964         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10965                 (forwardMostMove / 2) + 1,
10966                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10967         DisplayError(move, 0);
10968         done = TRUE;
10969         break;
10970     }
10971
10972     if (done) {
10973         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10974             DrawPosition(FALSE, boards[currentMove]);
10975             DisplayBothClocks();
10976             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10977               DisplayComment(currentMove - 1, commentList[currentMove]);
10978         }
10979         (void) StopLoadGameTimer();
10980         gameFileFP = NULL;
10981         cmailOldMove = forwardMostMove;
10982         return FALSE;
10983     } else {
10984         /* currentMoveString is set as a side-effect of yylex */
10985
10986         thinkOutput[0] = NULLCHAR;
10987         MakeMove(fromX, fromY, toX, toY, promoChar);
10988         currentMove = forwardMostMove;
10989         return TRUE;
10990     }
10991 }
10992
10993 /* Load the nth game from the given file */
10994 int
10995 LoadGameFromFile(filename, n, title, useList)
10996      char *filename;
10997      int n;
10998      char *title;
10999      /*Boolean*/ int useList;
11000 {
11001     FILE *f;
11002     char buf[MSG_SIZ];
11003
11004     if (strcmp(filename, "-") == 0) {
11005         f = stdin;
11006         title = "stdin";
11007     } else {
11008         f = fopen(filename, "rb");
11009         if (f == NULL) {
11010           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
11011             DisplayError(buf, errno);
11012             return FALSE;
11013         }
11014     }
11015     if (fseek(f, 0, 0) == -1) {
11016         /* f is not seekable; probably a pipe */
11017         useList = FALSE;
11018     }
11019     if (useList && n == 0) {
11020         int error = GameListBuild(f);
11021         if (error) {
11022             DisplayError(_("Cannot build game list"), error);
11023         } else if (!ListEmpty(&gameList) &&
11024                    ((ListGame *) gameList.tailPred)->number > 1) {
11025             GameListPopUp(f, title);
11026             return TRUE;
11027         }
11028         GameListDestroy();
11029         n = 1;
11030     }
11031     if (n == 0) n = 1;
11032     return LoadGame(f, n, title, FALSE);
11033 }
11034
11035
11036 void
11037 MakeRegisteredMove()
11038 {
11039     int fromX, fromY, toX, toY;
11040     char promoChar;
11041     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11042         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11043           case CMAIL_MOVE:
11044           case CMAIL_DRAW:
11045             if (appData.debugMode)
11046               fprintf(debugFP, "Restoring %s for game %d\n",
11047                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11048
11049             thinkOutput[0] = NULLCHAR;
11050             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11051             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11052             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11053             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11054             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11055             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11056             MakeMove(fromX, fromY, toX, toY, promoChar);
11057             ShowMove(fromX, fromY, toX, toY);
11058
11059             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11060               case MT_NONE:
11061               case MT_CHECK:
11062                 break;
11063
11064               case MT_CHECKMATE:
11065               case MT_STAINMATE:
11066                 if (WhiteOnMove(currentMove)) {
11067                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11068                 } else {
11069                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11070                 }
11071                 break;
11072
11073               case MT_STALEMATE:
11074                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11075                 break;
11076             }
11077
11078             break;
11079
11080           case CMAIL_RESIGN:
11081             if (WhiteOnMove(currentMove)) {
11082                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11083             } else {
11084                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11085             }
11086             break;
11087
11088           case CMAIL_ACCEPT:
11089             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11090             break;
11091
11092           default:
11093             break;
11094         }
11095     }
11096
11097     return;
11098 }
11099
11100 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11101 int
11102 CmailLoadGame(f, gameNumber, title, useList)
11103      FILE *f;
11104      int gameNumber;
11105      char *title;
11106      int useList;
11107 {
11108     int retVal;
11109
11110     if (gameNumber > nCmailGames) {
11111         DisplayError(_("No more games in this message"), 0);
11112         return FALSE;
11113     }
11114     if (f == lastLoadGameFP) {
11115         int offset = gameNumber - lastLoadGameNumber;
11116         if (offset == 0) {
11117             cmailMsg[0] = NULLCHAR;
11118             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11119                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11120                 nCmailMovesRegistered--;
11121             }
11122             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11123             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11124                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11125             }
11126         } else {
11127             if (! RegisterMove()) return FALSE;
11128         }
11129     }
11130
11131     retVal = LoadGame(f, gameNumber, title, useList);
11132
11133     /* Make move registered during previous look at this game, if any */
11134     MakeRegisteredMove();
11135
11136     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11137         commentList[currentMove]
11138           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11139         DisplayComment(currentMove - 1, commentList[currentMove]);
11140     }
11141
11142     return retVal;
11143 }
11144
11145 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11146 int
11147 ReloadGame(offset)
11148      int offset;
11149 {
11150     int gameNumber = lastLoadGameNumber + offset;
11151     if (lastLoadGameFP == NULL) {
11152         DisplayError(_("No game has been loaded yet"), 0);
11153         return FALSE;
11154     }
11155     if (gameNumber <= 0) {
11156         DisplayError(_("Can't back up any further"), 0);
11157         return FALSE;
11158     }
11159     if (cmailMsgLoaded) {
11160         return CmailLoadGame(lastLoadGameFP, gameNumber,
11161                              lastLoadGameTitle, lastLoadGameUseList);
11162     } else {
11163         return LoadGame(lastLoadGameFP, gameNumber,
11164                         lastLoadGameTitle, lastLoadGameUseList);
11165     }
11166 }
11167
11168 int keys[EmptySquare+1];
11169
11170 int
11171 PositionMatches(Board b1, Board b2)
11172 {
11173     int r, f, sum=0;
11174     switch(appData.searchMode) {
11175         case 1: return CompareWithRights(b1, b2);
11176         case 2:
11177             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11178                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11179             }
11180             return TRUE;
11181         case 3:
11182             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11183               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11184                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11185             }
11186             return sum==0;
11187         case 4:
11188             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11189                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11190             }
11191             return sum==0;
11192     }
11193     return TRUE;
11194 }
11195
11196 #define Q_PROMO  4
11197 #define Q_EP     3
11198 #define Q_BCASTL 2
11199 #define Q_WCASTL 1
11200
11201 int pieceList[256], quickBoard[256];
11202 ChessSquare pieceType[256] = { EmptySquare };
11203 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11204 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11205 int soughtTotal, turn;
11206 Boolean epOK, flipSearch;
11207
11208 typedef struct {
11209     unsigned char piece, to;
11210 } Move;
11211
11212 #define DSIZE (250000)
11213
11214 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11215 Move *moveDatabase = initialSpace;
11216 unsigned int movePtr, dataSize = DSIZE;
11217
11218 int MakePieceList(Board board, int *counts)
11219 {
11220     int r, f, n=Q_PROMO, total=0;
11221     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11222     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11223         int sq = f + (r<<4);
11224         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11225             quickBoard[sq] = ++n;
11226             pieceList[n] = sq;
11227             pieceType[n] = board[r][f];
11228             counts[board[r][f]]++;
11229             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11230             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11231             total++;
11232         }
11233     }
11234     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11235     return total;
11236 }
11237
11238 void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11239 {
11240     int sq = fromX + (fromY<<4);
11241     int piece = quickBoard[sq];
11242     quickBoard[sq] = 0;
11243     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11244     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11245         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11246         moveDatabase[movePtr++].piece = Q_WCASTL;
11247         quickBoard[sq] = piece;
11248         piece = quickBoard[from]; quickBoard[from] = 0;
11249         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11250     } else
11251     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11252         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11253         moveDatabase[movePtr++].piece = Q_BCASTL;
11254         quickBoard[sq] = piece;
11255         piece = quickBoard[from]; quickBoard[from] = 0;
11256         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11257     } else
11258     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11259         quickBoard[(fromY<<4)+toX] = 0;
11260         moveDatabase[movePtr].piece = Q_EP;
11261         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11262         moveDatabase[movePtr].to = sq;
11263     } else
11264     if(promoPiece != pieceType[piece]) {
11265         moveDatabase[movePtr++].piece = Q_PROMO;
11266         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11267     }
11268     moveDatabase[movePtr].piece = piece;
11269     quickBoard[sq] = piece;
11270     movePtr++;
11271 }
11272
11273 int PackGame(Board board)
11274 {
11275     Move *newSpace = NULL;
11276     moveDatabase[movePtr].piece = 0; // terminate previous game
11277     if(movePtr > dataSize) {
11278         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11279         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11280         if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move));
11281         if(newSpace) {
11282             int i;
11283             Move *p = moveDatabase, *q = newSpace;
11284             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11285             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11286             moveDatabase = newSpace;
11287         } else { // calloc failed, we must be out of memory. Too bad...
11288             dataSize = 0; // prevent calloc events for all subsequent games
11289             return 0;     // and signal this one isn't cached
11290         }
11291     }
11292     movePtr++;
11293     MakePieceList(board, counts);
11294     return movePtr;
11295 }
11296
11297 int QuickCompare(Board board, int *minCounts, int *maxCounts)
11298 {   // compare according to search mode
11299     int r, f;
11300     switch(appData.searchMode)
11301     {
11302       case 1: // exact position match
11303         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11304         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11305             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11306         }
11307         break;
11308       case 2: // can have extra material on empty squares
11309         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11310             if(board[r][f] == EmptySquare) continue;
11311             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11312         }
11313         break;
11314       case 3: // material with exact Pawn structure
11315         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11316             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11317             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11318         } // fall through to material comparison
11319       case 4: // exact material
11320         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11321         break;
11322       case 6: // material range with given imbalance
11323         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11324         // fall through to range comparison
11325       case 5: // material range
11326         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11327     }
11328     return TRUE;
11329 }
11330
11331 int QuickScan(Board board, Move *move)
11332 {   // reconstruct game,and compare all positions in it
11333     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11334     do {
11335         int piece = move->piece;
11336         int to = move->to, from = pieceList[piece];
11337         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11338           if(!piece) return -1;
11339           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11340             piece = (++move)->piece;
11341             from = pieceList[piece];
11342             counts[pieceType[piece]]--;
11343             pieceType[piece] = (ChessSquare) move->to;
11344             counts[move->to]++;
11345           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11346             counts[pieceType[quickBoard[to]]]--;
11347             quickBoard[to] = 0; total--;
11348             move++;
11349             continue;
11350           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11351             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11352             from  = pieceList[piece]; // so this must be King
11353             quickBoard[from] = 0;
11354             quickBoard[to] = piece;
11355             pieceList[piece] = to;
11356             move++;
11357             continue;
11358           }
11359         }
11360         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11361         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11362         quickBoard[from] = 0;
11363         quickBoard[to] = piece;
11364         pieceList[piece] = to;
11365         cnt++; turn ^= 3;
11366         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11367            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11368            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11369                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11370           ) {
11371             static int lastCounts[EmptySquare+1];
11372             int i;
11373             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11374             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11375         } else stretch = 0;
11376         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11377         move++;
11378     } while(1);
11379 }
11380
11381 void InitSearch()
11382 {
11383     int r, f;
11384     flipSearch = FALSE;
11385     CopyBoard(soughtBoard, boards[currentMove]);
11386     soughtTotal = MakePieceList(soughtBoard, maxSought);
11387     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11388     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11389     CopyBoard(reverseBoard, boards[currentMove]);
11390     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11391         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11392         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11393         reverseBoard[r][f] = piece;
11394     }
11395     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11396     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11397     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11398                  || (boards[currentMove][CASTLING][2] == NoRights || 
11399                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11400                  && (boards[currentMove][CASTLING][5] == NoRights || 
11401                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11402       ) {
11403         flipSearch = TRUE;
11404         CopyBoard(flipBoard, soughtBoard);
11405         CopyBoard(rotateBoard, reverseBoard);
11406         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11407             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11408             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11409         }
11410     }
11411     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11412     if(appData.searchMode >= 5) {
11413         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11414         MakePieceList(soughtBoard, minSought);
11415         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11416     }
11417     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11418         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11419 }
11420
11421 GameInfo dummyInfo;
11422
11423 int GameContainsPosition(FILE *f, ListGame *lg)
11424 {
11425     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11426     int fromX, fromY, toX, toY;
11427     char promoChar;
11428     static int initDone=FALSE;
11429
11430     // weed out games based on numerical tag comparison
11431     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11432     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11433     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11434     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11435     if(!initDone) {
11436         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11437         initDone = TRUE;
11438     }
11439     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11440     else CopyBoard(boards[scratch], initialPosition); // default start position
11441     if(lg->moves) {
11442         turn = btm + 1;
11443         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11444         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11445     }
11446     if(btm) plyNr++;
11447     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11448     fseek(f, lg->offset, 0);
11449     yynewfile(f);
11450     while(1) {
11451         yyboardindex = scratch;
11452         quickFlag = plyNr+1;
11453         next = Myylex();
11454         quickFlag = 0;
11455         switch(next) {
11456             case PGNTag:
11457                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11458             default:
11459                 continue;
11460
11461             case XBoardGame:
11462             case GNUChessGame:
11463                 if(plyNr) return -1; // after we have seen moves, this is for new game
11464               continue;
11465
11466             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11467             case ImpossibleMove:
11468             case WhiteWins: // game ends here with these four
11469             case BlackWins:
11470             case GameIsDrawn:
11471             case GameUnfinished:
11472                 return -1;
11473
11474             case IllegalMove:
11475                 if(appData.testLegality) return -1;
11476             case WhiteCapturesEnPassant:
11477             case BlackCapturesEnPassant:
11478             case WhitePromotion:
11479             case BlackPromotion:
11480             case WhiteNonPromotion:
11481             case BlackNonPromotion:
11482             case NormalMove:
11483             case WhiteKingSideCastle:
11484             case WhiteQueenSideCastle:
11485             case BlackKingSideCastle:
11486             case BlackQueenSideCastle:
11487             case WhiteKingSideCastleWild:
11488             case WhiteQueenSideCastleWild:
11489             case BlackKingSideCastleWild:
11490             case BlackQueenSideCastleWild:
11491             case WhiteHSideCastleFR:
11492             case WhiteASideCastleFR:
11493             case BlackHSideCastleFR:
11494             case BlackASideCastleFR:
11495                 fromX = currentMoveString[0] - AAA;
11496                 fromY = currentMoveString[1] - ONE;
11497                 toX = currentMoveString[2] - AAA;
11498                 toY = currentMoveString[3] - ONE;
11499                 promoChar = currentMoveString[4];
11500                 break;
11501             case WhiteDrop:
11502             case BlackDrop:
11503                 fromX = next == WhiteDrop ?
11504                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11505                   (int) CharToPiece(ToLower(currentMoveString[0]));
11506                 fromY = DROP_RANK;
11507                 toX = currentMoveString[2] - AAA;
11508                 toY = currentMoveString[3] - ONE;
11509                 promoChar = 0;
11510                 break;
11511         }
11512         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11513         plyNr++;
11514         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11515         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11516         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11517         if(appData.findMirror) {
11518             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11519             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11520         }
11521     }
11522 }
11523
11524 /* Load the nth game from open file f */
11525 int
11526 LoadGame(f, gameNumber, title, useList)
11527      FILE *f;
11528      int gameNumber;
11529      char *title;
11530      int useList;
11531 {
11532     ChessMove cm;
11533     char buf[MSG_SIZ];
11534     int gn = gameNumber;
11535     ListGame *lg = NULL;
11536     int numPGNTags = 0;
11537     int err, pos = -1;
11538     GameMode oldGameMode;
11539     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11540
11541     if (appData.debugMode)
11542         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11543
11544     if (gameMode == Training )
11545         SetTrainingModeOff();
11546
11547     oldGameMode = gameMode;
11548     if (gameMode != BeginningOfGame) {
11549       Reset(FALSE, TRUE);
11550     }
11551
11552     gameFileFP = f;
11553     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11554         fclose(lastLoadGameFP);
11555     }
11556
11557     if (useList) {
11558         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11559
11560         if (lg) {
11561             fseek(f, lg->offset, 0);
11562             GameListHighlight(gameNumber);
11563             pos = lg->position;
11564             gn = 1;
11565         }
11566         else {
11567             DisplayError(_("Game number out of range"), 0);
11568             return FALSE;
11569         }
11570     } else {
11571         GameListDestroy();
11572         if (fseek(f, 0, 0) == -1) {
11573             if (f == lastLoadGameFP ?
11574                 gameNumber == lastLoadGameNumber + 1 :
11575                 gameNumber == 1) {
11576                 gn = 1;
11577             } else {
11578                 DisplayError(_("Can't seek on game file"), 0);
11579                 return FALSE;
11580             }
11581         }
11582     }
11583     lastLoadGameFP = f;
11584     lastLoadGameNumber = gameNumber;
11585     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11586     lastLoadGameUseList = useList;
11587
11588     yynewfile(f);
11589
11590     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11591       snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white,
11592                 lg->gameInfo.black);
11593             DisplayTitle(buf);
11594     } else if (*title != NULLCHAR) {
11595         if (gameNumber > 1) {
11596           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11597             DisplayTitle(buf);
11598         } else {
11599             DisplayTitle(title);
11600         }
11601     }
11602
11603     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11604         gameMode = PlayFromGameFile;
11605         ModeHighlight();
11606     }
11607
11608     currentMove = forwardMostMove = backwardMostMove = 0;
11609     CopyBoard(boards[0], initialPosition);
11610     StopClocks();
11611
11612     /*
11613      * Skip the first gn-1 games in the file.
11614      * Also skip over anything that precedes an identifiable
11615      * start of game marker, to avoid being confused by
11616      * garbage at the start of the file.  Currently
11617      * recognized start of game markers are the move number "1",
11618      * the pattern "gnuchess .* game", the pattern
11619      * "^[#;%] [^ ]* game file", and a PGN tag block.
11620      * A game that starts with one of the latter two patterns
11621      * will also have a move number 1, possibly
11622      * following a position diagram.
11623      * 5-4-02: Let's try being more lenient and allowing a game to
11624      * start with an unnumbered move.  Does that break anything?
11625      */
11626     cm = lastLoadGameStart = EndOfFile;
11627     while (gn > 0) {
11628         yyboardindex = forwardMostMove;
11629         cm = (ChessMove) Myylex();
11630         switch (cm) {
11631           case EndOfFile:
11632             if (cmailMsgLoaded) {
11633                 nCmailGames = CMAIL_MAX_GAMES - gn;
11634             } else {
11635                 Reset(TRUE, TRUE);
11636                 DisplayError(_("Game not found in file"), 0);
11637             }
11638             return FALSE;
11639
11640           case GNUChessGame:
11641           case XBoardGame:
11642             gn--;
11643             lastLoadGameStart = cm;
11644             break;
11645
11646           case MoveNumberOne:
11647             switch (lastLoadGameStart) {
11648               case GNUChessGame:
11649               case XBoardGame:
11650               case PGNTag:
11651                 break;
11652               case MoveNumberOne:
11653               case EndOfFile:
11654                 gn--;           /* count this game */
11655                 lastLoadGameStart = cm;
11656                 break;
11657               default:
11658                 /* impossible */
11659                 break;
11660             }
11661             break;
11662
11663           case PGNTag:
11664             switch (lastLoadGameStart) {
11665               case GNUChessGame:
11666               case PGNTag:
11667               case MoveNumberOne:
11668               case EndOfFile:
11669                 gn--;           /* count this game */
11670                 lastLoadGameStart = cm;
11671                 break;
11672               case XBoardGame:
11673                 lastLoadGameStart = cm; /* game counted already */
11674                 break;
11675               default:
11676                 /* impossible */
11677                 break;
11678             }
11679             if (gn > 0) {
11680                 do {
11681                     yyboardindex = forwardMostMove;
11682                     cm = (ChessMove) Myylex();
11683                 } while (cm == PGNTag || cm == Comment);
11684             }
11685             break;
11686
11687           case WhiteWins:
11688           case BlackWins:
11689           case GameIsDrawn:
11690             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11691                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11692                     != CMAIL_OLD_RESULT) {
11693                     nCmailResults ++ ;
11694                     cmailResult[  CMAIL_MAX_GAMES
11695                                 - gn - 1] = CMAIL_OLD_RESULT;
11696                 }
11697             }
11698             break;
11699
11700           case NormalMove:
11701             /* Only a NormalMove can be at the start of a game
11702              * without a position diagram. */
11703             if (lastLoadGameStart == EndOfFile ) {
11704               gn--;
11705               lastLoadGameStart = MoveNumberOne;
11706             }
11707             break;
11708
11709           default:
11710             break;
11711         }
11712     }
11713
11714     if (appData.debugMode)
11715       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11716
11717     if (cm == XBoardGame) {
11718         /* Skip any header junk before position diagram and/or move 1 */
11719         for (;;) {
11720             yyboardindex = forwardMostMove;
11721             cm = (ChessMove) Myylex();
11722
11723             if (cm == EndOfFile ||
11724                 cm == GNUChessGame || cm == XBoardGame) {
11725                 /* Empty game; pretend end-of-file and handle later */
11726                 cm = EndOfFile;
11727                 break;
11728             }
11729
11730             if (cm == MoveNumberOne || cm == PositionDiagram ||
11731                 cm == PGNTag || cm == Comment)
11732               break;
11733         }
11734     } else if (cm == GNUChessGame) {
11735         if (gameInfo.event != NULL) {
11736             free(gameInfo.event);
11737         }
11738         gameInfo.event = StrSave(yy_text);
11739     }
11740
11741     startedFromSetupPosition = FALSE;
11742     while (cm == PGNTag) {
11743         if (appData.debugMode)
11744           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11745         err = ParsePGNTag(yy_text, &gameInfo);
11746         if (!err) numPGNTags++;
11747
11748         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11749         if(gameInfo.variant != oldVariant) {
11750             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11751             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11752             InitPosition(TRUE);
11753             oldVariant = gameInfo.variant;
11754             if (appData.debugMode)
11755               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11756         }
11757
11758
11759         if (gameInfo.fen != NULL) {
11760           Board initial_position;
11761           startedFromSetupPosition = TRUE;
11762           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11763             Reset(TRUE, TRUE);
11764             DisplayError(_("Bad FEN position in file"), 0);
11765             return FALSE;
11766           }
11767           CopyBoard(boards[0], initial_position);
11768           if (blackPlaysFirst) {
11769             currentMove = forwardMostMove = backwardMostMove = 1;
11770             CopyBoard(boards[1], initial_position);
11771             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11772             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11773             timeRemaining[0][1] = whiteTimeRemaining;
11774             timeRemaining[1][1] = blackTimeRemaining;
11775             if (commentList[0] != NULL) {
11776               commentList[1] = commentList[0];
11777               commentList[0] = NULL;
11778             }
11779           } else {
11780             currentMove = forwardMostMove = backwardMostMove = 0;
11781           }
11782           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11783           {   int i;
11784               initialRulePlies = FENrulePlies;
11785               for( i=0; i< nrCastlingRights; i++ )
11786                   initialRights[i] = initial_position[CASTLING][i];
11787           }
11788           yyboardindex = forwardMostMove;
11789           free(gameInfo.fen);
11790           gameInfo.fen = NULL;
11791         }
11792
11793         yyboardindex = forwardMostMove;
11794         cm = (ChessMove) Myylex();
11795
11796         /* Handle comments interspersed among the tags */
11797         while (cm == Comment) {
11798             char *p;
11799             if (appData.debugMode)
11800               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11801             p = yy_text;
11802             AppendComment(currentMove, p, FALSE);
11803             yyboardindex = forwardMostMove;
11804             cm = (ChessMove) Myylex();
11805         }
11806     }
11807
11808     /* don't rely on existence of Event tag since if game was
11809      * pasted from clipboard the Event tag may not exist
11810      */
11811     if (numPGNTags > 0){
11812         char *tags;
11813         if (gameInfo.variant == VariantNormal) {
11814           VariantClass v = StringToVariant(gameInfo.event);
11815           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11816           if(v < VariantShogi) gameInfo.variant = v;
11817         }
11818         if (!matchMode) {
11819           if( appData.autoDisplayTags ) {
11820             tags = PGNTags(&gameInfo);
11821             TagsPopUp(tags, CmailMsg());
11822             free(tags);
11823           }
11824         }
11825     } else {
11826         /* Make something up, but don't display it now */
11827         SetGameInfo();
11828         TagsPopDown();
11829     }
11830
11831     if (cm == PositionDiagram) {
11832         int i, j;
11833         char *p;
11834         Board initial_position;
11835
11836         if (appData.debugMode)
11837           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11838
11839         if (!startedFromSetupPosition) {
11840             p = yy_text;
11841             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11842               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11843                 switch (*p) {
11844                   case '{':
11845                   case '[':
11846                   case '-':
11847                   case ' ':
11848                   case '\t':
11849                   case '\n':
11850                   case '\r':
11851                     break;
11852                   default:
11853                     initial_position[i][j++] = CharToPiece(*p);
11854                     break;
11855                 }
11856             while (*p == ' ' || *p == '\t' ||
11857                    *p == '\n' || *p == '\r') p++;
11858
11859             if (strncmp(p, "black", strlen("black"))==0)
11860               blackPlaysFirst = TRUE;
11861             else
11862               blackPlaysFirst = FALSE;
11863             startedFromSetupPosition = TRUE;
11864
11865             CopyBoard(boards[0], initial_position);
11866             if (blackPlaysFirst) {
11867                 currentMove = forwardMostMove = backwardMostMove = 1;
11868                 CopyBoard(boards[1], initial_position);
11869                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11870                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11871                 timeRemaining[0][1] = whiteTimeRemaining;
11872                 timeRemaining[1][1] = blackTimeRemaining;
11873                 if (commentList[0] != NULL) {
11874                     commentList[1] = commentList[0];
11875                     commentList[0] = NULL;
11876                 }
11877             } else {
11878                 currentMove = forwardMostMove = backwardMostMove = 0;
11879             }
11880         }
11881         yyboardindex = forwardMostMove;
11882         cm = (ChessMove) Myylex();
11883     }
11884
11885     if (first.pr == NoProc) {
11886         StartChessProgram(&first);
11887     }
11888     InitChessProgram(&first, FALSE);
11889     SendToProgram("force\n", &first);
11890     if (startedFromSetupPosition) {
11891         SendBoard(&first, forwardMostMove);
11892     if (appData.debugMode) {
11893         fprintf(debugFP, "Load Game\n");
11894     }
11895         DisplayBothClocks();
11896     }
11897
11898     /* [HGM] server: flag to write setup moves in broadcast file as one */
11899     loadFlag = appData.suppressLoadMoves;
11900
11901     while (cm == Comment) {
11902         char *p;
11903         if (appData.debugMode)
11904           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11905         p = yy_text;
11906         AppendComment(currentMove, p, FALSE);
11907         yyboardindex = forwardMostMove;
11908         cm = (ChessMove) Myylex();
11909     }
11910
11911     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11912         cm == WhiteWins || cm == BlackWins ||
11913         cm == GameIsDrawn || cm == GameUnfinished) {
11914         DisplayMessage("", _("No moves in game"));
11915         if (cmailMsgLoaded) {
11916             if (appData.debugMode)
11917               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11918             ClearHighlights();
11919             flipView = FALSE;
11920         }
11921         DrawPosition(FALSE, boards[currentMove]);
11922         DisplayBothClocks();
11923         gameMode = EditGame;
11924         ModeHighlight();
11925         gameFileFP = NULL;
11926         cmailOldMove = 0;
11927         return TRUE;
11928     }
11929
11930     // [HGM] PV info: routine tests if comment empty
11931     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11932         DisplayComment(currentMove - 1, commentList[currentMove]);
11933     }
11934     if (!matchMode && appData.timeDelay != 0)
11935       DrawPosition(FALSE, boards[currentMove]);
11936
11937     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11938       programStats.ok_to_send = 1;
11939     }
11940
11941     /* if the first token after the PGN tags is a move
11942      * and not move number 1, retrieve it from the parser
11943      */
11944     if (cm != MoveNumberOne)
11945         LoadGameOneMove(cm);
11946
11947     /* load the remaining moves from the file */
11948     while (LoadGameOneMove(EndOfFile)) {
11949       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11950       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11951     }
11952
11953     /* rewind to the start of the game */
11954     currentMove = backwardMostMove;
11955
11956     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11957
11958     if (oldGameMode == AnalyzeFile ||
11959         oldGameMode == AnalyzeMode) {
11960       AnalyzeFileEvent();
11961     }
11962
11963     if (!matchMode && pos >= 0) {
11964         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11965     } else
11966     if (matchMode || appData.timeDelay == 0) {
11967       ToEndEvent();
11968     } else if (appData.timeDelay > 0) {
11969       AutoPlayGameLoop();
11970     }
11971
11972     if (appData.debugMode)
11973         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11974
11975     loadFlag = 0; /* [HGM] true game starts */
11976     return TRUE;
11977 }
11978
11979 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11980 int
11981 ReloadPosition(offset)
11982      int offset;
11983 {
11984     int positionNumber = lastLoadPositionNumber + offset;
11985     if (lastLoadPositionFP == NULL) {
11986         DisplayError(_("No position has been loaded yet"), 0);
11987         return FALSE;
11988     }
11989     if (positionNumber <= 0) {
11990         DisplayError(_("Can't back up any further"), 0);
11991         return FALSE;
11992     }
11993     return LoadPosition(lastLoadPositionFP, positionNumber,
11994                         lastLoadPositionTitle);
11995 }
11996
11997 /* Load the nth position from the given file */
11998 int
11999 LoadPositionFromFile(filename, n, title)
12000      char *filename;
12001      int n;
12002      char *title;
12003 {
12004     FILE *f;
12005     char buf[MSG_SIZ];
12006
12007     if (strcmp(filename, "-") == 0) {
12008         return LoadPosition(stdin, n, "stdin");
12009     } else {
12010         f = fopen(filename, "rb");
12011         if (f == NULL) {
12012             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12013             DisplayError(buf, errno);
12014             return FALSE;
12015         } else {
12016             return LoadPosition(f, n, title);
12017         }
12018     }
12019 }
12020
12021 /* Load the nth position from the given open file, and close it */
12022 int
12023 LoadPosition(f, positionNumber, title)
12024      FILE *f;
12025      int positionNumber;
12026      char *title;
12027 {
12028     char *p, line[MSG_SIZ];
12029     Board initial_position;
12030     int i, j, fenMode, pn;
12031
12032     if (gameMode == Training )
12033         SetTrainingModeOff();
12034
12035     if (gameMode != BeginningOfGame) {
12036         Reset(FALSE, TRUE);
12037     }
12038     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
12039         fclose(lastLoadPositionFP);
12040     }
12041     if (positionNumber == 0) positionNumber = 1;
12042     lastLoadPositionFP = f;
12043     lastLoadPositionNumber = positionNumber;
12044     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
12045     if (first.pr == NoProc && !appData.noChessProgram) {
12046       StartChessProgram(&first);
12047       InitChessProgram(&first, FALSE);
12048     }
12049     pn = positionNumber;
12050     if (positionNumber < 0) {
12051         /* Negative position number means to seek to that byte offset */
12052         if (fseek(f, -positionNumber, 0) == -1) {
12053             DisplayError(_("Can't seek on position file"), 0);
12054             return FALSE;
12055         };
12056         pn = 1;
12057     } else {
12058         if (fseek(f, 0, 0) == -1) {
12059             if (f == lastLoadPositionFP ?
12060                 positionNumber == lastLoadPositionNumber + 1 :
12061                 positionNumber == 1) {
12062                 pn = 1;
12063             } else {
12064                 DisplayError(_("Can't seek on position file"), 0);
12065                 return FALSE;
12066             }
12067         }
12068     }
12069     /* See if this file is FEN or old-style xboard */
12070     if (fgets(line, MSG_SIZ, f) == NULL) {
12071         DisplayError(_("Position not found in file"), 0);
12072         return FALSE;
12073     }
12074     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12075     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12076
12077     if (pn >= 2) {
12078         if (fenMode || line[0] == '#') pn--;
12079         while (pn > 0) {
12080             /* skip positions before number pn */
12081             if (fgets(line, MSG_SIZ, f) == NULL) {
12082                 Reset(TRUE, TRUE);
12083                 DisplayError(_("Position not found in file"), 0);
12084                 return FALSE;
12085             }
12086             if (fenMode || line[0] == '#') pn--;
12087         }
12088     }
12089
12090     if (fenMode) {
12091         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12092             DisplayError(_("Bad FEN position in file"), 0);
12093             return FALSE;
12094         }
12095     } else {
12096         (void) fgets(line, MSG_SIZ, f);
12097         (void) fgets(line, MSG_SIZ, f);
12098
12099         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12100             (void) fgets(line, MSG_SIZ, f);
12101             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12102                 if (*p == ' ')
12103                   continue;
12104                 initial_position[i][j++] = CharToPiece(*p);
12105             }
12106         }
12107
12108         blackPlaysFirst = FALSE;
12109         if (!feof(f)) {
12110             (void) fgets(line, MSG_SIZ, f);
12111             if (strncmp(line, "black", strlen("black"))==0)
12112               blackPlaysFirst = TRUE;
12113         }
12114     }
12115     startedFromSetupPosition = TRUE;
12116
12117     CopyBoard(boards[0], initial_position);
12118     if (blackPlaysFirst) {
12119         currentMove = forwardMostMove = backwardMostMove = 1;
12120         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12121         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12122         CopyBoard(boards[1], initial_position);
12123         DisplayMessage("", _("Black to play"));
12124     } else {
12125         currentMove = forwardMostMove = backwardMostMove = 0;
12126         DisplayMessage("", _("White to play"));
12127     }
12128     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12129     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12130         SendToProgram("force\n", &first);
12131         SendBoard(&first, forwardMostMove);
12132     }
12133     if (appData.debugMode) {
12134 int i, j;
12135   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12136   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12137         fprintf(debugFP, "Load Position\n");
12138     }
12139
12140     if (positionNumber > 1) {
12141       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12142         DisplayTitle(line);
12143     } else {
12144         DisplayTitle(title);
12145     }
12146     gameMode = EditGame;
12147     ModeHighlight();
12148     ResetClocks();
12149     timeRemaining[0][1] = whiteTimeRemaining;
12150     timeRemaining[1][1] = blackTimeRemaining;
12151     DrawPosition(FALSE, boards[currentMove]);
12152
12153     return TRUE;
12154 }
12155
12156
12157 void
12158 CopyPlayerNameIntoFileName(dest, src)
12159      char **dest, *src;
12160 {
12161     while (*src != NULLCHAR && *src != ',') {
12162         if (*src == ' ') {
12163             *(*dest)++ = '_';
12164             src++;
12165         } else {
12166             *(*dest)++ = *src++;
12167         }
12168     }
12169 }
12170
12171 char *DefaultFileName(ext)
12172      char *ext;
12173 {
12174     static char def[MSG_SIZ];
12175     char *p;
12176
12177     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12178         p = def;
12179         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12180         *p++ = '-';
12181         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12182         *p++ = '.';
12183         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12184     } else {
12185         def[0] = NULLCHAR;
12186     }
12187     return def;
12188 }
12189
12190 /* Save the current game to the given file */
12191 int
12192 SaveGameToFile(filename, append)
12193      char *filename;
12194      int append;
12195 {
12196     FILE *f;
12197     char buf[MSG_SIZ];
12198     int result, i, t,tot=0;
12199
12200     if (strcmp(filename, "-") == 0) {
12201         return SaveGame(stdout, 0, NULL);
12202     } else {
12203         for(i=0; i<10; i++) { // upto 10 tries
12204              f = fopen(filename, append ? "a" : "w");
12205              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12206              if(f || errno != 13) break;
12207              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12208              tot += t;
12209         }
12210         if (f == NULL) {
12211             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12212             DisplayError(buf, errno);
12213             return FALSE;
12214         } else {
12215             safeStrCpy(buf, lastMsg, MSG_SIZ);
12216             DisplayMessage(_("Waiting for access to save file"), "");
12217             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12218             DisplayMessage(_("Saving game"), "");
12219             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12220             result = SaveGame(f, 0, NULL);
12221             DisplayMessage(buf, "");
12222             return result;
12223         }
12224     }
12225 }
12226
12227 char *
12228 SavePart(str)
12229      char *str;
12230 {
12231     static char buf[MSG_SIZ];
12232     char *p;
12233
12234     p = strchr(str, ' ');
12235     if (p == NULL) return str;
12236     strncpy(buf, str, p - str);
12237     buf[p - str] = NULLCHAR;
12238     return buf;
12239 }
12240
12241 #define PGN_MAX_LINE 75
12242
12243 #define PGN_SIDE_WHITE  0
12244 #define PGN_SIDE_BLACK  1
12245
12246 /* [AS] */
12247 static int FindFirstMoveOutOfBook( int side )
12248 {
12249     int result = -1;
12250
12251     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12252         int index = backwardMostMove;
12253         int has_book_hit = 0;
12254
12255         if( (index % 2) != side ) {
12256             index++;
12257         }
12258
12259         while( index < forwardMostMove ) {
12260             /* Check to see if engine is in book */
12261             int depth = pvInfoList[index].depth;
12262             int score = pvInfoList[index].score;
12263             int in_book = 0;
12264
12265             if( depth <= 2 ) {
12266                 in_book = 1;
12267             }
12268             else if( score == 0 && depth == 63 ) {
12269                 in_book = 1; /* Zappa */
12270             }
12271             else if( score == 2 && depth == 99 ) {
12272                 in_book = 1; /* Abrok */
12273             }
12274
12275             has_book_hit += in_book;
12276
12277             if( ! in_book ) {
12278                 result = index;
12279
12280                 break;
12281             }
12282
12283             index += 2;
12284         }
12285     }
12286
12287     return result;
12288 }
12289
12290 /* [AS] */
12291 void GetOutOfBookInfo( char * buf )
12292 {
12293     int oob[2];
12294     int i;
12295     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12296
12297     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12298     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12299
12300     *buf = '\0';
12301
12302     if( oob[0] >= 0 || oob[1] >= 0 ) {
12303         for( i=0; i<2; i++ ) {
12304             int idx = oob[i];
12305
12306             if( idx >= 0 ) {
12307                 if( i > 0 && oob[0] >= 0 ) {
12308                     strcat( buf, "   " );
12309                 }
12310
12311                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12312                 sprintf( buf+strlen(buf), "%s%.2f",
12313                     pvInfoList[idx].score >= 0 ? "+" : "",
12314                     pvInfoList[idx].score / 100.0 );
12315             }
12316         }
12317     }
12318 }
12319
12320 /* Save game in PGN style and close the file */
12321 int
12322 SaveGamePGN(f)
12323      FILE *f;
12324 {
12325     int i, offset, linelen, newblock;
12326     time_t tm;
12327 //    char *movetext;
12328     char numtext[32];
12329     int movelen, numlen, blank;
12330     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12331
12332     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12333
12334     tm = time((time_t *) NULL);
12335
12336     PrintPGNTags(f, &gameInfo);
12337
12338     if (backwardMostMove > 0 || startedFromSetupPosition) {
12339         char *fen = PositionToFEN(backwardMostMove, NULL);
12340         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12341         fprintf(f, "\n{--------------\n");
12342         PrintPosition(f, backwardMostMove);
12343         fprintf(f, "--------------}\n");
12344         free(fen);
12345     }
12346     else {
12347         /* [AS] Out of book annotation */
12348         if( appData.saveOutOfBookInfo ) {
12349             char buf[64];
12350
12351             GetOutOfBookInfo( buf );
12352
12353             if( buf[0] != '\0' ) {
12354                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12355             }
12356         }
12357
12358         fprintf(f, "\n");
12359     }
12360
12361     i = backwardMostMove;
12362     linelen = 0;
12363     newblock = TRUE;
12364
12365     while (i < forwardMostMove) {
12366         /* Print comments preceding this move */
12367         if (commentList[i] != NULL) {
12368             if (linelen > 0) fprintf(f, "\n");
12369             fprintf(f, "%s", commentList[i]);
12370             linelen = 0;
12371             newblock = TRUE;
12372         }
12373
12374         /* Format move number */
12375         if ((i % 2) == 0)
12376           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12377         else
12378           if (newblock)
12379             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12380           else
12381             numtext[0] = NULLCHAR;
12382
12383         numlen = strlen(numtext);
12384         newblock = FALSE;
12385
12386         /* Print move number */
12387         blank = linelen > 0 && numlen > 0;
12388         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12389             fprintf(f, "\n");
12390             linelen = 0;
12391             blank = 0;
12392         }
12393         if (blank) {
12394             fprintf(f, " ");
12395             linelen++;
12396         }
12397         fprintf(f, "%s", numtext);
12398         linelen += numlen;
12399
12400         /* Get move */
12401         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12402         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12403
12404         /* Print move */
12405         blank = linelen > 0 && movelen > 0;
12406         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12407             fprintf(f, "\n");
12408             linelen = 0;
12409             blank = 0;
12410         }
12411         if (blank) {
12412             fprintf(f, " ");
12413             linelen++;
12414         }
12415         fprintf(f, "%s", move_buffer);
12416         linelen += movelen;
12417
12418         /* [AS] Add PV info if present */
12419         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12420             /* [HGM] add time */
12421             char buf[MSG_SIZ]; int seconds;
12422
12423             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12424
12425             if( seconds <= 0)
12426               buf[0] = 0;
12427             else
12428               if( seconds < 30 )
12429                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12430               else
12431                 {
12432                   seconds = (seconds + 4)/10; // round to full seconds
12433                   if( seconds < 60 )
12434                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12435                   else
12436                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12437                 }
12438
12439             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12440                       pvInfoList[i].score >= 0 ? "+" : "",
12441                       pvInfoList[i].score / 100.0,
12442                       pvInfoList[i].depth,
12443                       buf );
12444
12445             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12446
12447             /* Print score/depth */
12448             blank = linelen > 0 && movelen > 0;
12449             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12450                 fprintf(f, "\n");
12451                 linelen = 0;
12452                 blank = 0;
12453             }
12454             if (blank) {
12455                 fprintf(f, " ");
12456                 linelen++;
12457             }
12458             fprintf(f, "%s", move_buffer);
12459             linelen += movelen;
12460         }
12461
12462         i++;
12463     }
12464
12465     /* Start a new line */
12466     if (linelen > 0) fprintf(f, "\n");
12467
12468     /* Print comments after last move */
12469     if (commentList[i] != NULL) {
12470         fprintf(f, "%s\n", commentList[i]);
12471     }
12472
12473     /* Print result */
12474     if (gameInfo.resultDetails != NULL &&
12475         gameInfo.resultDetails[0] != NULLCHAR) {
12476         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12477                 PGNResult(gameInfo.result));
12478     } else {
12479         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12480     }
12481
12482     fclose(f);
12483     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12484     return TRUE;
12485 }
12486
12487 /* Save game in old style and close the file */
12488 int
12489 SaveGameOldStyle(f)
12490      FILE *f;
12491 {
12492     int i, offset;
12493     time_t tm;
12494
12495     tm = time((time_t *) NULL);
12496
12497     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12498     PrintOpponents(f);
12499
12500     if (backwardMostMove > 0 || startedFromSetupPosition) {
12501         fprintf(f, "\n[--------------\n");
12502         PrintPosition(f, backwardMostMove);
12503         fprintf(f, "--------------]\n");
12504     } else {
12505         fprintf(f, "\n");
12506     }
12507
12508     i = backwardMostMove;
12509     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12510
12511     while (i < forwardMostMove) {
12512         if (commentList[i] != NULL) {
12513             fprintf(f, "[%s]\n", commentList[i]);
12514         }
12515
12516         if ((i % 2) == 1) {
12517             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12518             i++;
12519         } else {
12520             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12521             i++;
12522             if (commentList[i] != NULL) {
12523                 fprintf(f, "\n");
12524                 continue;
12525             }
12526             if (i >= forwardMostMove) {
12527                 fprintf(f, "\n");
12528                 break;
12529             }
12530             fprintf(f, "%s\n", parseList[i]);
12531             i++;
12532         }
12533     }
12534
12535     if (commentList[i] != NULL) {
12536         fprintf(f, "[%s]\n", commentList[i]);
12537     }
12538
12539     /* This isn't really the old style, but it's close enough */
12540     if (gameInfo.resultDetails != NULL &&
12541         gameInfo.resultDetails[0] != NULLCHAR) {
12542         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12543                 gameInfo.resultDetails);
12544     } else {
12545         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12546     }
12547
12548     fclose(f);
12549     return TRUE;
12550 }
12551
12552 /* Save the current game to open file f and close the file */
12553 int
12554 SaveGame(f, dummy, dummy2)
12555      FILE *f;
12556      int dummy;
12557      char *dummy2;
12558 {
12559     if (gameMode == EditPosition) EditPositionDone(TRUE);
12560     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12561     if (appData.oldSaveStyle)
12562       return SaveGameOldStyle(f);
12563     else
12564       return SaveGamePGN(f);
12565 }
12566
12567 /* Save the current position to the given file */
12568 int
12569 SavePositionToFile(filename)
12570      char *filename;
12571 {
12572     FILE *f;
12573     char buf[MSG_SIZ];
12574
12575     if (strcmp(filename, "-") == 0) {
12576         return SavePosition(stdout, 0, NULL);
12577     } else {
12578         f = fopen(filename, "a");
12579         if (f == NULL) {
12580             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12581             DisplayError(buf, errno);
12582             return FALSE;
12583         } else {
12584             safeStrCpy(buf, lastMsg, MSG_SIZ);
12585             DisplayMessage(_("Waiting for access to save file"), "");
12586             flock(fileno(f), LOCK_EX); // [HGM] lock
12587             DisplayMessage(_("Saving position"), "");
12588             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12589             SavePosition(f, 0, NULL);
12590             DisplayMessage(buf, "");
12591             return TRUE;
12592         }
12593     }
12594 }
12595
12596 /* Save the current position to the given open file and close the file */
12597 int
12598 SavePosition(f, dummy, dummy2)
12599      FILE *f;
12600      int dummy;
12601      char *dummy2;
12602 {
12603     time_t tm;
12604     char *fen;
12605
12606     if (gameMode == EditPosition) EditPositionDone(TRUE);
12607     if (appData.oldSaveStyle) {
12608         tm = time((time_t *) NULL);
12609
12610         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12611         PrintOpponents(f);
12612         fprintf(f, "[--------------\n");
12613         PrintPosition(f, currentMove);
12614         fprintf(f, "--------------]\n");
12615     } else {
12616         fen = PositionToFEN(currentMove, NULL);
12617         fprintf(f, "%s\n", fen);
12618         free(fen);
12619     }
12620     fclose(f);
12621     return TRUE;
12622 }
12623
12624 void
12625 ReloadCmailMsgEvent(unregister)
12626      int unregister;
12627 {
12628 #if !WIN32
12629     static char *inFilename = NULL;
12630     static char *outFilename;
12631     int i;
12632     struct stat inbuf, outbuf;
12633     int status;
12634
12635     /* Any registered moves are unregistered if unregister is set, */
12636     /* i.e. invoked by the signal handler */
12637     if (unregister) {
12638         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12639             cmailMoveRegistered[i] = FALSE;
12640             if (cmailCommentList[i] != NULL) {
12641                 free(cmailCommentList[i]);
12642                 cmailCommentList[i] = NULL;
12643             }
12644         }
12645         nCmailMovesRegistered = 0;
12646     }
12647
12648     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12649         cmailResult[i] = CMAIL_NOT_RESULT;
12650     }
12651     nCmailResults = 0;
12652
12653     if (inFilename == NULL) {
12654         /* Because the filenames are static they only get malloced once  */
12655         /* and they never get freed                                      */
12656         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12657         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12658
12659         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12660         sprintf(outFilename, "%s.out", appData.cmailGameName);
12661     }
12662
12663     status = stat(outFilename, &outbuf);
12664     if (status < 0) {
12665         cmailMailedMove = FALSE;
12666     } else {
12667         status = stat(inFilename, &inbuf);
12668         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12669     }
12670
12671     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12672        counts the games, notes how each one terminated, etc.
12673
12674        It would be nice to remove this kludge and instead gather all
12675        the information while building the game list.  (And to keep it
12676        in the game list nodes instead of having a bunch of fixed-size
12677        parallel arrays.)  Note this will require getting each game's
12678        termination from the PGN tags, as the game list builder does
12679        not process the game moves.  --mann
12680        */
12681     cmailMsgLoaded = TRUE;
12682     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12683
12684     /* Load first game in the file or popup game menu */
12685     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12686
12687 #endif /* !WIN32 */
12688     return;
12689 }
12690
12691 int
12692 RegisterMove()
12693 {
12694     FILE *f;
12695     char string[MSG_SIZ];
12696
12697     if (   cmailMailedMove
12698         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12699         return TRUE;            /* Allow free viewing  */
12700     }
12701
12702     /* Unregister move to ensure that we don't leave RegisterMove        */
12703     /* with the move registered when the conditions for registering no   */
12704     /* longer hold                                                       */
12705     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12706         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12707         nCmailMovesRegistered --;
12708
12709         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12710           {
12711               free(cmailCommentList[lastLoadGameNumber - 1]);
12712               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12713           }
12714     }
12715
12716     if (cmailOldMove == -1) {
12717         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12718         return FALSE;
12719     }
12720
12721     if (currentMove > cmailOldMove + 1) {
12722         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12723         return FALSE;
12724     }
12725
12726     if (currentMove < cmailOldMove) {
12727         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12728         return FALSE;
12729     }
12730
12731     if (forwardMostMove > currentMove) {
12732         /* Silently truncate extra moves */
12733         TruncateGame();
12734     }
12735
12736     if (   (currentMove == cmailOldMove + 1)
12737         || (   (currentMove == cmailOldMove)
12738             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12739                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12740         if (gameInfo.result != GameUnfinished) {
12741             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12742         }
12743
12744         if (commentList[currentMove] != NULL) {
12745             cmailCommentList[lastLoadGameNumber - 1]
12746               = StrSave(commentList[currentMove]);
12747         }
12748         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12749
12750         if (appData.debugMode)
12751           fprintf(debugFP, "Saving %s for game %d\n",
12752                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12753
12754         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12755
12756         f = fopen(string, "w");
12757         if (appData.oldSaveStyle) {
12758             SaveGameOldStyle(f); /* also closes the file */
12759
12760             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12761             f = fopen(string, "w");
12762             SavePosition(f, 0, NULL); /* also closes the file */
12763         } else {
12764             fprintf(f, "{--------------\n");
12765             PrintPosition(f, currentMove);
12766             fprintf(f, "--------------}\n\n");
12767
12768             SaveGame(f, 0, NULL); /* also closes the file*/
12769         }
12770
12771         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12772         nCmailMovesRegistered ++;
12773     } else if (nCmailGames == 1) {
12774         DisplayError(_("You have not made a move yet"), 0);
12775         return FALSE;
12776     }
12777
12778     return TRUE;
12779 }
12780
12781 void
12782 MailMoveEvent()
12783 {
12784 #if !WIN32
12785     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12786     FILE *commandOutput;
12787     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12788     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12789     int nBuffers;
12790     int i;
12791     int archived;
12792     char *arcDir;
12793
12794     if (! cmailMsgLoaded) {
12795         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12796         return;
12797     }
12798
12799     if (nCmailGames == nCmailResults) {
12800         DisplayError(_("No unfinished games"), 0);
12801         return;
12802     }
12803
12804 #if CMAIL_PROHIBIT_REMAIL
12805     if (cmailMailedMove) {
12806       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);
12807         DisplayError(msg, 0);
12808         return;
12809     }
12810 #endif
12811
12812     if (! (cmailMailedMove || RegisterMove())) return;
12813
12814     if (   cmailMailedMove
12815         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12816       snprintf(string, MSG_SIZ, partCommandString,
12817                appData.debugMode ? " -v" : "", appData.cmailGameName);
12818         commandOutput = popen(string, "r");
12819
12820         if (commandOutput == NULL) {
12821             DisplayError(_("Failed to invoke cmail"), 0);
12822         } else {
12823             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12824                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12825             }
12826             if (nBuffers > 1) {
12827                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12828                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12829                 nBytes = MSG_SIZ - 1;
12830             } else {
12831                 (void) memcpy(msg, buffer, nBytes);
12832             }
12833             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12834
12835             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12836                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12837
12838                 archived = TRUE;
12839                 for (i = 0; i < nCmailGames; i ++) {
12840                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12841                         archived = FALSE;
12842                     }
12843                 }
12844                 if (   archived
12845                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12846                         != NULL)) {
12847                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12848                            arcDir,
12849                            appData.cmailGameName,
12850                            gameInfo.date);
12851                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12852                     cmailMsgLoaded = FALSE;
12853                 }
12854             }
12855
12856             DisplayInformation(msg);
12857             pclose(commandOutput);
12858         }
12859     } else {
12860         if ((*cmailMsg) != '\0') {
12861             DisplayInformation(cmailMsg);
12862         }
12863     }
12864
12865     return;
12866 #endif /* !WIN32 */
12867 }
12868
12869 char *
12870 CmailMsg()
12871 {
12872 #if WIN32
12873     return NULL;
12874 #else
12875     int  prependComma = 0;
12876     char number[5];
12877     char string[MSG_SIZ];       /* Space for game-list */
12878     int  i;
12879
12880     if (!cmailMsgLoaded) return "";
12881
12882     if (cmailMailedMove) {
12883       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12884     } else {
12885         /* Create a list of games left */
12886       snprintf(string, MSG_SIZ, "[");
12887         for (i = 0; i < nCmailGames; i ++) {
12888             if (! (   cmailMoveRegistered[i]
12889                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12890                 if (prependComma) {
12891                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12892                 } else {
12893                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12894                     prependComma = 1;
12895                 }
12896
12897                 strcat(string, number);
12898             }
12899         }
12900         strcat(string, "]");
12901
12902         if (nCmailMovesRegistered + nCmailResults == 0) {
12903             switch (nCmailGames) {
12904               case 1:
12905                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12906                 break;
12907
12908               case 2:
12909                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12910                 break;
12911
12912               default:
12913                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12914                          nCmailGames);
12915                 break;
12916             }
12917         } else {
12918             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12919               case 1:
12920                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12921                          string);
12922                 break;
12923
12924               case 0:
12925                 if (nCmailResults == nCmailGames) {
12926                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12927                 } else {
12928                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12929                 }
12930                 break;
12931
12932               default:
12933                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12934                          string);
12935             }
12936         }
12937     }
12938     return cmailMsg;
12939 #endif /* WIN32 */
12940 }
12941
12942 void
12943 ResetGameEvent()
12944 {
12945     if (gameMode == Training)
12946       SetTrainingModeOff();
12947
12948     Reset(TRUE, TRUE);
12949     cmailMsgLoaded = FALSE;
12950     if (appData.icsActive) {
12951       SendToICS(ics_prefix);
12952       SendToICS("refresh\n");
12953     }
12954 }
12955
12956 void
12957 ExitEvent(status)
12958      int status;
12959 {
12960     exiting++;
12961     if (exiting > 2) {
12962       /* Give up on clean exit */
12963       exit(status);
12964     }
12965     if (exiting > 1) {
12966       /* Keep trying for clean exit */
12967       return;
12968     }
12969
12970     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12971
12972     if (telnetISR != NULL) {
12973       RemoveInputSource(telnetISR);
12974     }
12975     if (icsPR != NoProc) {
12976       DestroyChildProcess(icsPR, TRUE);
12977     }
12978
12979     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12980     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12981
12982     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12983     /* make sure this other one finishes before killing it!                  */
12984     if(endingGame) { int count = 0;
12985         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12986         while(endingGame && count++ < 10) DoSleep(1);
12987         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12988     }
12989
12990     /* Kill off chess programs */
12991     if (first.pr != NoProc) {
12992         ExitAnalyzeMode();
12993
12994         DoSleep( appData.delayBeforeQuit );
12995         SendToProgram("quit\n", &first);
12996         DoSleep( appData.delayAfterQuit );
12997         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12998     }
12999     if (second.pr != NoProc) {
13000         DoSleep( appData.delayBeforeQuit );
13001         SendToProgram("quit\n", &second);
13002         DoSleep( appData.delayAfterQuit );
13003         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
13004     }
13005     if (first.isr != NULL) {
13006         RemoveInputSource(first.isr);
13007     }
13008     if (second.isr != NULL) {
13009         RemoveInputSource(second.isr);
13010     }
13011
13012     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
13013     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
13014
13015     ShutDownFrontEnd();
13016     exit(status);
13017 }
13018
13019 void
13020 PauseEvent()
13021 {
13022     if (appData.debugMode)
13023         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
13024     if (pausing) {
13025         pausing = FALSE;
13026         ModeHighlight();
13027         if (gameMode == MachinePlaysWhite ||
13028             gameMode == MachinePlaysBlack) {
13029             StartClocks();
13030         } else {
13031             DisplayBothClocks();
13032         }
13033         if (gameMode == PlayFromGameFile) {
13034             if (appData.timeDelay >= 0)
13035                 AutoPlayGameLoop();
13036         } else if (gameMode == IcsExamining && pauseExamInvalid) {
13037             Reset(FALSE, TRUE);
13038             SendToICS(ics_prefix);
13039             SendToICS("refresh\n");
13040         } else if (currentMove < forwardMostMove) {
13041             ForwardInner(forwardMostMove);
13042         }
13043         pauseExamInvalid = FALSE;
13044     } else {
13045         switch (gameMode) {
13046           default:
13047             return;
13048           case IcsExamining:
13049             pauseExamForwardMostMove = forwardMostMove;
13050             pauseExamInvalid = FALSE;
13051             /* fall through */
13052           case IcsObserving:
13053           case IcsPlayingWhite:
13054           case IcsPlayingBlack:
13055             pausing = TRUE;
13056             ModeHighlight();
13057             return;
13058           case PlayFromGameFile:
13059             (void) StopLoadGameTimer();
13060             pausing = TRUE;
13061             ModeHighlight();
13062             break;
13063           case BeginningOfGame:
13064             if (appData.icsActive) return;
13065             /* else fall through */
13066           case MachinePlaysWhite:
13067           case MachinePlaysBlack:
13068           case TwoMachinesPlay:
13069             if (forwardMostMove == 0)
13070               return;           /* don't pause if no one has moved */
13071             if ((gameMode == MachinePlaysWhite &&
13072                  !WhiteOnMove(forwardMostMove)) ||
13073                 (gameMode == MachinePlaysBlack &&
13074                  WhiteOnMove(forwardMostMove))) {
13075                 StopClocks();
13076             }
13077             pausing = TRUE;
13078             ModeHighlight();
13079             break;
13080         }
13081     }
13082 }
13083
13084 void
13085 EditCommentEvent()
13086 {
13087     char title[MSG_SIZ];
13088
13089     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13090       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13091     } else {
13092       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13093                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13094                parseList[currentMove - 1]);
13095     }
13096
13097     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13098 }
13099
13100
13101 void
13102 EditTagsEvent()
13103 {
13104     char *tags = PGNTags(&gameInfo);
13105     bookUp = FALSE;
13106     EditTagsPopUp(tags, NULL);
13107     free(tags);
13108 }
13109
13110 void
13111 AnalyzeModeEvent()
13112 {
13113     if (appData.noChessProgram || gameMode == AnalyzeMode)
13114       return;
13115
13116     if (gameMode != AnalyzeFile) {
13117         if (!appData.icsEngineAnalyze) {
13118                EditGameEvent();
13119                if (gameMode != EditGame) return;
13120         }
13121         ResurrectChessProgram();
13122         SendToProgram("analyze\n", &first);
13123         first.analyzing = TRUE;
13124         /*first.maybeThinking = TRUE;*/
13125         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13126         EngineOutputPopUp();
13127     }
13128     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13129     pausing = FALSE;
13130     ModeHighlight();
13131     SetGameInfo();
13132
13133     StartAnalysisClock();
13134     GetTimeMark(&lastNodeCountTime);
13135     lastNodeCount = 0;
13136 }
13137
13138 void
13139 AnalyzeFileEvent()
13140 {
13141     if (appData.noChessProgram || gameMode == AnalyzeFile)
13142       return;
13143
13144     if (gameMode != AnalyzeMode) {
13145         EditGameEvent();
13146         if (gameMode != EditGame) return;
13147         ResurrectChessProgram();
13148         SendToProgram("analyze\n", &first);
13149         first.analyzing = TRUE;
13150         /*first.maybeThinking = TRUE;*/
13151         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13152         EngineOutputPopUp();
13153     }
13154     gameMode = AnalyzeFile;
13155     pausing = FALSE;
13156     ModeHighlight();
13157     SetGameInfo();
13158
13159     StartAnalysisClock();
13160     GetTimeMark(&lastNodeCountTime);
13161     lastNodeCount = 0;
13162     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13163 }
13164
13165 void
13166 MachineWhiteEvent()
13167 {
13168     char buf[MSG_SIZ];
13169     char *bookHit = NULL;
13170
13171     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13172       return;
13173
13174
13175     if (gameMode == PlayFromGameFile ||
13176         gameMode == TwoMachinesPlay  ||
13177         gameMode == Training         ||
13178         gameMode == AnalyzeMode      ||
13179         gameMode == EndOfGame)
13180         EditGameEvent();
13181
13182     if (gameMode == EditPosition)
13183         EditPositionDone(TRUE);
13184
13185     if (!WhiteOnMove(currentMove)) {
13186         DisplayError(_("It is not White's turn"), 0);
13187         return;
13188     }
13189
13190     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13191       ExitAnalyzeMode();
13192
13193     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13194         gameMode == AnalyzeFile)
13195         TruncateGame();
13196
13197     ResurrectChessProgram();    /* in case it isn't running */
13198     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13199         gameMode = MachinePlaysWhite;
13200         ResetClocks();
13201     } else
13202     gameMode = MachinePlaysWhite;
13203     pausing = FALSE;
13204     ModeHighlight();
13205     SetGameInfo();
13206     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13207     DisplayTitle(buf);
13208     if (first.sendName) {
13209       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13210       SendToProgram(buf, &first);
13211     }
13212     if (first.sendTime) {
13213       if (first.useColors) {
13214         SendToProgram("black\n", &first); /*gnu kludge*/
13215       }
13216       SendTimeRemaining(&first, TRUE);
13217     }
13218     if (first.useColors) {
13219       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13220     }
13221     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13222     SetMachineThinkingEnables();
13223     first.maybeThinking = TRUE;
13224     StartClocks();
13225     firstMove = FALSE;
13226
13227     if (appData.autoFlipView && !flipView) {
13228       flipView = !flipView;
13229       DrawPosition(FALSE, NULL);
13230       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13231     }
13232
13233     if(bookHit) { // [HGM] book: simulate book reply
13234         static char bookMove[MSG_SIZ]; // a bit generous?
13235
13236         programStats.nodes = programStats.depth = programStats.time =
13237         programStats.score = programStats.got_only_move = 0;
13238         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13239
13240         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13241         strcat(bookMove, bookHit);
13242         HandleMachineMove(bookMove, &first);
13243     }
13244 }
13245
13246 void
13247 MachineBlackEvent()
13248 {
13249   char buf[MSG_SIZ];
13250   char *bookHit = NULL;
13251
13252     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13253         return;
13254
13255
13256     if (gameMode == PlayFromGameFile ||
13257         gameMode == TwoMachinesPlay  ||
13258         gameMode == Training         ||
13259         gameMode == AnalyzeMode      ||
13260         gameMode == EndOfGame)
13261         EditGameEvent();
13262
13263     if (gameMode == EditPosition)
13264         EditPositionDone(TRUE);
13265
13266     if (WhiteOnMove(currentMove)) {
13267         DisplayError(_("It is not Black's turn"), 0);
13268         return;
13269     }
13270
13271     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13272       ExitAnalyzeMode();
13273
13274     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13275         gameMode == AnalyzeFile)
13276         TruncateGame();
13277
13278     ResurrectChessProgram();    /* in case it isn't running */
13279     gameMode = MachinePlaysBlack;
13280     pausing = FALSE;
13281     ModeHighlight();
13282     SetGameInfo();
13283     snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13284     DisplayTitle(buf);
13285     if (first.sendName) {
13286       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13287       SendToProgram(buf, &first);
13288     }
13289     if (first.sendTime) {
13290       if (first.useColors) {
13291         SendToProgram("white\n", &first); /*gnu kludge*/
13292       }
13293       SendTimeRemaining(&first, FALSE);
13294     }
13295     if (first.useColors) {
13296       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13297     }
13298     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13299     SetMachineThinkingEnables();
13300     first.maybeThinking = TRUE;
13301     StartClocks();
13302
13303     if (appData.autoFlipView && flipView) {
13304       flipView = !flipView;
13305       DrawPosition(FALSE, NULL);
13306       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13307     }
13308     if(bookHit) { // [HGM] book: simulate book reply
13309         static char bookMove[MSG_SIZ]; // a bit generous?
13310
13311         programStats.nodes = programStats.depth = programStats.time =
13312         programStats.score = programStats.got_only_move = 0;
13313         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13314
13315         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13316         strcat(bookMove, bookHit);
13317         HandleMachineMove(bookMove, &first);
13318     }
13319 }
13320
13321
13322 void
13323 DisplayTwoMachinesTitle()
13324 {
13325     char buf[MSG_SIZ];
13326     if (appData.matchGames > 0) {
13327         if(appData.tourneyFile[0]) {
13328           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"),
13329                    gameInfo.white, gameInfo.black,
13330                    nextGame+1, appData.matchGames+1,
13331                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13332         } else 
13333         if (first.twoMachinesColor[0] == 'w') {
13334           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13335                    gameInfo.white, gameInfo.black,
13336                    first.matchWins, second.matchWins,
13337                    matchGame - 1 - (first.matchWins + second.matchWins));
13338         } else {
13339           snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"),
13340                    gameInfo.white, gameInfo.black,
13341                    second.matchWins, first.matchWins,
13342                    matchGame - 1 - (first.matchWins + second.matchWins));
13343         }
13344     } else {
13345       snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black);
13346     }
13347     DisplayTitle(buf);
13348 }
13349
13350 void
13351 SettingsMenuIfReady()
13352 {
13353   if (second.lastPing != second.lastPong) {
13354     DisplayMessage("", _("Waiting for second chess program"));
13355     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13356     return;
13357   }
13358   ThawUI();
13359   DisplayMessage("", "");
13360   SettingsPopUp(&second);
13361 }
13362
13363 int
13364 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
13365 {
13366     char buf[MSG_SIZ];
13367     if (cps->pr == NoProc) {
13368         StartChessProgram(cps);
13369         if (cps->protocolVersion == 1) {
13370           retry();
13371         } else {
13372           /* kludge: allow timeout for initial "feature" command */
13373           FreezeUI();
13374           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13375           DisplayMessage("", buf);
13376           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13377         }
13378         return 1;
13379     }
13380     return 0;
13381 }
13382
13383 void
13384 TwoMachinesEvent P((void))
13385 {
13386     int i;
13387     char buf[MSG_SIZ];
13388     ChessProgramState *onmove;
13389     char *bookHit = NULL;
13390     static int stalling = 0;
13391     TimeMark now;
13392     long wait;
13393
13394     if (appData.noChessProgram) return;
13395
13396     switch (gameMode) {
13397       case TwoMachinesPlay:
13398         return;
13399       case MachinePlaysWhite:
13400       case MachinePlaysBlack:
13401         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13402             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13403             return;
13404         }
13405         /* fall through */
13406       case BeginningOfGame:
13407       case PlayFromGameFile:
13408       case EndOfGame:
13409         EditGameEvent();
13410         if (gameMode != EditGame) return;
13411         break;
13412       case EditPosition:
13413         EditPositionDone(TRUE);
13414         break;
13415       case AnalyzeMode:
13416       case AnalyzeFile:
13417         ExitAnalyzeMode();
13418         break;
13419       case EditGame:
13420       default:
13421         break;
13422     }
13423
13424 //    forwardMostMove = currentMove;
13425     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13426
13427     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13428
13429     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13430     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13431       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13432       return;
13433     }
13434     if(!stalling) {
13435       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13436       SendToProgram("force\n", &second);
13437       stalling = 1;
13438       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13439       return;
13440     }
13441     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13442     if(appData.matchPause>10000 || appData.matchPause<10)
13443                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13444     wait = SubtractTimeMarks(&now, &pauseStart);
13445     if(wait < appData.matchPause) {
13446         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13447         return;
13448     }
13449     stalling = 0;
13450     DisplayMessage("", "");
13451     if (startedFromSetupPosition) {
13452         SendBoard(&second, backwardMostMove);
13453     if (appData.debugMode) {
13454         fprintf(debugFP, "Two Machines\n");
13455     }
13456     }
13457     for (i = backwardMostMove; i < forwardMostMove; i++) {
13458         SendMoveToProgram(i, &second);
13459     }
13460
13461     gameMode = TwoMachinesPlay;
13462     pausing = FALSE;
13463     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13464     SetGameInfo();
13465     DisplayTwoMachinesTitle();
13466     firstMove = TRUE;
13467     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13468         onmove = &first;
13469     } else {
13470         onmove = &second;
13471     }
13472     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13473     SendToProgram(first.computerString, &first);
13474     if (first.sendName) {
13475       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13476       SendToProgram(buf, &first);
13477     }
13478     SendToProgram(second.computerString, &second);
13479     if (second.sendName) {
13480       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13481       SendToProgram(buf, &second);
13482     }
13483
13484     ResetClocks();
13485     if (!first.sendTime || !second.sendTime) {
13486         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13487         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13488     }
13489     if (onmove->sendTime) {
13490       if (onmove->useColors) {
13491         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13492       }
13493       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13494     }
13495     if (onmove->useColors) {
13496       SendToProgram(onmove->twoMachinesColor, onmove);
13497     }
13498     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13499 //    SendToProgram("go\n", onmove);
13500     onmove->maybeThinking = TRUE;
13501     SetMachineThinkingEnables();
13502
13503     StartClocks();
13504
13505     if(bookHit) { // [HGM] book: simulate book reply
13506         static char bookMove[MSG_SIZ]; // a bit generous?
13507
13508         programStats.nodes = programStats.depth = programStats.time =
13509         programStats.score = programStats.got_only_move = 0;
13510         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13511
13512         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13513         strcat(bookMove, bookHit);
13514         savedMessage = bookMove; // args for deferred call
13515         savedState = onmove;
13516         ScheduleDelayedEvent(DeferredBookMove, 1);
13517     }
13518 }
13519
13520 void
13521 TrainingEvent()
13522 {
13523     if (gameMode == Training) {
13524       SetTrainingModeOff();
13525       gameMode = PlayFromGameFile;
13526       DisplayMessage("", _("Training mode off"));
13527     } else {
13528       gameMode = Training;
13529       animateTraining = appData.animate;
13530
13531       /* make sure we are not already at the end of the game */
13532       if (currentMove < forwardMostMove) {
13533         SetTrainingModeOn();
13534         DisplayMessage("", _("Training mode on"));
13535       } else {
13536         gameMode = PlayFromGameFile;
13537         DisplayError(_("Already at end of game"), 0);
13538       }
13539     }
13540     ModeHighlight();
13541 }
13542
13543 void
13544 IcsClientEvent()
13545 {
13546     if (!appData.icsActive) return;
13547     switch (gameMode) {
13548       case IcsPlayingWhite:
13549       case IcsPlayingBlack:
13550       case IcsObserving:
13551       case IcsIdle:
13552       case BeginningOfGame:
13553       case IcsExamining:
13554         return;
13555
13556       case EditGame:
13557         break;
13558
13559       case EditPosition:
13560         EditPositionDone(TRUE);
13561         break;
13562
13563       case AnalyzeMode:
13564       case AnalyzeFile:
13565         ExitAnalyzeMode();
13566         break;
13567
13568       default:
13569         EditGameEvent();
13570         break;
13571     }
13572
13573     gameMode = IcsIdle;
13574     ModeHighlight();
13575     return;
13576 }
13577
13578
13579 void
13580 EditGameEvent()
13581 {
13582     int i;
13583
13584     switch (gameMode) {
13585       case Training:
13586         SetTrainingModeOff();
13587         break;
13588       case MachinePlaysWhite:
13589       case MachinePlaysBlack:
13590       case BeginningOfGame:
13591         SendToProgram("force\n", &first);
13592         SetUserThinkingEnables();
13593         break;
13594       case PlayFromGameFile:
13595         (void) StopLoadGameTimer();
13596         if (gameFileFP != NULL) {
13597             gameFileFP = NULL;
13598         }
13599         break;
13600       case EditPosition:
13601         EditPositionDone(TRUE);
13602         break;
13603       case AnalyzeMode:
13604       case AnalyzeFile:
13605         ExitAnalyzeMode();
13606         SendToProgram("force\n", &first);
13607         break;
13608       case TwoMachinesPlay:
13609         GameEnds(EndOfFile, NULL, GE_PLAYER);
13610         ResurrectChessProgram();
13611         SetUserThinkingEnables();
13612         break;
13613       case EndOfGame:
13614         ResurrectChessProgram();
13615         break;
13616       case IcsPlayingBlack:
13617       case IcsPlayingWhite:
13618         DisplayError(_("Warning: You are still playing a game"), 0);
13619         break;
13620       case IcsObserving:
13621         DisplayError(_("Warning: You are still observing a game"), 0);
13622         break;
13623       case IcsExamining:
13624         DisplayError(_("Warning: You are still examining a game"), 0);
13625         break;
13626       case IcsIdle:
13627         break;
13628       case EditGame:
13629       default:
13630         return;
13631     }
13632
13633     pausing = FALSE;
13634     StopClocks();
13635     first.offeredDraw = second.offeredDraw = 0;
13636
13637     if (gameMode == PlayFromGameFile) {
13638         whiteTimeRemaining = timeRemaining[0][currentMove];
13639         blackTimeRemaining = timeRemaining[1][currentMove];
13640         DisplayTitle("");
13641     }
13642
13643     if (gameMode == MachinePlaysWhite ||
13644         gameMode == MachinePlaysBlack ||
13645         gameMode == TwoMachinesPlay ||
13646         gameMode == EndOfGame) {
13647         i = forwardMostMove;
13648         while (i > currentMove) {
13649             SendToProgram("undo\n", &first);
13650             i--;
13651         }
13652         if(!adjustedClock) {
13653         whiteTimeRemaining = timeRemaining[0][currentMove];
13654         blackTimeRemaining = timeRemaining[1][currentMove];
13655         DisplayBothClocks();
13656         }
13657         if (whiteFlag || blackFlag) {
13658             whiteFlag = blackFlag = 0;
13659         }
13660         DisplayTitle("");
13661     }
13662
13663     gameMode = EditGame;
13664     ModeHighlight();
13665     SetGameInfo();
13666 }
13667
13668
13669 void
13670 EditPositionEvent()
13671 {
13672     if (gameMode == EditPosition) {
13673         EditGameEvent();
13674         return;
13675     }
13676
13677     EditGameEvent();
13678     if (gameMode != EditGame) return;
13679
13680     gameMode = EditPosition;
13681     ModeHighlight();
13682     SetGameInfo();
13683     if (currentMove > 0)
13684       CopyBoard(boards[0], boards[currentMove]);
13685
13686     blackPlaysFirst = !WhiteOnMove(currentMove);
13687     ResetClocks();
13688     currentMove = forwardMostMove = backwardMostMove = 0;
13689     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13690     DisplayMove(-1);
13691 }
13692
13693 void
13694 ExitAnalyzeMode()
13695 {
13696     /* [DM] icsEngineAnalyze - possible call from other functions */
13697     if (appData.icsEngineAnalyze) {
13698         appData.icsEngineAnalyze = FALSE;
13699
13700         DisplayMessage("",_("Close ICS engine analyze..."));
13701     }
13702     if (first.analysisSupport && first.analyzing) {
13703       SendToProgram("exit\n", &first);
13704       first.analyzing = FALSE;
13705     }
13706     thinkOutput[0] = NULLCHAR;
13707 }
13708
13709 void
13710 EditPositionDone(Boolean fakeRights)
13711 {
13712     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13713
13714     startedFromSetupPosition = TRUE;
13715     InitChessProgram(&first, FALSE);
13716     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13717       boards[0][EP_STATUS] = EP_NONE;
13718       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13719     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13720         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13721         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13722       } else boards[0][CASTLING][2] = NoRights;
13723     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13724         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13725         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13726       } else boards[0][CASTLING][5] = NoRights;
13727     }
13728     SendToProgram("force\n", &first);
13729     if (blackPlaysFirst) {
13730         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13731         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13732         currentMove = forwardMostMove = backwardMostMove = 1;
13733         CopyBoard(boards[1], boards[0]);
13734     } else {
13735         currentMove = forwardMostMove = backwardMostMove = 0;
13736     }
13737     SendBoard(&first, forwardMostMove);
13738     if (appData.debugMode) {
13739         fprintf(debugFP, "EditPosDone\n");
13740     }
13741     DisplayTitle("");
13742     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13743     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13744     gameMode = EditGame;
13745     ModeHighlight();
13746     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13747     ClearHighlights(); /* [AS] */
13748 }
13749
13750 /* Pause for `ms' milliseconds */
13751 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13752 void
13753 TimeDelay(ms)
13754      long ms;
13755 {
13756     TimeMark m1, m2;
13757
13758     GetTimeMark(&m1);
13759     do {
13760         GetTimeMark(&m2);
13761     } while (SubtractTimeMarks(&m2, &m1) < ms);
13762 }
13763
13764 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13765 void
13766 SendMultiLineToICS(buf)
13767      char *buf;
13768 {
13769     char temp[MSG_SIZ+1], *p;
13770     int len;
13771
13772     len = strlen(buf);
13773     if (len > MSG_SIZ)
13774       len = MSG_SIZ;
13775
13776     strncpy(temp, buf, len);
13777     temp[len] = 0;
13778
13779     p = temp;
13780     while (*p) {
13781         if (*p == '\n' || *p == '\r')
13782           *p = ' ';
13783         ++p;
13784     }
13785
13786     strcat(temp, "\n");
13787     SendToICS(temp);
13788     SendToPlayer(temp, strlen(temp));
13789 }
13790
13791 void
13792 SetWhiteToPlayEvent()
13793 {
13794     if (gameMode == EditPosition) {
13795         blackPlaysFirst = FALSE;
13796         DisplayBothClocks();    /* works because currentMove is 0 */
13797     } else if (gameMode == IcsExamining) {
13798         SendToICS(ics_prefix);
13799         SendToICS("tomove white\n");
13800     }
13801 }
13802
13803 void
13804 SetBlackToPlayEvent()
13805 {
13806     if (gameMode == EditPosition) {
13807         blackPlaysFirst = TRUE;
13808         currentMove = 1;        /* kludge */
13809         DisplayBothClocks();
13810         currentMove = 0;
13811     } else if (gameMode == IcsExamining) {
13812         SendToICS(ics_prefix);
13813         SendToICS("tomove black\n");
13814     }
13815 }
13816
13817 void
13818 EditPositionMenuEvent(selection, x, y)
13819      ChessSquare selection;
13820      int x, y;
13821 {
13822     char buf[MSG_SIZ];
13823     ChessSquare piece = boards[0][y][x];
13824
13825     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13826
13827     switch (selection) {
13828       case ClearBoard:
13829         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13830             SendToICS(ics_prefix);
13831             SendToICS("bsetup clear\n");
13832         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13833             SendToICS(ics_prefix);
13834             SendToICS("clearboard\n");
13835         } else {
13836             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13837                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13838                 for (y = 0; y < BOARD_HEIGHT; y++) {
13839                     if (gameMode == IcsExamining) {
13840                         if (boards[currentMove][y][x] != EmptySquare) {
13841                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13842                                     AAA + x, ONE + y);
13843                             SendToICS(buf);
13844                         }
13845                     } else {
13846                         boards[0][y][x] = p;
13847                     }
13848                 }
13849             }
13850         }
13851         if (gameMode == EditPosition) {
13852             DrawPosition(FALSE, boards[0]);
13853         }
13854         break;
13855
13856       case WhitePlay:
13857         SetWhiteToPlayEvent();
13858         break;
13859
13860       case BlackPlay:
13861         SetBlackToPlayEvent();
13862         break;
13863
13864       case EmptySquare:
13865         if (gameMode == IcsExamining) {
13866             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13867             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13868             SendToICS(buf);
13869         } else {
13870             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13871                 if(x == BOARD_LEFT-2) {
13872                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13873                     boards[0][y][1] = 0;
13874                 } else
13875                 if(x == BOARD_RGHT+1) {
13876                     if(y >= gameInfo.holdingsSize) break;
13877                     boards[0][y][BOARD_WIDTH-2] = 0;
13878                 } else break;
13879             }
13880             boards[0][y][x] = EmptySquare;
13881             DrawPosition(FALSE, boards[0]);
13882         }
13883         break;
13884
13885       case PromotePiece:
13886         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13887            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13888             selection = (ChessSquare) (PROMOTED piece);
13889         } else if(piece == EmptySquare) selection = WhiteSilver;
13890         else selection = (ChessSquare)((int)piece - 1);
13891         goto defaultlabel;
13892
13893       case DemotePiece:
13894         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13895            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13896             selection = (ChessSquare) (DEMOTED piece);
13897         } else if(piece == EmptySquare) selection = BlackSilver;
13898         else selection = (ChessSquare)((int)piece + 1);
13899         goto defaultlabel;
13900
13901       case WhiteQueen:
13902       case BlackQueen:
13903         if(gameInfo.variant == VariantShatranj ||
13904            gameInfo.variant == VariantXiangqi  ||
13905            gameInfo.variant == VariantCourier  ||
13906            gameInfo.variant == VariantMakruk     )
13907             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13908         goto defaultlabel;
13909
13910       case WhiteKing:
13911       case BlackKing:
13912         if(gameInfo.variant == VariantXiangqi)
13913             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13914         if(gameInfo.variant == VariantKnightmate)
13915             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13916       default:
13917         defaultlabel:
13918         if (gameMode == IcsExamining) {
13919             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13920             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13921                      PieceToChar(selection), AAA + x, ONE + y);
13922             SendToICS(buf);
13923         } else {
13924             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13925                 int n;
13926                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13927                     n = PieceToNumber(selection - BlackPawn);
13928                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13929                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13930                     boards[0][BOARD_HEIGHT-1-n][1]++;
13931                 } else
13932                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13933                     n = PieceToNumber(selection);
13934                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13935                     boards[0][n][BOARD_WIDTH-1] = selection;
13936                     boards[0][n][BOARD_WIDTH-2]++;
13937                 }
13938             } else
13939             boards[0][y][x] = selection;
13940             DrawPosition(TRUE, boards[0]);
13941         }
13942         break;
13943     }
13944 }
13945
13946
13947 void
13948 DropMenuEvent(selection, x, y)
13949      ChessSquare selection;
13950      int x, y;
13951 {
13952     ChessMove moveType;
13953
13954     switch (gameMode) {
13955       case IcsPlayingWhite:
13956       case MachinePlaysBlack:
13957         if (!WhiteOnMove(currentMove)) {
13958             DisplayMoveError(_("It is Black's turn"));
13959             return;
13960         }
13961         moveType = WhiteDrop;
13962         break;
13963       case IcsPlayingBlack:
13964       case MachinePlaysWhite:
13965         if (WhiteOnMove(currentMove)) {
13966             DisplayMoveError(_("It is White's turn"));
13967             return;
13968         }
13969         moveType = BlackDrop;
13970         break;
13971       case EditGame:
13972         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13973         break;
13974       default:
13975         return;
13976     }
13977
13978     if (moveType == BlackDrop && selection < BlackPawn) {
13979       selection = (ChessSquare) ((int) selection
13980                                  + (int) BlackPawn - (int) WhitePawn);
13981     }
13982     if (boards[currentMove][y][x] != EmptySquare) {
13983         DisplayMoveError(_("That square is occupied"));
13984         return;
13985     }
13986
13987     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13988 }
13989
13990 void
13991 AcceptEvent()
13992 {
13993     /* Accept a pending offer of any kind from opponent */
13994
13995     if (appData.icsActive) {
13996         SendToICS(ics_prefix);
13997         SendToICS("accept\n");
13998     } else if (cmailMsgLoaded) {
13999         if (currentMove == cmailOldMove &&
14000             commentList[cmailOldMove] != NULL &&
14001             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14002                    "Black offers a draw" : "White offers a draw")) {
14003             TruncateGame();
14004             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14005             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14006         } else {
14007             DisplayError(_("There is no pending offer on this move"), 0);
14008             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14009         }
14010     } else {
14011         /* Not used for offers from chess program */
14012     }
14013 }
14014
14015 void
14016 DeclineEvent()
14017 {
14018     /* Decline a pending offer of any kind from opponent */
14019
14020     if (appData.icsActive) {
14021         SendToICS(ics_prefix);
14022         SendToICS("decline\n");
14023     } else if (cmailMsgLoaded) {
14024         if (currentMove == cmailOldMove &&
14025             commentList[cmailOldMove] != NULL &&
14026             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14027                    "Black offers a draw" : "White offers a draw")) {
14028 #ifdef NOTDEF
14029             AppendComment(cmailOldMove, "Draw declined", TRUE);
14030             DisplayComment(cmailOldMove - 1, "Draw declined");
14031 #endif /*NOTDEF*/
14032         } else {
14033             DisplayError(_("There is no pending offer on this move"), 0);
14034         }
14035     } else {
14036         /* Not used for offers from chess program */
14037     }
14038 }
14039
14040 void
14041 RematchEvent()
14042 {
14043     /* Issue ICS rematch command */
14044     if (appData.icsActive) {
14045         SendToICS(ics_prefix);
14046         SendToICS("rematch\n");
14047     }
14048 }
14049
14050 void
14051 CallFlagEvent()
14052 {
14053     /* Call your opponent's flag (claim a win on time) */
14054     if (appData.icsActive) {
14055         SendToICS(ics_prefix);
14056         SendToICS("flag\n");
14057     } else {
14058         switch (gameMode) {
14059           default:
14060             return;
14061           case MachinePlaysWhite:
14062             if (whiteFlag) {
14063                 if (blackFlag)
14064                   GameEnds(GameIsDrawn, "Both players ran out of time",
14065                            GE_PLAYER);
14066                 else
14067                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
14068             } else {
14069                 DisplayError(_("Your opponent is not out of time"), 0);
14070             }
14071             break;
14072           case MachinePlaysBlack:
14073             if (blackFlag) {
14074                 if (whiteFlag)
14075                   GameEnds(GameIsDrawn, "Both players ran out of time",
14076                            GE_PLAYER);
14077                 else
14078                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14079             } else {
14080                 DisplayError(_("Your opponent is not out of time"), 0);
14081             }
14082             break;
14083         }
14084     }
14085 }
14086
14087 void
14088 ClockClick(int which)
14089 {       // [HGM] code moved to back-end from winboard.c
14090         if(which) { // black clock
14091           if (gameMode == EditPosition || gameMode == IcsExamining) {
14092             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14093             SetBlackToPlayEvent();
14094           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14095           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14096           } else if (shiftKey) {
14097             AdjustClock(which, -1);
14098           } else if (gameMode == IcsPlayingWhite ||
14099                      gameMode == MachinePlaysBlack) {
14100             CallFlagEvent();
14101           }
14102         } else { // white clock
14103           if (gameMode == EditPosition || gameMode == IcsExamining) {
14104             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14105             SetWhiteToPlayEvent();
14106           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14107           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14108           } else if (shiftKey) {
14109             AdjustClock(which, -1);
14110           } else if (gameMode == IcsPlayingBlack ||
14111                    gameMode == MachinePlaysWhite) {
14112             CallFlagEvent();
14113           }
14114         }
14115 }
14116
14117 void
14118 DrawEvent()
14119 {
14120     /* Offer draw or accept pending draw offer from opponent */
14121
14122     if (appData.icsActive) {
14123         /* Note: tournament rules require draw offers to be
14124            made after you make your move but before you punch
14125            your clock.  Currently ICS doesn't let you do that;
14126            instead, you immediately punch your clock after making
14127            a move, but you can offer a draw at any time. */
14128
14129         SendToICS(ics_prefix);
14130         SendToICS("draw\n");
14131         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14132     } else if (cmailMsgLoaded) {
14133         if (currentMove == cmailOldMove &&
14134             commentList[cmailOldMove] != NULL &&
14135             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14136                    "Black offers a draw" : "White offers a draw")) {
14137             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14138             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14139         } else if (currentMove == cmailOldMove + 1) {
14140             char *offer = WhiteOnMove(cmailOldMove) ?
14141               "White offers a draw" : "Black offers a draw";
14142             AppendComment(currentMove, offer, TRUE);
14143             DisplayComment(currentMove - 1, offer);
14144             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14145         } else {
14146             DisplayError(_("You must make your move before offering a draw"), 0);
14147             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14148         }
14149     } else if (first.offeredDraw) {
14150         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14151     } else {
14152         if (first.sendDrawOffers) {
14153             SendToProgram("draw\n", &first);
14154             userOfferedDraw = TRUE;
14155         }
14156     }
14157 }
14158
14159 void
14160 AdjournEvent()
14161 {
14162     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14163
14164     if (appData.icsActive) {
14165         SendToICS(ics_prefix);
14166         SendToICS("adjourn\n");
14167     } else {
14168         /* Currently GNU Chess doesn't offer or accept Adjourns */
14169     }
14170 }
14171
14172
14173 void
14174 AbortEvent()
14175 {
14176     /* Offer Abort or accept pending Abort offer from opponent */
14177
14178     if (appData.icsActive) {
14179         SendToICS(ics_prefix);
14180         SendToICS("abort\n");
14181     } else {
14182         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14183     }
14184 }
14185
14186 void
14187 ResignEvent()
14188 {
14189     /* Resign.  You can do this even if it's not your turn. */
14190
14191     if (appData.icsActive) {
14192         SendToICS(ics_prefix);
14193         SendToICS("resign\n");
14194     } else {
14195         switch (gameMode) {
14196           case MachinePlaysWhite:
14197             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14198             break;
14199           case MachinePlaysBlack:
14200             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14201             break;
14202           case EditGame:
14203             if (cmailMsgLoaded) {
14204                 TruncateGame();
14205                 if (WhiteOnMove(cmailOldMove)) {
14206                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14207                 } else {
14208                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14209                 }
14210                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14211             }
14212             break;
14213           default:
14214             break;
14215         }
14216     }
14217 }
14218
14219
14220 void
14221 StopObservingEvent()
14222 {
14223     /* Stop observing current games */
14224     SendToICS(ics_prefix);
14225     SendToICS("unobserve\n");
14226 }
14227
14228 void
14229 StopExaminingEvent()
14230 {
14231     /* Stop observing current game */
14232     SendToICS(ics_prefix);
14233     SendToICS("unexamine\n");
14234 }
14235
14236 void
14237 ForwardInner(target)
14238      int target;
14239 {
14240     int limit;
14241
14242     if (appData.debugMode)
14243         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14244                 target, currentMove, forwardMostMove);
14245
14246     if (gameMode == EditPosition)
14247       return;
14248
14249     MarkTargetSquares(1);
14250
14251     if (gameMode == PlayFromGameFile && !pausing)
14252       PauseEvent();
14253
14254     if (gameMode == IcsExamining && pausing)
14255       limit = pauseExamForwardMostMove;
14256     else
14257       limit = forwardMostMove;
14258
14259     if (target > limit) target = limit;
14260
14261     if (target > 0 && moveList[target - 1][0]) {
14262         int fromX, fromY, toX, toY;
14263         toX = moveList[target - 1][2] - AAA;
14264         toY = moveList[target - 1][3] - ONE;
14265         if (moveList[target - 1][1] == '@') {
14266             if (appData.highlightLastMove) {
14267                 SetHighlights(-1, -1, toX, toY);
14268             }
14269         } else {
14270             fromX = moveList[target - 1][0] - AAA;
14271             fromY = moveList[target - 1][1] - ONE;
14272             if (target == currentMove + 1) {
14273                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14274             }
14275             if (appData.highlightLastMove) {
14276                 SetHighlights(fromX, fromY, toX, toY);
14277             }
14278         }
14279     }
14280     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14281         gameMode == Training || gameMode == PlayFromGameFile ||
14282         gameMode == AnalyzeFile) {
14283         while (currentMove < target) {
14284             SendMoveToProgram(currentMove++, &first);
14285         }
14286     } else {
14287         currentMove = target;
14288     }
14289
14290     if (gameMode == EditGame || gameMode == EndOfGame) {
14291         whiteTimeRemaining = timeRemaining[0][currentMove];
14292         blackTimeRemaining = timeRemaining[1][currentMove];
14293     }
14294     DisplayBothClocks();
14295     DisplayMove(currentMove - 1);
14296     DrawPosition(FALSE, boards[currentMove]);
14297     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14298     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14299         DisplayComment(currentMove - 1, commentList[currentMove]);
14300     }
14301 }
14302
14303
14304 void
14305 ForwardEvent()
14306 {
14307     if (gameMode == IcsExamining && !pausing) {
14308         SendToICS(ics_prefix);
14309         SendToICS("forward\n");
14310     } else {
14311         ForwardInner(currentMove + 1);
14312     }
14313 }
14314
14315 void
14316 ToEndEvent()
14317 {
14318     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14319         /* to optimze, we temporarily turn off analysis mode while we feed
14320          * the remaining moves to the engine. Otherwise we get analysis output
14321          * after each move.
14322          */
14323         if (first.analysisSupport) {
14324           SendToProgram("exit\nforce\n", &first);
14325           first.analyzing = FALSE;
14326         }
14327     }
14328
14329     if (gameMode == IcsExamining && !pausing) {
14330         SendToICS(ics_prefix);
14331         SendToICS("forward 999999\n");
14332     } else {
14333         ForwardInner(forwardMostMove);
14334     }
14335
14336     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14337         /* we have fed all the moves, so reactivate analysis mode */
14338         SendToProgram("analyze\n", &first);
14339         first.analyzing = TRUE;
14340         /*first.maybeThinking = TRUE;*/
14341         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14342     }
14343 }
14344
14345 void
14346 BackwardInner(target)
14347      int target;
14348 {
14349     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14350
14351     if (appData.debugMode)
14352         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14353                 target, currentMove, forwardMostMove);
14354
14355     if (gameMode == EditPosition) return;
14356     MarkTargetSquares(1);
14357     if (currentMove <= backwardMostMove) {
14358         ClearHighlights();
14359         DrawPosition(full_redraw, boards[currentMove]);
14360         return;
14361     }
14362     if (gameMode == PlayFromGameFile && !pausing)
14363       PauseEvent();
14364
14365     if (moveList[target][0]) {
14366         int fromX, fromY, toX, toY;
14367         toX = moveList[target][2] - AAA;
14368         toY = moveList[target][3] - ONE;
14369         if (moveList[target][1] == '@') {
14370             if (appData.highlightLastMove) {
14371                 SetHighlights(-1, -1, toX, toY);
14372             }
14373         } else {
14374             fromX = moveList[target][0] - AAA;
14375             fromY = moveList[target][1] - ONE;
14376             if (target == currentMove - 1) {
14377                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14378             }
14379             if (appData.highlightLastMove) {
14380                 SetHighlights(fromX, fromY, toX, toY);
14381             }
14382         }
14383     }
14384     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14385         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14386         while (currentMove > target) {
14387             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14388                 // null move cannot be undone. Reload program with move history before it.
14389                 int i;
14390                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14391                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14392                 }
14393                 SendBoard(&first, i); 
14394                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14395                 break;
14396             }
14397             SendToProgram("undo\n", &first);
14398             currentMove--;
14399         }
14400     } else {
14401         currentMove = target;
14402     }
14403
14404     if (gameMode == EditGame || gameMode == EndOfGame) {
14405         whiteTimeRemaining = timeRemaining[0][currentMove];
14406         blackTimeRemaining = timeRemaining[1][currentMove];
14407     }
14408     DisplayBothClocks();
14409     DisplayMove(currentMove - 1);
14410     DrawPosition(full_redraw, boards[currentMove]);
14411     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14412     // [HGM] PV info: routine tests if comment empty
14413     DisplayComment(currentMove - 1, commentList[currentMove]);
14414 }
14415
14416 void
14417 BackwardEvent()
14418 {
14419     if (gameMode == IcsExamining && !pausing) {
14420         SendToICS(ics_prefix);
14421         SendToICS("backward\n");
14422     } else {
14423         BackwardInner(currentMove - 1);
14424     }
14425 }
14426
14427 void
14428 ToStartEvent()
14429 {
14430     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14431         /* to optimize, we temporarily turn off analysis mode while we undo
14432          * all the moves. Otherwise we get analysis output after each undo.
14433          */
14434         if (first.analysisSupport) {
14435           SendToProgram("exit\nforce\n", &first);
14436           first.analyzing = FALSE;
14437         }
14438     }
14439
14440     if (gameMode == IcsExamining && !pausing) {
14441         SendToICS(ics_prefix);
14442         SendToICS("backward 999999\n");
14443     } else {
14444         BackwardInner(backwardMostMove);
14445     }
14446
14447     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14448         /* we have fed all the moves, so reactivate analysis mode */
14449         SendToProgram("analyze\n", &first);
14450         first.analyzing = TRUE;
14451         /*first.maybeThinking = TRUE;*/
14452         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14453     }
14454 }
14455
14456 void
14457 ToNrEvent(int to)
14458 {
14459   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14460   if (to >= forwardMostMove) to = forwardMostMove;
14461   if (to <= backwardMostMove) to = backwardMostMove;
14462   if (to < currentMove) {
14463     BackwardInner(to);
14464   } else {
14465     ForwardInner(to);
14466   }
14467 }
14468
14469 void
14470 RevertEvent(Boolean annotate)
14471 {
14472     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14473         return;
14474     }
14475     if (gameMode != IcsExamining) {
14476         DisplayError(_("You are not examining a game"), 0);
14477         return;
14478     }
14479     if (pausing) {
14480         DisplayError(_("You can't revert while pausing"), 0);
14481         return;
14482     }
14483     SendToICS(ics_prefix);
14484     SendToICS("revert\n");
14485 }
14486
14487 void
14488 RetractMoveEvent()
14489 {
14490     switch (gameMode) {
14491       case MachinePlaysWhite:
14492       case MachinePlaysBlack:
14493         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14494             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14495             return;
14496         }
14497         if (forwardMostMove < 2) return;
14498         currentMove = forwardMostMove = forwardMostMove - 2;
14499         whiteTimeRemaining = timeRemaining[0][currentMove];
14500         blackTimeRemaining = timeRemaining[1][currentMove];
14501         DisplayBothClocks();
14502         DisplayMove(currentMove - 1);
14503         ClearHighlights();/*!! could figure this out*/
14504         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14505         SendToProgram("remove\n", &first);
14506         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14507         break;
14508
14509       case BeginningOfGame:
14510       default:
14511         break;
14512
14513       case IcsPlayingWhite:
14514       case IcsPlayingBlack:
14515         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14516             SendToICS(ics_prefix);
14517             SendToICS("takeback 2\n");
14518         } else {
14519             SendToICS(ics_prefix);
14520             SendToICS("takeback 1\n");
14521         }
14522         break;
14523     }
14524 }
14525
14526 void
14527 MoveNowEvent()
14528 {
14529     ChessProgramState *cps;
14530
14531     switch (gameMode) {
14532       case MachinePlaysWhite:
14533         if (!WhiteOnMove(forwardMostMove)) {
14534             DisplayError(_("It is your turn"), 0);
14535             return;
14536         }
14537         cps = &first;
14538         break;
14539       case MachinePlaysBlack:
14540         if (WhiteOnMove(forwardMostMove)) {
14541             DisplayError(_("It is your turn"), 0);
14542             return;
14543         }
14544         cps = &first;
14545         break;
14546       case TwoMachinesPlay:
14547         if (WhiteOnMove(forwardMostMove) ==
14548             (first.twoMachinesColor[0] == 'w')) {
14549             cps = &first;
14550         } else {
14551             cps = &second;
14552         }
14553         break;
14554       case BeginningOfGame:
14555       default:
14556         return;
14557     }
14558     SendToProgram("?\n", cps);
14559 }
14560
14561 void
14562 TruncateGameEvent()
14563 {
14564     EditGameEvent();
14565     if (gameMode != EditGame) return;
14566     TruncateGame();
14567 }
14568
14569 void
14570 TruncateGame()
14571 {
14572     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14573     if (forwardMostMove > currentMove) {
14574         if (gameInfo.resultDetails != NULL) {
14575             free(gameInfo.resultDetails);
14576             gameInfo.resultDetails = NULL;
14577             gameInfo.result = GameUnfinished;
14578         }
14579         forwardMostMove = currentMove;
14580         HistorySet(parseList, backwardMostMove, forwardMostMove,
14581                    currentMove-1);
14582     }
14583 }
14584
14585 void
14586 HintEvent()
14587 {
14588     if (appData.noChessProgram) return;
14589     switch (gameMode) {
14590       case MachinePlaysWhite:
14591         if (WhiteOnMove(forwardMostMove)) {
14592             DisplayError(_("Wait until your turn"), 0);
14593             return;
14594         }
14595         break;
14596       case BeginningOfGame:
14597       case MachinePlaysBlack:
14598         if (!WhiteOnMove(forwardMostMove)) {
14599             DisplayError(_("Wait until your turn"), 0);
14600             return;
14601         }
14602         break;
14603       default:
14604         DisplayError(_("No hint available"), 0);
14605         return;
14606     }
14607     SendToProgram("hint\n", &first);
14608     hintRequested = TRUE;
14609 }
14610
14611 void
14612 BookEvent()
14613 {
14614     if (appData.noChessProgram) return;
14615     switch (gameMode) {
14616       case MachinePlaysWhite:
14617         if (WhiteOnMove(forwardMostMove)) {
14618             DisplayError(_("Wait until your turn"), 0);
14619             return;
14620         }
14621         break;
14622       case BeginningOfGame:
14623       case MachinePlaysBlack:
14624         if (!WhiteOnMove(forwardMostMove)) {
14625             DisplayError(_("Wait until your turn"), 0);
14626             return;
14627         }
14628         break;
14629       case EditPosition:
14630         EditPositionDone(TRUE);
14631         break;
14632       case TwoMachinesPlay:
14633         return;
14634       default:
14635         break;
14636     }
14637     SendToProgram("bk\n", &first);
14638     bookOutput[0] = NULLCHAR;
14639     bookRequested = TRUE;
14640 }
14641
14642 void
14643 AboutGameEvent()
14644 {
14645     char *tags = PGNTags(&gameInfo);
14646     TagsPopUp(tags, CmailMsg());
14647     free(tags);
14648 }
14649
14650 /* end button procedures */
14651
14652 void
14653 PrintPosition(fp, move)
14654      FILE *fp;
14655      int move;
14656 {
14657     int i, j;
14658
14659     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14660         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14661             char c = PieceToChar(boards[move][i][j]);
14662             fputc(c == 'x' ? '.' : c, fp);
14663             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14664         }
14665     }
14666     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14667       fprintf(fp, "white to play\n");
14668     else
14669       fprintf(fp, "black to play\n");
14670 }
14671
14672 void
14673 PrintOpponents(fp)
14674      FILE *fp;
14675 {
14676     if (gameInfo.white != NULL) {
14677         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14678     } else {
14679         fprintf(fp, "\n");
14680     }
14681 }
14682
14683 /* Find last component of program's own name, using some heuristics */
14684 void
14685 TidyProgramName(prog, host, buf)
14686      char *prog, *host, buf[MSG_SIZ];
14687 {
14688     char *p, *q;
14689     int local = (strcmp(host, "localhost") == 0);
14690     while (!local && (p = strchr(prog, ';')) != NULL) {
14691         p++;
14692         while (*p == ' ') p++;
14693         prog = p;
14694     }
14695     if (*prog == '"' || *prog == '\'') {
14696         q = strchr(prog + 1, *prog);
14697     } else {
14698         q = strchr(prog, ' ');
14699     }
14700     if (q == NULL) q = prog + strlen(prog);
14701     p = q;
14702     while (p >= prog && *p != '/' && *p != '\\') p--;
14703     p++;
14704     if(p == prog && *p == '"') p++;
14705     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14706     memcpy(buf, p, q - p);
14707     buf[q - p] = NULLCHAR;
14708     if (!local) {
14709         strcat(buf, "@");
14710         strcat(buf, host);
14711     }
14712 }
14713
14714 char *
14715 TimeControlTagValue()
14716 {
14717     char buf[MSG_SIZ];
14718     if (!appData.clockMode) {
14719       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14720     } else if (movesPerSession > 0) {
14721       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14722     } else if (timeIncrement == 0) {
14723       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14724     } else {
14725       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14726     }
14727     return StrSave(buf);
14728 }
14729
14730 void
14731 SetGameInfo()
14732 {
14733     /* This routine is used only for certain modes */
14734     VariantClass v = gameInfo.variant;
14735     ChessMove r = GameUnfinished;
14736     char *p = NULL;
14737
14738     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14739         r = gameInfo.result;
14740         p = gameInfo.resultDetails;
14741         gameInfo.resultDetails = NULL;
14742     }
14743     ClearGameInfo(&gameInfo);
14744     gameInfo.variant = v;
14745
14746     switch (gameMode) {
14747       case MachinePlaysWhite:
14748         gameInfo.event = StrSave( appData.pgnEventHeader );
14749         gameInfo.site = StrSave(HostName());
14750         gameInfo.date = PGNDate();
14751         gameInfo.round = StrSave("-");
14752         gameInfo.white = StrSave(first.tidy);
14753         gameInfo.black = StrSave(UserName());
14754         gameInfo.timeControl = TimeControlTagValue();
14755         break;
14756
14757       case MachinePlaysBlack:
14758         gameInfo.event = StrSave( appData.pgnEventHeader );
14759         gameInfo.site = StrSave(HostName());
14760         gameInfo.date = PGNDate();
14761         gameInfo.round = StrSave("-");
14762         gameInfo.white = StrSave(UserName());
14763         gameInfo.black = StrSave(first.tidy);
14764         gameInfo.timeControl = TimeControlTagValue();
14765         break;
14766
14767       case TwoMachinesPlay:
14768         gameInfo.event = StrSave( appData.pgnEventHeader );
14769         gameInfo.site = StrSave(HostName());
14770         gameInfo.date = PGNDate();
14771         if (roundNr > 0) {
14772             char buf[MSG_SIZ];
14773             snprintf(buf, MSG_SIZ, "%d", roundNr);
14774             gameInfo.round = StrSave(buf);
14775         } else {
14776             gameInfo.round = StrSave("-");
14777         }
14778         if (first.twoMachinesColor[0] == 'w') {
14779             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14780             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14781         } else {
14782             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14783             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14784         }
14785         gameInfo.timeControl = TimeControlTagValue();
14786         break;
14787
14788       case EditGame:
14789         gameInfo.event = StrSave("Edited game");
14790         gameInfo.site = StrSave(HostName());
14791         gameInfo.date = PGNDate();
14792         gameInfo.round = StrSave("-");
14793         gameInfo.white = StrSave("-");
14794         gameInfo.black = StrSave("-");
14795         gameInfo.result = r;
14796         gameInfo.resultDetails = p;
14797         break;
14798
14799       case EditPosition:
14800         gameInfo.event = StrSave("Edited position");
14801         gameInfo.site = StrSave(HostName());
14802         gameInfo.date = PGNDate();
14803         gameInfo.round = StrSave("-");
14804         gameInfo.white = StrSave("-");
14805         gameInfo.black = StrSave("-");
14806         break;
14807
14808       case IcsPlayingWhite:
14809       case IcsPlayingBlack:
14810       case IcsObserving:
14811       case IcsExamining:
14812         break;
14813
14814       case PlayFromGameFile:
14815         gameInfo.event = StrSave("Game from non-PGN file");
14816         gameInfo.site = StrSave(HostName());
14817         gameInfo.date = PGNDate();
14818         gameInfo.round = StrSave("-");
14819         gameInfo.white = StrSave("?");
14820         gameInfo.black = StrSave("?");
14821         break;
14822
14823       default:
14824         break;
14825     }
14826 }
14827
14828 void
14829 ReplaceComment(index, text)
14830      int index;
14831      char *text;
14832 {
14833     int len;
14834     char *p;
14835     float score;
14836
14837     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14838        pvInfoList[index-1].depth == len &&
14839        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14840        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14841     while (*text == '\n') text++;
14842     len = strlen(text);
14843     while (len > 0 && text[len - 1] == '\n') len--;
14844
14845     if (commentList[index] != NULL)
14846       free(commentList[index]);
14847
14848     if (len == 0) {
14849         commentList[index] = NULL;
14850         return;
14851     }
14852   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14853       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14854       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14855     commentList[index] = (char *) malloc(len + 2);
14856     strncpy(commentList[index], text, len);
14857     commentList[index][len] = '\n';
14858     commentList[index][len + 1] = NULLCHAR;
14859   } else {
14860     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14861     char *p;
14862     commentList[index] = (char *) malloc(len + 7);
14863     safeStrCpy(commentList[index], "{\n", 3);
14864     safeStrCpy(commentList[index]+2, text, len+1);
14865     commentList[index][len+2] = NULLCHAR;
14866     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14867     strcat(commentList[index], "\n}\n");
14868   }
14869 }
14870
14871 void
14872 CrushCRs(text)
14873      char *text;
14874 {
14875   char *p = text;
14876   char *q = text;
14877   char ch;
14878
14879   do {
14880     ch = *p++;
14881     if (ch == '\r') continue;
14882     *q++ = ch;
14883   } while (ch != '\0');
14884 }
14885
14886 void
14887 AppendComment(index, text, addBraces)
14888      int index;
14889      char *text;
14890      Boolean addBraces; // [HGM] braces: tells if we should add {}
14891 {
14892     int oldlen, len;
14893     char *old;
14894
14895 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14896     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14897
14898     CrushCRs(text);
14899     while (*text == '\n') text++;
14900     len = strlen(text);
14901     while (len > 0 && text[len - 1] == '\n') len--;
14902
14903     if (len == 0) return;
14904
14905     if (commentList[index] != NULL) {
14906       Boolean addClosingBrace = addBraces;
14907         old = commentList[index];
14908         oldlen = strlen(old);
14909         while(commentList[index][oldlen-1] ==  '\n')
14910           commentList[index][--oldlen] = NULLCHAR;
14911         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14912         safeStrCpy(commentList[index], old, oldlen + len + 6);
14913         free(old);
14914         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14915         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14916           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14917           while (*text == '\n') { text++; len--; }
14918           commentList[index][--oldlen] = NULLCHAR;
14919       }
14920         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14921         else          strcat(commentList[index], "\n");
14922         strcat(commentList[index], text);
14923         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14924         else          strcat(commentList[index], "\n");
14925     } else {
14926         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14927         if(addBraces)
14928           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14929         else commentList[index][0] = NULLCHAR;
14930         strcat(commentList[index], text);
14931         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14932         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14933     }
14934 }
14935
14936 static char * FindStr( char * text, char * sub_text )
14937 {
14938     char * result = strstr( text, sub_text );
14939
14940     if( result != NULL ) {
14941         result += strlen( sub_text );
14942     }
14943
14944     return result;
14945 }
14946
14947 /* [AS] Try to extract PV info from PGN comment */
14948 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14949 char *GetInfoFromComment( int index, char * text )
14950 {
14951     char * sep = text, *p;
14952
14953     if( text != NULL && index > 0 ) {
14954         int score = 0;
14955         int depth = 0;
14956         int time = -1, sec = 0, deci;
14957         char * s_eval = FindStr( text, "[%eval " );
14958         char * s_emt = FindStr( text, "[%emt " );
14959
14960         if( s_eval != NULL || s_emt != NULL ) {
14961             /* New style */
14962             char delim;
14963
14964             if( s_eval != NULL ) {
14965                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14966                     return text;
14967                 }
14968
14969                 if( delim != ']' ) {
14970                     return text;
14971                 }
14972             }
14973
14974             if( s_emt != NULL ) {
14975             }
14976                 return text;
14977         }
14978         else {
14979             /* We expect something like: [+|-]nnn.nn/dd */
14980             int score_lo = 0;
14981
14982             if(*text != '{') return text; // [HGM] braces: must be normal comment
14983
14984             sep = strchr( text, '/' );
14985             if( sep == NULL || sep < (text+4) ) {
14986                 return text;
14987             }
14988
14989             p = text;
14990             if(p[1] == '(') { // comment starts with PV
14991                p = strchr(p, ')'); // locate end of PV
14992                if(p == NULL || sep < p+5) return text;
14993                // at this point we have something like "{(.*) +0.23/6 ..."
14994                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14995                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14996                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14997             }
14998             time = -1; sec = -1; deci = -1;
14999             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
15000                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
15001                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
15002                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
15003                 return text;
15004             }
15005
15006             if( score_lo < 0 || score_lo >= 100 ) {
15007                 return text;
15008             }
15009
15010             if(sec >= 0) time = 600*time + 10*sec; else
15011             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
15012
15013             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
15014
15015             /* [HGM] PV time: now locate end of PV info */
15016             while( *++sep >= '0' && *sep <= '9'); // strip depth
15017             if(time >= 0)
15018             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
15019             if(sec >= 0)
15020             while( *++sep >= '0' && *sep <= '9'); // strip seconds
15021             if(deci >= 0)
15022             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
15023             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
15024         }
15025
15026         if( depth <= 0 ) {
15027             return text;
15028         }
15029
15030         if( time < 0 ) {
15031             time = -1;
15032         }
15033
15034         pvInfoList[index-1].depth = depth;
15035         pvInfoList[index-1].score = score;
15036         pvInfoList[index-1].time  = 10*time; // centi-sec
15037         if(*sep == '}') *sep = 0; else *--sep = '{';
15038         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
15039     }
15040     return sep;
15041 }
15042
15043 void
15044 SendToProgram(message, cps)
15045      char *message;
15046      ChessProgramState *cps;
15047 {
15048     int count, outCount, error;
15049     char buf[MSG_SIZ];
15050
15051     if (cps->pr == NoProc) return;
15052     Attention(cps);
15053
15054     if (appData.debugMode) {
15055         TimeMark now;
15056         GetTimeMark(&now);
15057         fprintf(debugFP, "%ld >%-6s: %s",
15058                 SubtractTimeMarks(&now, &programStartTime),
15059                 cps->which, message);
15060     }
15061
15062     count = strlen(message);
15063     outCount = OutputToProcess(cps->pr, message, count, &error);
15064     if (outCount < count && !exiting
15065                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
15066       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15067       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
15068         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15069             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15070                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15071                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15072                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15073             } else {
15074                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15075                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15076                 gameInfo.result = res;
15077             }
15078             gameInfo.resultDetails = StrSave(buf);
15079         }
15080         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15081         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15082     }
15083 }
15084
15085 void
15086 ReceiveFromProgram(isr, closure, message, count, error)
15087      InputSourceRef isr;
15088      VOIDSTAR closure;
15089      char *message;
15090      int count;
15091      int error;
15092 {
15093     char *end_str;
15094     char buf[MSG_SIZ];
15095     ChessProgramState *cps = (ChessProgramState *)closure;
15096
15097     if (isr != cps->isr) return; /* Killed intentionally */
15098     if (count <= 0) {
15099         if (count == 0) {
15100             RemoveInputSource(cps->isr);
15101             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15102             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15103                     _(cps->which), cps->program);
15104         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15105                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15106                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15107                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15108                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15109                 } else {
15110                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15111                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15112                     gameInfo.result = res;
15113                 }
15114                 gameInfo.resultDetails = StrSave(buf);
15115             }
15116             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15117             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15118         } else {
15119             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15120                     _(cps->which), cps->program);
15121             RemoveInputSource(cps->isr);
15122
15123             /* [AS] Program is misbehaving badly... kill it */
15124             if( count == -2 ) {
15125                 DestroyChildProcess( cps->pr, 9 );
15126                 cps->pr = NoProc;
15127             }
15128
15129             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15130         }
15131         return;
15132     }
15133
15134     if ((end_str = strchr(message, '\r')) != NULL)
15135       *end_str = NULLCHAR;
15136     if ((end_str = strchr(message, '\n')) != NULL)
15137       *end_str = NULLCHAR;
15138
15139     if (appData.debugMode) {
15140         TimeMark now; int print = 1;
15141         char *quote = ""; char c; int i;
15142
15143         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15144                 char start = message[0];
15145                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15146                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15147                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15148                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15149                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15150                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15151                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15152                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15153                    sscanf(message, "hint: %c", &c)!=1 && 
15154                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15155                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15156                     print = (appData.engineComments >= 2);
15157                 }
15158                 message[0] = start; // restore original message
15159         }
15160         if(print) {
15161                 GetTimeMark(&now);
15162                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15163                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15164                         quote,
15165                         message);
15166         }
15167     }
15168
15169     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15170     if (appData.icsEngineAnalyze) {
15171         if (strstr(message, "whisper") != NULL ||
15172              strstr(message, "kibitz") != NULL ||
15173             strstr(message, "tellics") != NULL) return;
15174     }
15175
15176     HandleMachineMove(message, cps);
15177 }
15178
15179
15180 void
15181 SendTimeControl(cps, mps, tc, inc, sd, st)
15182      ChessProgramState *cps;
15183      int mps, inc, sd, st;
15184      long tc;
15185 {
15186     char buf[MSG_SIZ];
15187     int seconds;
15188
15189     if( timeControl_2 > 0 ) {
15190         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15191             tc = timeControl_2;
15192         }
15193     }
15194     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15195     inc /= cps->timeOdds;
15196     st  /= cps->timeOdds;
15197
15198     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15199
15200     if (st > 0) {
15201       /* Set exact time per move, normally using st command */
15202       if (cps->stKludge) {
15203         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15204         seconds = st % 60;
15205         if (seconds == 0) {
15206           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15207         } else {
15208           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15209         }
15210       } else {
15211         snprintf(buf, MSG_SIZ, "st %d\n", st);
15212       }
15213     } else {
15214       /* Set conventional or incremental time control, using level command */
15215       if (seconds == 0) {
15216         /* Note old gnuchess bug -- minutes:seconds used to not work.
15217            Fixed in later versions, but still avoid :seconds
15218            when seconds is 0. */
15219         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15220       } else {
15221         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15222                  seconds, inc/1000.);
15223       }
15224     }
15225     SendToProgram(buf, cps);
15226
15227     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15228     /* Orthogonally, limit search to given depth */
15229     if (sd > 0) {
15230       if (cps->sdKludge) {
15231         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15232       } else {
15233         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15234       }
15235       SendToProgram(buf, cps);
15236     }
15237
15238     if(cps->nps >= 0) { /* [HGM] nps */
15239         if(cps->supportsNPS == FALSE)
15240           cps->nps = -1; // don't use if engine explicitly says not supported!
15241         else {
15242           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15243           SendToProgram(buf, cps);
15244         }
15245     }
15246 }
15247
15248 ChessProgramState *WhitePlayer()
15249 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15250 {
15251     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15252        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15253         return &second;
15254     return &first;
15255 }
15256
15257 void
15258 SendTimeRemaining(cps, machineWhite)
15259      ChessProgramState *cps;
15260      int /*boolean*/ machineWhite;
15261 {
15262     char message[MSG_SIZ];
15263     long time, otime;
15264
15265     /* Note: this routine must be called when the clocks are stopped
15266        or when they have *just* been set or switched; otherwise
15267        it will be off by the time since the current tick started.
15268     */
15269     if (machineWhite) {
15270         time = whiteTimeRemaining / 10;
15271         otime = blackTimeRemaining / 10;
15272     } else {
15273         time = blackTimeRemaining / 10;
15274         otime = whiteTimeRemaining / 10;
15275     }
15276     /* [HGM] translate opponent's time by time-odds factor */
15277     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15278     if (appData.debugMode) {
15279         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15280     }
15281
15282     if (time <= 0) time = 1;
15283     if (otime <= 0) otime = 1;
15284
15285     snprintf(message, MSG_SIZ, "time %ld\n", time);
15286     SendToProgram(message, cps);
15287
15288     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15289     SendToProgram(message, cps);
15290 }
15291
15292 int
15293 BoolFeature(p, name, loc, cps)
15294      char **p;
15295      char *name;
15296      int *loc;
15297      ChessProgramState *cps;
15298 {
15299   char buf[MSG_SIZ];
15300   int len = strlen(name);
15301   int val;
15302
15303   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15304     (*p) += len + 1;
15305     sscanf(*p, "%d", &val);
15306     *loc = (val != 0);
15307     while (**p && **p != ' ')
15308       (*p)++;
15309     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15310     SendToProgram(buf, cps);
15311     return TRUE;
15312   }
15313   return FALSE;
15314 }
15315
15316 int
15317 IntFeature(p, name, loc, cps)
15318      char **p;
15319      char *name;
15320      int *loc;
15321      ChessProgramState *cps;
15322 {
15323   char buf[MSG_SIZ];
15324   int len = strlen(name);
15325   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15326     (*p) += len + 1;
15327     sscanf(*p, "%d", loc);
15328     while (**p && **p != ' ') (*p)++;
15329     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15330     SendToProgram(buf, cps);
15331     return TRUE;
15332   }
15333   return FALSE;
15334 }
15335
15336 int
15337 StringFeature(p, name, loc, cps)
15338      char **p;
15339      char *name;
15340      char loc[];
15341      ChessProgramState *cps;
15342 {
15343   char buf[MSG_SIZ];
15344   int len = strlen(name);
15345   if (strncmp((*p), name, len) == 0
15346       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15347     (*p) += len + 2;
15348     sscanf(*p, "%[^\"]", loc);
15349     while (**p && **p != '\"') (*p)++;
15350     if (**p == '\"') (*p)++;
15351     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15352     SendToProgram(buf, cps);
15353     return TRUE;
15354   }
15355   return FALSE;
15356 }
15357
15358 int
15359 ParseOption(Option *opt, ChessProgramState *cps)
15360 // [HGM] options: process the string that defines an engine option, and determine
15361 // name, type, default value, and allowed value range
15362 {
15363         char *p, *q, buf[MSG_SIZ];
15364         int n, min = (-1)<<31, max = 1<<31, def;
15365
15366         if(p = strstr(opt->name, " -spin ")) {
15367             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15368             if(max < min) max = min; // enforce consistency
15369             if(def < min) def = min;
15370             if(def > max) def = max;
15371             opt->value = def;
15372             opt->min = min;
15373             opt->max = max;
15374             opt->type = Spin;
15375         } else if((p = strstr(opt->name, " -slider "))) {
15376             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15377             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15378             if(max < min) max = min; // enforce consistency
15379             if(def < min) def = min;
15380             if(def > max) def = max;
15381             opt->value = def;
15382             opt->min = min;
15383             opt->max = max;
15384             opt->type = Spin; // Slider;
15385         } else if((p = strstr(opt->name, " -string "))) {
15386             opt->textValue = p+9;
15387             opt->type = TextBox;
15388         } else if((p = strstr(opt->name, " -file "))) {
15389             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15390             opt->textValue = p+7;
15391             opt->type = FileName; // FileName;
15392         } else if((p = strstr(opt->name, " -path "))) {
15393             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15394             opt->textValue = p+7;
15395             opt->type = PathName; // PathName;
15396         } else if(p = strstr(opt->name, " -check ")) {
15397             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15398             opt->value = (def != 0);
15399             opt->type = CheckBox;
15400         } else if(p = strstr(opt->name, " -combo ")) {
15401             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15402             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15403             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15404             opt->value = n = 0;
15405             while(q = StrStr(q, " /// ")) {
15406                 n++; *q = 0;    // count choices, and null-terminate each of them
15407                 q += 5;
15408                 if(*q == '*') { // remember default, which is marked with * prefix
15409                     q++;
15410                     opt->value = n;
15411                 }
15412                 cps->comboList[cps->comboCnt++] = q;
15413             }
15414             cps->comboList[cps->comboCnt++] = NULL;
15415             opt->max = n + 1;
15416             opt->type = ComboBox;
15417         } else if(p = strstr(opt->name, " -button")) {
15418             opt->type = Button;
15419         } else if(p = strstr(opt->name, " -save")) {
15420             opt->type = SaveButton;
15421         } else return FALSE;
15422         *p = 0; // terminate option name
15423         // now look if the command-line options define a setting for this engine option.
15424         if(cps->optionSettings && cps->optionSettings[0])
15425             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15426         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15427           snprintf(buf, MSG_SIZ, "option %s", p);
15428                 if(p = strstr(buf, ",")) *p = 0;
15429                 if(q = strchr(buf, '=')) switch(opt->type) {
15430                     case ComboBox:
15431                         for(n=0; n<opt->max; n++)
15432                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15433                         break;
15434                     case TextBox:
15435                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15436                         break;
15437                     case Spin:
15438                     case CheckBox:
15439                         opt->value = atoi(q+1);
15440                     default:
15441                         break;
15442                 }
15443                 strcat(buf, "\n");
15444                 SendToProgram(buf, cps);
15445         }
15446         return TRUE;
15447 }
15448
15449 void
15450 FeatureDone(cps, val)
15451      ChessProgramState* cps;
15452      int val;
15453 {
15454   DelayedEventCallback cb = GetDelayedEvent();
15455   if ((cb == InitBackEnd3 && cps == &first) ||
15456       (cb == SettingsMenuIfReady && cps == &second) ||
15457       (cb == LoadEngine) ||
15458       (cb == TwoMachinesEventIfReady)) {
15459     CancelDelayedEvent();
15460     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15461   }
15462   cps->initDone = val;
15463 }
15464
15465 /* Parse feature command from engine */
15466 void
15467 ParseFeatures(args, cps)
15468      char* args;
15469      ChessProgramState *cps;
15470 {
15471   char *p = args;
15472   char *q;
15473   int val;
15474   char buf[MSG_SIZ];
15475
15476   for (;;) {
15477     while (*p == ' ') p++;
15478     if (*p == NULLCHAR) return;
15479
15480     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15481     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15482     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15483     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15484     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15485     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15486     if (BoolFeature(&p, "reuse", &val, cps)) {
15487       /* Engine can disable reuse, but can't enable it if user said no */
15488       if (!val) cps->reuse = FALSE;
15489       continue;
15490     }
15491     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15492     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
15493       if (gameMode == TwoMachinesPlay) {
15494         DisplayTwoMachinesTitle();
15495       } else {
15496         DisplayTitle("");
15497       }
15498       continue;
15499     }
15500     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
15501     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15502     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15503     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15504     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15505     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15506     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15507     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15508     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15509     if (IntFeature(&p, "done", &val, cps)) {
15510       FeatureDone(cps, val);
15511       continue;
15512     }
15513     /* Added by Tord: */
15514     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15515     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15516     /* End of additions by Tord */
15517
15518     /* [HGM] added features: */
15519     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15520     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15521     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15522     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15523     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15524     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
15525     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
15526         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15527           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15528             SendToProgram(buf, cps);
15529             continue;
15530         }
15531         if(cps->nrOptions >= MAX_OPTIONS) {
15532             cps->nrOptions--;
15533             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15534             DisplayError(buf, 0);
15535         }
15536         continue;
15537     }
15538     /* End of additions by HGM */
15539
15540     /* unknown feature: complain and skip */
15541     q = p;
15542     while (*q && *q != '=') q++;
15543     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15544     SendToProgram(buf, cps);
15545     p = q;
15546     if (*p == '=') {
15547       p++;
15548       if (*p == '\"') {
15549         p++;
15550         while (*p && *p != '\"') p++;
15551         if (*p == '\"') p++;
15552       } else {
15553         while (*p && *p != ' ') p++;
15554       }
15555     }
15556   }
15557
15558 }
15559
15560 void
15561 PeriodicUpdatesEvent(newState)
15562      int newState;
15563 {
15564     if (newState == appData.periodicUpdates)
15565       return;
15566
15567     appData.periodicUpdates=newState;
15568
15569     /* Display type changes, so update it now */
15570 //    DisplayAnalysis();
15571
15572     /* Get the ball rolling again... */
15573     if (newState) {
15574         AnalysisPeriodicEvent(1);
15575         StartAnalysisClock();
15576     }
15577 }
15578
15579 void
15580 PonderNextMoveEvent(newState)
15581      int newState;
15582 {
15583     if (newState == appData.ponderNextMove) return;
15584     if (gameMode == EditPosition) EditPositionDone(TRUE);
15585     if (newState) {
15586         SendToProgram("hard\n", &first);
15587         if (gameMode == TwoMachinesPlay) {
15588             SendToProgram("hard\n", &second);
15589         }
15590     } else {
15591         SendToProgram("easy\n", &first);
15592         thinkOutput[0] = NULLCHAR;
15593         if (gameMode == TwoMachinesPlay) {
15594             SendToProgram("easy\n", &second);
15595         }
15596     }
15597     appData.ponderNextMove = newState;
15598 }
15599
15600 void
15601 NewSettingEvent(option, feature, command, value)
15602      char *command;
15603      int option, value, *feature;
15604 {
15605     char buf[MSG_SIZ];
15606
15607     if (gameMode == EditPosition) EditPositionDone(TRUE);
15608     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15609     if(feature == NULL || *feature) SendToProgram(buf, &first);
15610     if (gameMode == TwoMachinesPlay) {
15611         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15612     }
15613 }
15614
15615 void
15616 ShowThinkingEvent()
15617 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15618 {
15619     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15620     int newState = appData.showThinking
15621         // [HGM] thinking: other features now need thinking output as well
15622         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15623
15624     if (oldState == newState) return;
15625     oldState = newState;
15626     if (gameMode == EditPosition) EditPositionDone(TRUE);
15627     if (oldState) {
15628         SendToProgram("post\n", &first);
15629         if (gameMode == TwoMachinesPlay) {
15630             SendToProgram("post\n", &second);
15631         }
15632     } else {
15633         SendToProgram("nopost\n", &first);
15634         thinkOutput[0] = NULLCHAR;
15635         if (gameMode == TwoMachinesPlay) {
15636             SendToProgram("nopost\n", &second);
15637         }
15638     }
15639 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15640 }
15641
15642 void
15643 AskQuestionEvent(title, question, replyPrefix, which)
15644      char *title; char *question; char *replyPrefix; char *which;
15645 {
15646   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15647   if (pr == NoProc) return;
15648   AskQuestion(title, question, replyPrefix, pr);
15649 }
15650
15651 void
15652 TypeInEvent(char firstChar)
15653 {
15654     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15655         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15656         gameMode == AnalyzeMode || gameMode == EditGame || 
15657         gameMode == EditPosition || gameMode == IcsExamining ||
15658         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15659         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15660                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15661                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15662         gameMode == Training) PopUpMoveDialog(firstChar);
15663 }
15664
15665 void
15666 TypeInDoneEvent(char *move)
15667 {
15668         Board board;
15669         int n, fromX, fromY, toX, toY;
15670         char promoChar;
15671         ChessMove moveType;
15672
15673         // [HGM] FENedit
15674         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15675                 EditPositionPasteFEN(move);
15676                 return;
15677         }
15678         // [HGM] movenum: allow move number to be typed in any mode
15679         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15680           ToNrEvent(2*n-1);
15681           return;
15682         }
15683
15684       if (gameMode != EditGame && currentMove != forwardMostMove && 
15685         gameMode != Training) {
15686         DisplayMoveError(_("Displayed move is not current"));
15687       } else {
15688         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15689           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15690         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15691         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15692           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15693           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15694         } else {
15695           DisplayMoveError(_("Could not parse move"));
15696         }
15697       }
15698 }
15699
15700 void
15701 DisplayMove(moveNumber)
15702      int moveNumber;
15703 {
15704     char message[MSG_SIZ];
15705     char res[MSG_SIZ];
15706     char cpThinkOutput[MSG_SIZ];
15707
15708     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15709
15710     if (moveNumber == forwardMostMove - 1 ||
15711         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15712
15713         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15714
15715         if (strchr(cpThinkOutput, '\n')) {
15716             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15717         }
15718     } else {
15719         *cpThinkOutput = NULLCHAR;
15720     }
15721
15722     /* [AS] Hide thinking from human user */
15723     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15724         *cpThinkOutput = NULLCHAR;
15725         if( thinkOutput[0] != NULLCHAR ) {
15726             int i;
15727
15728             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15729                 cpThinkOutput[i] = '.';
15730             }
15731             cpThinkOutput[i] = NULLCHAR;
15732             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15733         }
15734     }
15735
15736     if (moveNumber == forwardMostMove - 1 &&
15737         gameInfo.resultDetails != NULL) {
15738         if (gameInfo.resultDetails[0] == NULLCHAR) {
15739           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15740         } else {
15741           snprintf(res, MSG_SIZ, " {%s} %s",
15742                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15743         }
15744     } else {
15745         res[0] = NULLCHAR;
15746     }
15747
15748     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15749         DisplayMessage(res, cpThinkOutput);
15750     } else {
15751       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15752                 WhiteOnMove(moveNumber) ? " " : ".. ",
15753                 parseList[moveNumber], res);
15754         DisplayMessage(message, cpThinkOutput);
15755     }
15756 }
15757
15758 void
15759 DisplayComment(moveNumber, text)
15760      int moveNumber;
15761      char *text;
15762 {
15763     char title[MSG_SIZ];
15764
15765     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15766       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15767     } else {
15768       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15769               WhiteOnMove(moveNumber) ? " " : ".. ",
15770               parseList[moveNumber]);
15771     }
15772     if (text != NULL && (appData.autoDisplayComment || commentUp))
15773         CommentPopUp(title, text);
15774 }
15775
15776 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15777  * might be busy thinking or pondering.  It can be omitted if your
15778  * gnuchess is configured to stop thinking immediately on any user
15779  * input.  However, that gnuchess feature depends on the FIONREAD
15780  * ioctl, which does not work properly on some flavors of Unix.
15781  */
15782 void
15783 Attention(cps)
15784      ChessProgramState *cps;
15785 {
15786 #if ATTENTION
15787     if (!cps->useSigint) return;
15788     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15789     switch (gameMode) {
15790       case MachinePlaysWhite:
15791       case MachinePlaysBlack:
15792       case TwoMachinesPlay:
15793       case IcsPlayingWhite:
15794       case IcsPlayingBlack:
15795       case AnalyzeMode:
15796       case AnalyzeFile:
15797         /* Skip if we know it isn't thinking */
15798         if (!cps->maybeThinking) return;
15799         if (appData.debugMode)
15800           fprintf(debugFP, "Interrupting %s\n", cps->which);
15801         InterruptChildProcess(cps->pr);
15802         cps->maybeThinking = FALSE;
15803         break;
15804       default:
15805         break;
15806     }
15807 #endif /*ATTENTION*/
15808 }
15809
15810 int
15811 CheckFlags()
15812 {
15813     if (whiteTimeRemaining <= 0) {
15814         if (!whiteFlag) {
15815             whiteFlag = TRUE;
15816             if (appData.icsActive) {
15817                 if (appData.autoCallFlag &&
15818                     gameMode == IcsPlayingBlack && !blackFlag) {
15819                   SendToICS(ics_prefix);
15820                   SendToICS("flag\n");
15821                 }
15822             } else {
15823                 if (blackFlag) {
15824                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15825                 } else {
15826                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15827                     if (appData.autoCallFlag) {
15828                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15829                         return TRUE;
15830                     }
15831                 }
15832             }
15833         }
15834     }
15835     if (blackTimeRemaining <= 0) {
15836         if (!blackFlag) {
15837             blackFlag = TRUE;
15838             if (appData.icsActive) {
15839                 if (appData.autoCallFlag &&
15840                     gameMode == IcsPlayingWhite && !whiteFlag) {
15841                   SendToICS(ics_prefix);
15842                   SendToICS("flag\n");
15843                 }
15844             } else {
15845                 if (whiteFlag) {
15846                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15847                 } else {
15848                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15849                     if (appData.autoCallFlag) {
15850                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15851                         return TRUE;
15852                     }
15853                 }
15854             }
15855         }
15856     }
15857     return FALSE;
15858 }
15859
15860 void
15861 CheckTimeControl()
15862 {
15863     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15864         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15865
15866     /*
15867      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15868      */
15869     if ( !WhiteOnMove(forwardMostMove) ) {
15870         /* White made time control */
15871         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15872         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15873         /* [HGM] time odds: correct new time quota for time odds! */
15874                                             / WhitePlayer()->timeOdds;
15875         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15876     } else {
15877         lastBlack -= blackTimeRemaining;
15878         /* Black made time control */
15879         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15880                                             / WhitePlayer()->other->timeOdds;
15881         lastWhite = whiteTimeRemaining;
15882     }
15883 }
15884
15885 void
15886 DisplayBothClocks()
15887 {
15888     int wom = gameMode == EditPosition ?
15889       !blackPlaysFirst : WhiteOnMove(currentMove);
15890     DisplayWhiteClock(whiteTimeRemaining, wom);
15891     DisplayBlackClock(blackTimeRemaining, !wom);
15892 }
15893
15894
15895 /* Timekeeping seems to be a portability nightmare.  I think everyone
15896    has ftime(), but I'm really not sure, so I'm including some ifdefs
15897    to use other calls if you don't.  Clocks will be less accurate if
15898    you have neither ftime nor gettimeofday.
15899 */
15900
15901 /* VS 2008 requires the #include outside of the function */
15902 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15903 #include <sys/timeb.h>
15904 #endif
15905
15906 /* Get the current time as a TimeMark */
15907 void
15908 GetTimeMark(tm)
15909      TimeMark *tm;
15910 {
15911 #if HAVE_GETTIMEOFDAY
15912
15913     struct timeval timeVal;
15914     struct timezone timeZone;
15915
15916     gettimeofday(&timeVal, &timeZone);
15917     tm->sec = (long) timeVal.tv_sec;
15918     tm->ms = (int) (timeVal.tv_usec / 1000L);
15919
15920 #else /*!HAVE_GETTIMEOFDAY*/
15921 #if HAVE_FTIME
15922
15923 // include <sys/timeb.h> / moved to just above start of function
15924     struct timeb timeB;
15925
15926     ftime(&timeB);
15927     tm->sec = (long) timeB.time;
15928     tm->ms = (int) timeB.millitm;
15929
15930 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15931     tm->sec = (long) time(NULL);
15932     tm->ms = 0;
15933 #endif
15934 #endif
15935 }
15936
15937 /* Return the difference in milliseconds between two
15938    time marks.  We assume the difference will fit in a long!
15939 */
15940 long
15941 SubtractTimeMarks(tm2, tm1)
15942      TimeMark *tm2, *tm1;
15943 {
15944     return 1000L*(tm2->sec - tm1->sec) +
15945            (long) (tm2->ms - tm1->ms);
15946 }
15947
15948
15949 /*
15950  * Code to manage the game clocks.
15951  *
15952  * In tournament play, black starts the clock and then white makes a move.
15953  * We give the human user a slight advantage if he is playing white---the
15954  * clocks don't run until he makes his first move, so it takes zero time.
15955  * Also, we don't account for network lag, so we could get out of sync
15956  * with GNU Chess's clock -- but then, referees are always right.
15957  */
15958
15959 static TimeMark tickStartTM;
15960 static long intendedTickLength;
15961
15962 long
15963 NextTickLength(timeRemaining)
15964      long timeRemaining;
15965 {
15966     long nominalTickLength, nextTickLength;
15967
15968     if (timeRemaining > 0L && timeRemaining <= 10000L)
15969       nominalTickLength = 100L;
15970     else
15971       nominalTickLength = 1000L;
15972     nextTickLength = timeRemaining % nominalTickLength;
15973     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15974
15975     return nextTickLength;
15976 }
15977
15978 /* Adjust clock one minute up or down */
15979 void
15980 AdjustClock(Boolean which, int dir)
15981 {
15982     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15983     if(which) blackTimeRemaining += 60000*dir;
15984     else      whiteTimeRemaining += 60000*dir;
15985     DisplayBothClocks();
15986     adjustedClock = TRUE;
15987 }
15988
15989 /* Stop clocks and reset to a fresh time control */
15990 void
15991 ResetClocks()
15992 {
15993     (void) StopClockTimer();
15994     if (appData.icsActive) {
15995         whiteTimeRemaining = blackTimeRemaining = 0;
15996     } else if (searchTime) {
15997         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15998         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15999     } else { /* [HGM] correct new time quote for time odds */
16000         whiteTC = blackTC = fullTimeControlString;
16001         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
16002         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
16003     }
16004     if (whiteFlag || blackFlag) {
16005         DisplayTitle("");
16006         whiteFlag = blackFlag = FALSE;
16007     }
16008     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
16009     DisplayBothClocks();
16010     adjustedClock = FALSE;
16011 }
16012
16013 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
16014
16015 /* Decrement running clock by amount of time that has passed */
16016 void
16017 DecrementClocks()
16018 {
16019     long timeRemaining;
16020     long lastTickLength, fudge;
16021     TimeMark now;
16022
16023     if (!appData.clockMode) return;
16024     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
16025
16026     GetTimeMark(&now);
16027
16028     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16029
16030     /* Fudge if we woke up a little too soon */
16031     fudge = intendedTickLength - lastTickLength;
16032     if (fudge < 0 || fudge > FUDGE) fudge = 0;
16033
16034     if (WhiteOnMove(forwardMostMove)) {
16035         if(whiteNPS >= 0) lastTickLength = 0;
16036         timeRemaining = whiteTimeRemaining -= lastTickLength;
16037         if(timeRemaining < 0 && !appData.icsActive) {
16038             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
16039             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
16040                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
16041                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
16042             }
16043         }
16044         DisplayWhiteClock(whiteTimeRemaining - fudge,
16045                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16046     } else {
16047         if(blackNPS >= 0) lastTickLength = 0;
16048         timeRemaining = blackTimeRemaining -= lastTickLength;
16049         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
16050             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
16051             if(suddenDeath) {
16052                 blackStartMove = forwardMostMove;
16053                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
16054             }
16055         }
16056         DisplayBlackClock(blackTimeRemaining - fudge,
16057                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
16058     }
16059     if (CheckFlags()) return;
16060
16061     tickStartTM = now;
16062     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
16063     StartClockTimer(intendedTickLength);
16064
16065     /* if the time remaining has fallen below the alarm threshold, sound the
16066      * alarm. if the alarm has sounded and (due to a takeback or time control
16067      * with increment) the time remaining has increased to a level above the
16068      * threshold, reset the alarm so it can sound again.
16069      */
16070
16071     if (appData.icsActive && appData.icsAlarm) {
16072
16073         /* make sure we are dealing with the user's clock */
16074         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
16075                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
16076            )) return;
16077
16078         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
16079             alarmSounded = FALSE;
16080         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
16081             PlayAlarmSound();
16082             alarmSounded = TRUE;
16083         }
16084     }
16085 }
16086
16087
16088 /* A player has just moved, so stop the previously running
16089    clock and (if in clock mode) start the other one.
16090    We redisplay both clocks in case we're in ICS mode, because
16091    ICS gives us an update to both clocks after every move.
16092    Note that this routine is called *after* forwardMostMove
16093    is updated, so the last fractional tick must be subtracted
16094    from the color that is *not* on move now.
16095 */
16096 void
16097 SwitchClocks(int newMoveNr)
16098 {
16099     long lastTickLength;
16100     TimeMark now;
16101     int flagged = FALSE;
16102
16103     GetTimeMark(&now);
16104
16105     if (StopClockTimer() && appData.clockMode) {
16106         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16107         if (!WhiteOnMove(forwardMostMove)) {
16108             if(blackNPS >= 0) lastTickLength = 0;
16109             blackTimeRemaining -= lastTickLength;
16110            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16111 //         if(pvInfoList[forwardMostMove].time == -1)
16112                  pvInfoList[forwardMostMove].time =               // use GUI time
16113                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
16114         } else {
16115            if(whiteNPS >= 0) lastTickLength = 0;
16116            whiteTimeRemaining -= lastTickLength;
16117            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16118 //         if(pvInfoList[forwardMostMove].time == -1)
16119                  pvInfoList[forwardMostMove].time =
16120                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16121         }
16122         flagged = CheckFlags();
16123     }
16124     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16125     CheckTimeControl();
16126
16127     if (flagged || !appData.clockMode) return;
16128
16129     switch (gameMode) {
16130       case MachinePlaysBlack:
16131       case MachinePlaysWhite:
16132       case BeginningOfGame:
16133         if (pausing) return;
16134         break;
16135
16136       case EditGame:
16137       case PlayFromGameFile:
16138       case IcsExamining:
16139         return;
16140
16141       default:
16142         break;
16143     }
16144
16145     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16146         if(WhiteOnMove(forwardMostMove))
16147              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16148         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16149     }
16150
16151     tickStartTM = now;
16152     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16153       whiteTimeRemaining : blackTimeRemaining);
16154     StartClockTimer(intendedTickLength);
16155 }
16156
16157
16158 /* Stop both clocks */
16159 void
16160 StopClocks()
16161 {
16162     long lastTickLength;
16163     TimeMark now;
16164
16165     if (!StopClockTimer()) return;
16166     if (!appData.clockMode) return;
16167
16168     GetTimeMark(&now);
16169
16170     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16171     if (WhiteOnMove(forwardMostMove)) {
16172         if(whiteNPS >= 0) lastTickLength = 0;
16173         whiteTimeRemaining -= lastTickLength;
16174         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16175     } else {
16176         if(blackNPS >= 0) lastTickLength = 0;
16177         blackTimeRemaining -= lastTickLength;
16178         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16179     }
16180     CheckFlags();
16181 }
16182
16183 /* Start clock of player on move.  Time may have been reset, so
16184    if clock is already running, stop and restart it. */
16185 void
16186 StartClocks()
16187 {
16188     (void) StopClockTimer(); /* in case it was running already */
16189     DisplayBothClocks();
16190     if (CheckFlags()) return;
16191
16192     if (!appData.clockMode) return;
16193     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16194
16195     GetTimeMark(&tickStartTM);
16196     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16197       whiteTimeRemaining : blackTimeRemaining);
16198
16199    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16200     whiteNPS = blackNPS = -1;
16201     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16202        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16203         whiteNPS = first.nps;
16204     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16205        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16206         blackNPS = first.nps;
16207     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16208         whiteNPS = second.nps;
16209     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16210         blackNPS = second.nps;
16211     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16212
16213     StartClockTimer(intendedTickLength);
16214 }
16215
16216 char *
16217 TimeString(ms)
16218      long ms;
16219 {
16220     long second, minute, hour, day;
16221     char *sign = "";
16222     static char buf[32];
16223
16224     if (ms > 0 && ms <= 9900) {
16225       /* convert milliseconds to tenths, rounding up */
16226       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16227
16228       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16229       return buf;
16230     }
16231
16232     /* convert milliseconds to seconds, rounding up */
16233     /* use floating point to avoid strangeness of integer division
16234        with negative dividends on many machines */
16235     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16236
16237     if (second < 0) {
16238         sign = "-";
16239         second = -second;
16240     }
16241
16242     day = second / (60 * 60 * 24);
16243     second = second % (60 * 60 * 24);
16244     hour = second / (60 * 60);
16245     second = second % (60 * 60);
16246     minute = second / 60;
16247     second = second % 60;
16248
16249     if (day > 0)
16250       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16251               sign, day, hour, minute, second);
16252     else if (hour > 0)
16253       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16254     else
16255       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16256
16257     return buf;
16258 }
16259
16260
16261 /*
16262  * This is necessary because some C libraries aren't ANSI C compliant yet.
16263  */
16264 char *
16265 StrStr(string, match)
16266      char *string, *match;
16267 {
16268     int i, length;
16269
16270     length = strlen(match);
16271
16272     for (i = strlen(string) - length; i >= 0; i--, string++)
16273       if (!strncmp(match, string, length))
16274         return string;
16275
16276     return NULL;
16277 }
16278
16279 char *
16280 StrCaseStr(string, match)
16281      char *string, *match;
16282 {
16283     int i, j, length;
16284
16285     length = strlen(match);
16286
16287     for (i = strlen(string) - length; i >= 0; i--, string++) {
16288         for (j = 0; j < length; j++) {
16289             if (ToLower(match[j]) != ToLower(string[j]))
16290               break;
16291         }
16292         if (j == length) return string;
16293     }
16294
16295     return NULL;
16296 }
16297
16298 #ifndef _amigados
16299 int
16300 StrCaseCmp(s1, s2)
16301      char *s1, *s2;
16302 {
16303     char c1, c2;
16304
16305     for (;;) {
16306         c1 = ToLower(*s1++);
16307         c2 = ToLower(*s2++);
16308         if (c1 > c2) return 1;
16309         if (c1 < c2) return -1;
16310         if (c1 == NULLCHAR) return 0;
16311     }
16312 }
16313
16314
16315 int
16316 ToLower(c)
16317      int c;
16318 {
16319     return isupper(c) ? tolower(c) : c;
16320 }
16321
16322
16323 int
16324 ToUpper(c)
16325      int c;
16326 {
16327     return islower(c) ? toupper(c) : c;
16328 }
16329 #endif /* !_amigados    */
16330
16331 char *
16332 StrSave(s)
16333      char *s;
16334 {
16335   char *ret;
16336
16337   if ((ret = (char *) malloc(strlen(s) + 1)))
16338     {
16339       safeStrCpy(ret, s, strlen(s)+1);
16340     }
16341   return ret;
16342 }
16343
16344 char *
16345 StrSavePtr(s, savePtr)
16346      char *s, **savePtr;
16347 {
16348     if (*savePtr) {
16349         free(*savePtr);
16350     }
16351     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16352       safeStrCpy(*savePtr, s, strlen(s)+1);
16353     }
16354     return(*savePtr);
16355 }
16356
16357 char *
16358 PGNDate()
16359 {
16360     time_t clock;
16361     struct tm *tm;
16362     char buf[MSG_SIZ];
16363
16364     clock = time((time_t *)NULL);
16365     tm = localtime(&clock);
16366     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16367             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16368     return StrSave(buf);
16369 }
16370
16371
16372 char *
16373 PositionToFEN(move, overrideCastling)
16374      int move;
16375      char *overrideCastling;
16376 {
16377     int i, j, fromX, fromY, toX, toY;
16378     int whiteToPlay;
16379     char buf[MSG_SIZ];
16380     char *p, *q;
16381     int emptycount;
16382     ChessSquare piece;
16383
16384     whiteToPlay = (gameMode == EditPosition) ?
16385       !blackPlaysFirst : (move % 2 == 0);
16386     p = buf;
16387
16388     /* Piece placement data */
16389     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16390         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16391         emptycount = 0;
16392         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16393             if (boards[move][i][j] == EmptySquare) {
16394                 emptycount++;
16395             } else { ChessSquare piece = boards[move][i][j];
16396                 if (emptycount > 0) {
16397                     if(emptycount<10) /* [HGM] can be >= 10 */
16398                         *p++ = '0' + emptycount;
16399                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16400                     emptycount = 0;
16401                 }
16402                 if(PieceToChar(piece) == '+') {
16403                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16404                     *p++ = '+';
16405                     piece = (ChessSquare)(DEMOTED piece);
16406                 }
16407                 *p++ = PieceToChar(piece);
16408                 if(p[-1] == '~') {
16409                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16410                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16411                     *p++ = '~';
16412                 }
16413             }
16414         }
16415         if (emptycount > 0) {
16416             if(emptycount<10) /* [HGM] can be >= 10 */
16417                 *p++ = '0' + emptycount;
16418             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16419             emptycount = 0;
16420         }
16421         *p++ = '/';
16422     }
16423     *(p - 1) = ' ';
16424
16425     /* [HGM] print Crazyhouse or Shogi holdings */
16426     if( gameInfo.holdingsWidth ) {
16427         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16428         q = p;
16429         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16430             piece = boards[move][i][BOARD_WIDTH-1];
16431             if( piece != EmptySquare )
16432               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16433                   *p++ = PieceToChar(piece);
16434         }
16435         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16436             piece = boards[move][BOARD_HEIGHT-i-1][0];
16437             if( piece != EmptySquare )
16438               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16439                   *p++ = PieceToChar(piece);
16440         }
16441
16442         if( q == p ) *p++ = '-';
16443         *p++ = ']';
16444         *p++ = ' ';
16445     }
16446
16447     /* Active color */
16448     *p++ = whiteToPlay ? 'w' : 'b';
16449     *p++ = ' ';
16450
16451   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16452     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16453   } else {
16454   if(nrCastlingRights) {
16455      q = p;
16456      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16457        /* [HGM] write directly from rights */
16458            if(boards[move][CASTLING][2] != NoRights &&
16459               boards[move][CASTLING][0] != NoRights   )
16460                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16461            if(boards[move][CASTLING][2] != NoRights &&
16462               boards[move][CASTLING][1] != NoRights   )
16463                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16464            if(boards[move][CASTLING][5] != NoRights &&
16465               boards[move][CASTLING][3] != NoRights   )
16466                 *p++ = boards[move][CASTLING][3] + AAA;
16467            if(boards[move][CASTLING][5] != NoRights &&
16468               boards[move][CASTLING][4] != NoRights   )
16469                 *p++ = boards[move][CASTLING][4] + AAA;
16470      } else {
16471
16472         /* [HGM] write true castling rights */
16473         if( nrCastlingRights == 6 ) {
16474             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16475                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16476             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16477                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16478             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16479                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16480             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16481                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16482         }
16483      }
16484      if (q == p) *p++ = '-'; /* No castling rights */
16485      *p++ = ' ';
16486   }
16487
16488   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16489      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16490     /* En passant target square */
16491     if (move > backwardMostMove) {
16492         fromX = moveList[move - 1][0] - AAA;
16493         fromY = moveList[move - 1][1] - ONE;
16494         toX = moveList[move - 1][2] - AAA;
16495         toY = moveList[move - 1][3] - ONE;
16496         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16497             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16498             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16499             fromX == toX) {
16500             /* 2-square pawn move just happened */
16501             *p++ = toX + AAA;
16502             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16503         } else {
16504             *p++ = '-';
16505         }
16506     } else if(move == backwardMostMove) {
16507         // [HGM] perhaps we should always do it like this, and forget the above?
16508         if((signed char)boards[move][EP_STATUS] >= 0) {
16509             *p++ = boards[move][EP_STATUS] + AAA;
16510             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16511         } else {
16512             *p++ = '-';
16513         }
16514     } else {
16515         *p++ = '-';
16516     }
16517     *p++ = ' ';
16518   }
16519   }
16520
16521     /* [HGM] find reversible plies */
16522     {   int i = 0, j=move;
16523
16524         if (appData.debugMode) { int k;
16525             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16526             for(k=backwardMostMove; k<=forwardMostMove; k++)
16527                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16528
16529         }
16530
16531         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16532         if( j == backwardMostMove ) i += initialRulePlies;
16533         sprintf(p, "%d ", i);
16534         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16535     }
16536     /* Fullmove number */
16537     sprintf(p, "%d", (move / 2) + 1);
16538
16539     return StrSave(buf);
16540 }
16541
16542 Boolean
16543 ParseFEN(board, blackPlaysFirst, fen)
16544     Board board;
16545      int *blackPlaysFirst;
16546      char *fen;
16547 {
16548     int i, j;
16549     char *p, c;
16550     int emptycount;
16551     ChessSquare piece;
16552
16553     p = fen;
16554
16555     /* [HGM] by default clear Crazyhouse holdings, if present */
16556     if(gameInfo.holdingsWidth) {
16557        for(i=0; i<BOARD_HEIGHT; i++) {
16558            board[i][0]             = EmptySquare; /* black holdings */
16559            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16560            board[i][1]             = (ChessSquare) 0; /* black counts */
16561            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16562        }
16563     }
16564
16565     /* Piece placement data */
16566     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16567         j = 0;
16568         for (;;) {
16569             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16570                 if (*p == '/') p++;
16571                 emptycount = gameInfo.boardWidth - j;
16572                 while (emptycount--)
16573                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16574                 break;
16575 #if(BOARD_FILES >= 10)
16576             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16577                 p++; emptycount=10;
16578                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16579                 while (emptycount--)
16580                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16581 #endif
16582             } else if (isdigit(*p)) {
16583                 emptycount = *p++ - '0';
16584                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16585                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16586                 while (emptycount--)
16587                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16588             } else if (*p == '+' || isalpha(*p)) {
16589                 if (j >= gameInfo.boardWidth) return FALSE;
16590                 if(*p=='+') {
16591                     piece = CharToPiece(*++p);
16592                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16593                     piece = (ChessSquare) (PROMOTED piece ); p++;
16594                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16595                 } else piece = CharToPiece(*p++);
16596
16597                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16598                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16599                     piece = (ChessSquare) (PROMOTED piece);
16600                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16601                     p++;
16602                 }
16603                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16604             } else {
16605                 return FALSE;
16606             }
16607         }
16608     }
16609     while (*p == '/' || *p == ' ') p++;
16610
16611     /* [HGM] look for Crazyhouse holdings here */
16612     while(*p==' ') p++;
16613     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16614         if(*p == '[') p++;
16615         if(*p == '-' ) p++; /* empty holdings */ else {
16616             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16617             /* if we would allow FEN reading to set board size, we would   */
16618             /* have to add holdings and shift the board read so far here   */
16619             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16620                 p++;
16621                 if((int) piece >= (int) BlackPawn ) {
16622                     i = (int)piece - (int)BlackPawn;
16623                     i = PieceToNumber((ChessSquare)i);
16624                     if( i >= gameInfo.holdingsSize ) return FALSE;
16625                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16626                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16627                 } else {
16628                     i = (int)piece - (int)WhitePawn;
16629                     i = PieceToNumber((ChessSquare)i);
16630                     if( i >= gameInfo.holdingsSize ) return FALSE;
16631                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16632                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16633                 }
16634             }
16635         }
16636         if(*p == ']') p++;
16637     }
16638
16639     while(*p == ' ') p++;
16640
16641     /* Active color */
16642     c = *p++;
16643     if(appData.colorNickNames) {
16644       if( c == appData.colorNickNames[0] ) c = 'w'; else
16645       if( c == appData.colorNickNames[1] ) c = 'b';
16646     }
16647     switch (c) {
16648       case 'w':
16649         *blackPlaysFirst = FALSE;
16650         break;
16651       case 'b':
16652         *blackPlaysFirst = TRUE;
16653         break;
16654       default:
16655         return FALSE;
16656     }
16657
16658     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16659     /* return the extra info in global variiables             */
16660
16661     /* set defaults in case FEN is incomplete */
16662     board[EP_STATUS] = EP_UNKNOWN;
16663     for(i=0; i<nrCastlingRights; i++ ) {
16664         board[CASTLING][i] =
16665             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16666     }   /* assume possible unless obviously impossible */
16667     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16668     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16669     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16670                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16671     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16672     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16673     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16674                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16675     FENrulePlies = 0;
16676
16677     while(*p==' ') p++;
16678     if(nrCastlingRights) {
16679       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16680           /* castling indicator present, so default becomes no castlings */
16681           for(i=0; i<nrCastlingRights; i++ ) {
16682                  board[CASTLING][i] = NoRights;
16683           }
16684       }
16685       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16686              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16687              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16688              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16689         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16690
16691         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16692             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16693             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16694         }
16695         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16696             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16697         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16698                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16699         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16700                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16701         switch(c) {
16702           case'K':
16703               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16704               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16705               board[CASTLING][2] = whiteKingFile;
16706               break;
16707           case'Q':
16708               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16709               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16710               board[CASTLING][2] = whiteKingFile;
16711               break;
16712           case'k':
16713               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16714               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16715               board[CASTLING][5] = blackKingFile;
16716               break;
16717           case'q':
16718               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16719               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16720               board[CASTLING][5] = blackKingFile;
16721           case '-':
16722               break;
16723           default: /* FRC castlings */
16724               if(c >= 'a') { /* black rights */
16725                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16726                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16727                   if(i == BOARD_RGHT) break;
16728                   board[CASTLING][5] = i;
16729                   c -= AAA;
16730                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16731                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16732                   if(c > i)
16733                       board[CASTLING][3] = c;
16734                   else
16735                       board[CASTLING][4] = c;
16736               } else { /* white rights */
16737                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16738                     if(board[0][i] == WhiteKing) break;
16739                   if(i == BOARD_RGHT) break;
16740                   board[CASTLING][2] = i;
16741                   c -= AAA - 'a' + 'A';
16742                   if(board[0][c] >= WhiteKing) break;
16743                   if(c > i)
16744                       board[CASTLING][0] = c;
16745                   else
16746                       board[CASTLING][1] = c;
16747               }
16748         }
16749       }
16750       for(i=0; i<nrCastlingRights; i++)
16751         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16752     if (appData.debugMode) {
16753         fprintf(debugFP, "FEN castling rights:");
16754         for(i=0; i<nrCastlingRights; i++)
16755         fprintf(debugFP, " %d", board[CASTLING][i]);
16756         fprintf(debugFP, "\n");
16757     }
16758
16759       while(*p==' ') p++;
16760     }
16761
16762     /* read e.p. field in games that know e.p. capture */
16763     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16764        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16765       if(*p=='-') {
16766         p++; board[EP_STATUS] = EP_NONE;
16767       } else {
16768          char c = *p++ - AAA;
16769
16770          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16771          if(*p >= '0' && *p <='9') p++;
16772          board[EP_STATUS] = c;
16773       }
16774     }
16775
16776
16777     if(sscanf(p, "%d", &i) == 1) {
16778         FENrulePlies = i; /* 50-move ply counter */
16779         /* (The move number is still ignored)    */
16780     }
16781
16782     return TRUE;
16783 }
16784
16785 void
16786 EditPositionPasteFEN(char *fen)
16787 {
16788   if (fen != NULL) {
16789     Board initial_position;
16790
16791     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16792       DisplayError(_("Bad FEN position in clipboard"), 0);
16793       return ;
16794     } else {
16795       int savedBlackPlaysFirst = blackPlaysFirst;
16796       EditPositionEvent();
16797       blackPlaysFirst = savedBlackPlaysFirst;
16798       CopyBoard(boards[0], initial_position);
16799       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16800       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16801       DisplayBothClocks();
16802       DrawPosition(FALSE, boards[currentMove]);
16803     }
16804   }
16805 }
16806
16807 static char cseq[12] = "\\   ";
16808
16809 Boolean set_cont_sequence(char *new_seq)
16810 {
16811     int len;
16812     Boolean ret;
16813
16814     // handle bad attempts to set the sequence
16815         if (!new_seq)
16816                 return 0; // acceptable error - no debug
16817
16818     len = strlen(new_seq);
16819     ret = (len > 0) && (len < sizeof(cseq));
16820     if (ret)
16821       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16822     else if (appData.debugMode)
16823       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16824     return ret;
16825 }
16826
16827 /*
16828     reformat a source message so words don't cross the width boundary.  internal
16829     newlines are not removed.  returns the wrapped size (no null character unless
16830     included in source message).  If dest is NULL, only calculate the size required
16831     for the dest buffer.  lp argument indicats line position upon entry, and it's
16832     passed back upon exit.
16833 */
16834 int wrap(char *dest, char *src, int count, int width, int *lp)
16835 {
16836     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16837
16838     cseq_len = strlen(cseq);
16839     old_line = line = *lp;
16840     ansi = len = clen = 0;
16841
16842     for (i=0; i < count; i++)
16843     {
16844         if (src[i] == '\033')
16845             ansi = 1;
16846
16847         // if we hit the width, back up
16848         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16849         {
16850             // store i & len in case the word is too long
16851             old_i = i, old_len = len;
16852
16853             // find the end of the last word
16854             while (i && src[i] != ' ' && src[i] != '\n')
16855             {
16856                 i--;
16857                 len--;
16858             }
16859
16860             // word too long?  restore i & len before splitting it
16861             if ((old_i-i+clen) >= width)
16862             {
16863                 i = old_i;
16864                 len = old_len;
16865             }
16866
16867             // extra space?
16868             if (i && src[i-1] == ' ')
16869                 len--;
16870
16871             if (src[i] != ' ' && src[i] != '\n')
16872             {
16873                 i--;
16874                 if (len)
16875                     len--;
16876             }
16877
16878             // now append the newline and continuation sequence
16879             if (dest)
16880                 dest[len] = '\n';
16881             len++;
16882             if (dest)
16883                 strncpy(dest+len, cseq, cseq_len);
16884             len += cseq_len;
16885             line = cseq_len;
16886             clen = cseq_len;
16887             continue;
16888         }
16889
16890         if (dest)
16891             dest[len] = src[i];
16892         len++;
16893         if (!ansi)
16894             line++;
16895         if (src[i] == '\n')
16896             line = 0;
16897         if (src[i] == 'm')
16898             ansi = 0;
16899     }
16900     if (dest && appData.debugMode)
16901     {
16902         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16903             count, width, line, len, *lp);
16904         show_bytes(debugFP, src, count);
16905         fprintf(debugFP, "\ndest: ");
16906         show_bytes(debugFP, dest, len);
16907         fprintf(debugFP, "\n");
16908     }
16909     *lp = dest ? line : old_line;
16910
16911     return len;
16912 }
16913
16914 // [HGM] vari: routines for shelving variations
16915 Boolean modeRestore = FALSE;
16916
16917 void
16918 PushInner(int firstMove, int lastMove)
16919 {
16920         int i, j, nrMoves = lastMove - firstMove;
16921
16922         // push current tail of game on stack
16923         savedResult[storedGames] = gameInfo.result;
16924         savedDetails[storedGames] = gameInfo.resultDetails;
16925         gameInfo.resultDetails = NULL;
16926         savedFirst[storedGames] = firstMove;
16927         savedLast [storedGames] = lastMove;
16928         savedFramePtr[storedGames] = framePtr;
16929         framePtr -= nrMoves; // reserve space for the boards
16930         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16931             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16932             for(j=0; j<MOVE_LEN; j++)
16933                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16934             for(j=0; j<2*MOVE_LEN; j++)
16935                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16936             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16937             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16938             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16939             pvInfoList[firstMove+i-1].depth = 0;
16940             commentList[framePtr+i] = commentList[firstMove+i];
16941             commentList[firstMove+i] = NULL;
16942         }
16943
16944         storedGames++;
16945         forwardMostMove = firstMove; // truncate game so we can start variation
16946 }
16947
16948 void
16949 PushTail(int firstMove, int lastMove)
16950 {
16951         if(appData.icsActive) { // only in local mode
16952                 forwardMostMove = currentMove; // mimic old ICS behavior
16953                 return;
16954         }
16955         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16956
16957         PushInner(firstMove, lastMove);
16958         if(storedGames == 1) GreyRevert(FALSE);
16959         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16960 }
16961
16962 void
16963 PopInner(Boolean annotate)
16964 {
16965         int i, j, nrMoves;
16966         char buf[8000], moveBuf[20];
16967
16968         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16969         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16970         nrMoves = savedLast[storedGames] - currentMove;
16971         if(annotate) {
16972                 int cnt = 10;
16973                 if(!WhiteOnMove(currentMove))
16974                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16975                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16976                 for(i=currentMove; i<forwardMostMove; i++) {
16977                         if(WhiteOnMove(i))
16978                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16979                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16980                         strcat(buf, moveBuf);
16981                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16982                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16983                 }
16984                 strcat(buf, ")");
16985         }
16986         for(i=1; i<=nrMoves; i++) { // copy last variation back
16987             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16988             for(j=0; j<MOVE_LEN; j++)
16989                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16990             for(j=0; j<2*MOVE_LEN; j++)
16991                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16992             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16993             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16994             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16995             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16996             commentList[currentMove+i] = commentList[framePtr+i];
16997             commentList[framePtr+i] = NULL;
16998         }
16999         if(annotate) AppendComment(currentMove+1, buf, FALSE);
17000         framePtr = savedFramePtr[storedGames];
17001         gameInfo.result = savedResult[storedGames];
17002         if(gameInfo.resultDetails != NULL) {
17003             free(gameInfo.resultDetails);
17004       }
17005         gameInfo.resultDetails = savedDetails[storedGames];
17006         forwardMostMove = currentMove + nrMoves;
17007 }
17008
17009 Boolean
17010 PopTail(Boolean annotate)
17011 {
17012         if(appData.icsActive) return FALSE; // only in local mode
17013         if(!storedGames) return FALSE; // sanity
17014         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
17015
17016         PopInner(annotate);
17017         if(currentMove < forwardMostMove) ForwardEvent(); else
17018         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
17019
17020         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
17021         return TRUE;
17022 }
17023
17024 void
17025 CleanupTail()
17026 {       // remove all shelved variations
17027         int i;
17028         for(i=0; i<storedGames; i++) {
17029             if(savedDetails[i])
17030                 free(savedDetails[i]);
17031             savedDetails[i] = NULL;
17032         }
17033         for(i=framePtr; i<MAX_MOVES; i++) {
17034                 if(commentList[i]) free(commentList[i]);
17035                 commentList[i] = NULL;
17036         }
17037         framePtr = MAX_MOVES-1;
17038         storedGames = 0;
17039 }
17040
17041 void
17042 LoadVariation(int index, char *text)
17043 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
17044         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
17045         int level = 0, move;
17046
17047         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
17048         // first find outermost bracketing variation
17049         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
17050             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
17051                 if(*p == '{') wait = '}'; else
17052                 if(*p == '[') wait = ']'; else
17053                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
17054                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
17055             }
17056             if(*p == wait) wait = NULLCHAR; // closing ]} found
17057             p++;
17058         }
17059         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
17060         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
17061         end[1] = NULLCHAR; // clip off comment beyond variation
17062         ToNrEvent(currentMove-1);
17063         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
17064         // kludge: use ParsePV() to append variation to game
17065         move = currentMove;
17066         ParsePV(start, TRUE, TRUE);
17067         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
17068         ClearPremoveHighlights();
17069         CommentPopDown();
17070         ToNrEvent(currentMove+1);
17071 }
17072