e669f50bd1f44505a912008340a57cdb7abf19b5
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152                          char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154                       char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225
226 #ifdef WIN32
227        extern void ConsoleCreate();
228 #endif
229
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
233
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
241 Boolean abortMatch;
242
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
246 int endPV = -1;
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
250 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
254 Boolean partnerUp;
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
266 int chattingPartner;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
272
273 /* States for ics_getting_history */
274 #define H_FALSE 0
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
280
281 /* whosays values for GameEnds */
282 #define GE_ICS 0
283 #define GE_ENGINE 1
284 #define GE_PLAYER 2
285 #define GE_FILE 3
286 #define GE_XBOARD 4
287 #define GE_ENGINE1 5
288 #define GE_ENGINE2 6
289
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
292
293 /* Different types of move when calling RegisterMove */
294 #define CMAIL_MOVE   0
295 #define CMAIL_RESIGN 1
296 #define CMAIL_DRAW   2
297 #define CMAIL_ACCEPT 3
298
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
303
304 /* Telnet protocol constants */
305 #define TN_WILL 0373
306 #define TN_WONT 0374
307 #define TN_DO   0375
308 #define TN_DONT 0376
309 #define TN_IAC  0377
310 #define TN_ECHO 0001
311 #define TN_SGA  0003
312 #define TN_PORT 23
313
314 char*
315 safeStrCpy (char *dst, const char *src, size_t count)
316 { // [HGM] made safe
317   int i;
318   assert( dst != NULL );
319   assert( src != NULL );
320   assert( count > 0 );
321
322   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323   if(  i == count && dst[count-1] != NULLCHAR)
324     {
325       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326       if(appData.debugMode)
327       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
328     }
329
330   return dst;
331 }
332
333 /* Some compiler can't cast u64 to double
334  * This function do the job for us:
335
336  * We use the highest bit for cast, this only
337  * works if the highest bit is not
338  * in use (This should not happen)
339  *
340  * We used this for all compiler
341  */
342 double
343 u64ToDouble (u64 value)
344 {
345   double r;
346   u64 tmp = value & u64Const(0x7fffffffffffffff);
347   r = (double)(s64)tmp;
348   if (value & u64Const(0x8000000000000000))
349        r +=  9.2233720368547758080e18; /* 2^63 */
350  return r;
351 }
352
353 /* Fake up flags for now, as we aren't keeping track of castling
354    availability yet. [HGM] Change of logic: the flag now only
355    indicates the type of castlings allowed by the rule of the game.
356    The actual rights themselves are maintained in the array
357    castlingRights, as part of the game history, and are not probed
358    by this function.
359  */
360 int
361 PosFlags (index)
362 {
363   int flags = F_ALL_CASTLE_OK;
364   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365   switch (gameInfo.variant) {
366   case VariantSuicide:
367     flags &= ~F_ALL_CASTLE_OK;
368   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369     flags |= F_IGNORE_CHECK;
370   case VariantLosers:
371     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372     break;
373   case VariantAtomic:
374     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
375     break;
376   case VariantKriegspiel:
377     flags |= F_KRIEGSPIEL_CAPTURE;
378     break;
379   case VariantCapaRandom:
380   case VariantFischeRandom:
381     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382   case VariantNoCastle:
383   case VariantShatranj:
384   case VariantCourier:
385   case VariantMakruk:
386   case VariantGrand:
387     flags &= ~F_ALL_CASTLE_OK;
388     break;
389   default:
390     break;
391   }
392   return flags;
393 }
394
395 FILE *gameFileFP, *debugFP;
396
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 (char *str)
647 {
648   while(*str && !isdigit(*str)) ++str;
649   if (!*str)
650     return 0;   /* One of the special "no rating" cases */
651   else
652     return atoi(str);
653 }
654
655 void
656 ClearProgramStats ()
657 {
658     /* Init programStats */
659     programStats.movelist[0] = 0;
660     programStats.depth = 0;
661     programStats.nr_moves = 0;
662     programStats.moves_left = 0;
663     programStats.nodes = 0;
664     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
665     programStats.score = 0;
666     programStats.got_only_move = 0;
667     programStats.got_fail = 0;
668     programStats.line_is_book = 0;
669 }
670
671 void
672 CommonEngineInit ()
673 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674     if (appData.firstPlaysBlack) {
675         first.twoMachinesColor = "black\n";
676         second.twoMachinesColor = "white\n";
677     } else {
678         first.twoMachinesColor = "white\n";
679         second.twoMachinesColor = "black\n";
680     }
681
682     first.other = &second;
683     second.other = &first;
684
685     { float norm = 1;
686         if(appData.timeOddsMode) {
687             norm = appData.timeOdds[0];
688             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
689         }
690         first.timeOdds  = appData.timeOdds[0]/norm;
691         second.timeOdds = appData.timeOdds[1]/norm;
692     }
693
694     if(programVersion) free(programVersion);
695     if (appData.noChessProgram) {
696         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697         sprintf(programVersion, "%s", PACKAGE_STRING);
698     } else {
699       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
702     }
703 }
704
705 void
706 UnloadEngine (ChessProgramState *cps)
707 {
708         /* Kill off first chess program */
709         if (cps->isr != NULL)
710           RemoveInputSource(cps->isr);
711         cps->isr = NULL;
712
713         if (cps->pr != NoProc) {
714             ExitAnalyzeMode();
715             DoSleep( appData.delayBeforeQuit );
716             SendToProgram("quit\n", cps);
717             DoSleep( appData.delayAfterQuit );
718             DestroyChildProcess(cps->pr, cps->useSigterm);
719         }
720         cps->pr = NoProc;
721         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
722 }
723
724 void
725 ClearOptions (ChessProgramState *cps)
726 {
727     int i;
728     cps->nrOptions = cps->comboCnt = 0;
729     for(i=0; i<MAX_OPTIONS; i++) {
730         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731         cps->option[i].textValue = 0;
732     }
733 }
734
735 char *engineNames[] = {
736 "first",
737 "second"
738 };
739
740 void
741 InitEngine (ChessProgramState *cps, int n)
742 {   // [HGM] all engine initialiation put in a function that does one engine
743
744     ClearOptions(cps);
745
746     cps->which = engineNames[n];
747     cps->maybeThinking = FALSE;
748     cps->pr = NoProc;
749     cps->isr = NULL;
750     cps->sendTime = 2;
751     cps->sendDrawOffers = 1;
752
753     cps->program = appData.chessProgram[n];
754     cps->host = appData.host[n];
755     cps->dir = appData.directory[n];
756     cps->initString = appData.engInitString[n];
757     cps->computerString = appData.computerString[n];
758     cps->useSigint  = TRUE;
759     cps->useSigterm = TRUE;
760     cps->reuse = appData.reuse[n];
761     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
762     cps->useSetboard = FALSE;
763     cps->useSAN = FALSE;
764     cps->usePing = FALSE;
765     cps->lastPing = 0;
766     cps->lastPong = 0;
767     cps->usePlayother = FALSE;
768     cps->useColors = TRUE;
769     cps->useUsermove = FALSE;
770     cps->sendICS = FALSE;
771     cps->sendName = appData.icsActive;
772     cps->sdKludge = FALSE;
773     cps->stKludge = FALSE;
774     TidyProgramName(cps->program, cps->host, cps->tidy);
775     cps->matchWins = 0;
776     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777     cps->analysisSupport = 2; /* detect */
778     cps->analyzing = FALSE;
779     cps->initDone = FALSE;
780
781     /* New features added by Tord: */
782     cps->useFEN960 = FALSE;
783     cps->useOOCastle = TRUE;
784     /* End of new features added by Tord. */
785     cps->fenOverride  = appData.fenOverride[n];
786
787     /* [HGM] time odds: set factor for each machine */
788     cps->timeOdds  = appData.timeOdds[n];
789
790     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791     cps->accumulateTC = appData.accumulateTC[n];
792     cps->maxNrOfSessions = 1;
793
794     /* [HGM] debug */
795     cps->debug = FALSE;
796
797     cps->supportsNPS = UNKNOWN;
798     cps->memSize = FALSE;
799     cps->maxCores = FALSE;
800     cps->egtFormats[0] = NULLCHAR;
801
802     /* [HGM] options */
803     cps->optionSettings  = appData.engOptions[n];
804
805     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806     cps->isUCI = appData.isUCI[n]; /* [AS] */
807     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808
809     if (appData.protocolVersion[n] > PROTOVER
810         || appData.protocolVersion[n] < 1)
811       {
812         char buf[MSG_SIZ];
813         int len;
814
815         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816                        appData.protocolVersion[n]);
817         if( (len >= MSG_SIZ) && appData.debugMode )
818           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819
820         DisplayFatalError(buf, 0, 2);
821       }
822     else
823       {
824         cps->protocolVersion = appData.protocolVersion[n];
825       }
826
827     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
828     ParseFeatures(appData.featureDefaults, cps);
829 }
830
831 ChessProgramState *savCps;
832
833 void
834 LoadEngine ()
835 {
836     int i;
837     if(WaitForEngine(savCps, LoadEngine)) return;
838     CommonEngineInit(); // recalculate time odds
839     if(gameInfo.variant != StringToVariant(appData.variant)) {
840         // we changed variant when loading the engine; this forces us to reset
841         Reset(TRUE, savCps != &first);
842         EditGameEvent(); // for consistency with other path, as Reset changes mode
843     }
844     InitChessProgram(savCps, FALSE);
845     SendToProgram("force\n", savCps);
846     DisplayMessage("", "");
847     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849     ThawUI();
850     SetGNUMode();
851 }
852
853 void
854 ReplaceEngine (ChessProgramState *cps, int n)
855 {
856     EditGameEvent();
857     UnloadEngine(cps);
858     appData.noChessProgram = FALSE;
859     appData.clockMode = TRUE;
860     InitEngine(cps, n);
861     UpdateLogos(TRUE);
862     if(n) return; // only startup first engine immediately; second can wait
863     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864     LoadEngine();
865 }
866
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
869
870 static char resetOptions[] = 
871         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
874
875 void
876 Load (ChessProgramState *cps, int i)
877 {
878     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
879     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
880         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
883         appData.firstProtocolVersion = PROTOVER;
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
1130 NextIntegerFromString (char ** str, long * value)
1131 {
1132     int result = -1;
1133     char * s = *str;
1134
1135     while( *s == ' ' || *s == '\t' ) {
1136         s++;
1137     }
1138
1139     *value = 0;
1140
1141     if( *s >= '0' && *s <= '9' ) {
1142         while( *s >= '0' && *s <= '9' ) {
1143             *value = *value * 10 + (*s - '0');
1144             s++;
1145         }
1146
1147         result = 0;
1148     }
1149
1150     *str = s;
1151
1152     return result;
1153 }
1154
1155 int
1156 NextTimeControlFromString (char ** str, long * value)
1157 {
1158     long temp;
1159     int result = NextIntegerFromString( str, &temp );
1160
1161     if( result == 0 ) {
1162         *value = temp * 60; /* Minutes */
1163         if( **str == ':' ) {
1164             (*str)++;
1165             result = NextIntegerFromString( str, &temp );
1166             *value += temp; /* Seconds */
1167         }
1168     }
1169
1170     return result;
1171 }
1172
1173 int
1174 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1175 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1176     int result = -1, type = 0; long temp, temp2;
1177
1178     if(**str != ':') return -1; // old params remain in force!
1179     (*str)++;
1180     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1181     if( NextIntegerFromString( str, &temp ) ) return -1;
1182     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1183
1184     if(**str != '/') {
1185         /* time only: incremental or sudden-death time control */
1186         if(**str == '+') { /* increment follows; read it */
1187             (*str)++;
1188             if(**str == '!') type = *(*str)++; // Bronstein TC
1189             if(result = NextIntegerFromString( str, &temp2)) return -1;
1190             *inc = temp2 * 1000;
1191             if(**str == '.') { // read fraction of increment
1192                 char *start = ++(*str);
1193                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1194                 temp2 *= 1000;
1195                 while(start++ < *str) temp2 /= 10;
1196                 *inc += temp2;
1197             }
1198         } else *inc = 0;
1199         *moves = 0; *tc = temp * 1000; *incType = type;
1200         return 0;
1201     }
1202
1203     (*str)++; /* classical time control */
1204     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1205
1206     if(result == 0) {
1207         *moves = temp;
1208         *tc    = temp2 * 1000;
1209         *inc   = 0;
1210         *incType = type;
1211     }
1212     return result;
1213 }
1214
1215 int
1216 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1217 {   /* [HGM] get time to add from the multi-session time-control string */
1218     int incType, moves=1; /* kludge to force reading of first session */
1219     long time, increment;
1220     char *s = tcString;
1221
1222     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1223     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1224     do {
1225         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1226         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1227         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1228         if(movenr == -1) return time;    /* last move before new session     */
1229         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1230         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1231         if(!moves) return increment;     /* current session is incremental   */
1232         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1233     } while(movenr >= -1);               /* try again for next session       */
1234
1235     return 0; // no new time quota on this move
1236 }
1237
1238 int
1239 ParseTimeControl (char *tc, float ti, int mps)
1240 {
1241   long tc1;
1242   long tc2;
1243   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1244   int min, sec=0;
1245
1246   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1247   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1248       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1249   if(ti > 0) {
1250
1251     if(mps)
1252       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1253     else 
1254       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1255   } else {
1256     if(mps)
1257       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1258     else 
1259       snprintf(buf, MSG_SIZ, ":%s", mytc);
1260   }
1261   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1262   
1263   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1264     return FALSE;
1265   }
1266
1267   if( *tc == '/' ) {
1268     /* Parse second time control */
1269     tc++;
1270
1271     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1272       return FALSE;
1273     }
1274
1275     if( tc2 == 0 ) {
1276       return FALSE;
1277     }
1278
1279     timeControl_2 = tc2 * 1000;
1280   }
1281   else {
1282     timeControl_2 = 0;
1283   }
1284
1285   if( tc1 == 0 ) {
1286     return FALSE;
1287   }
1288
1289   timeControl = tc1 * 1000;
1290
1291   if (ti >= 0) {
1292     timeIncrement = ti * 1000;  /* convert to ms */
1293     movesPerSession = 0;
1294   } else {
1295     timeIncrement = 0;
1296     movesPerSession = mps;
1297   }
1298   return TRUE;
1299 }
1300
1301 void
1302 InitBackEnd2 ()
1303 {
1304     if (appData.debugMode) {
1305         fprintf(debugFP, "%s\n", programVersion);
1306     }
1307
1308     set_cont_sequence(appData.wrapContSeq);
1309     if (appData.matchGames > 0) {
1310         appData.matchMode = TRUE;
1311     } else if (appData.matchMode) {
1312         appData.matchGames = 1;
1313     }
1314     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1315         appData.matchGames = appData.sameColorGames;
1316     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1317         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1318         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1319     }
1320     Reset(TRUE, FALSE);
1321     if (appData.noChessProgram || first.protocolVersion == 1) {
1322       InitBackEnd3();
1323     } else {
1324       /* kludge: allow timeout for initial "feature" commands */
1325       FreezeUI();
1326       DisplayMessage("", _("Starting chess program"));
1327       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1328     }
1329 }
1330
1331 int
1332 CalculateIndex (int index, int gameNr)
1333 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1334     int res;
1335     if(index > 0) return index; // fixed nmber
1336     if(index == 0) return 1;
1337     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1338     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1339     return res;
1340 }
1341
1342 int
1343 LoadGameOrPosition (int gameNr)
1344 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1345     if (*appData.loadGameFile != NULLCHAR) {
1346         if (!LoadGameFromFile(appData.loadGameFile,
1347                 CalculateIndex(appData.loadGameIndex, gameNr),
1348                               appData.loadGameFile, FALSE)) {
1349             DisplayFatalError(_("Bad game file"), 0, 1);
1350             return 0;
1351         }
1352     } else if (*appData.loadPositionFile != NULLCHAR) {
1353         if (!LoadPositionFromFile(appData.loadPositionFile,
1354                 CalculateIndex(appData.loadPositionIndex, gameNr),
1355                                   appData.loadPositionFile)) {
1356             DisplayFatalError(_("Bad position file"), 0, 1);
1357             return 0;
1358         }
1359     }
1360     return 1;
1361 }
1362
1363 void
1364 ReserveGame (int gameNr, char resChar)
1365 {
1366     FILE *tf = fopen(appData.tourneyFile, "r+");
1367     char *p, *q, c, buf[MSG_SIZ];
1368     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1369     safeStrCpy(buf, lastMsg, MSG_SIZ);
1370     DisplayMessage(_("Pick new game"), "");
1371     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1372     ParseArgsFromFile(tf);
1373     p = q = appData.results;
1374     if(appData.debugMode) {
1375       char *r = appData.participants;
1376       fprintf(debugFP, "results = '%s'\n", p);
1377       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1378       fprintf(debugFP, "\n");
1379     }
1380     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1381     nextGame = q - p;
1382     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1383     safeStrCpy(q, p, strlen(p) + 2);
1384     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1385     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1386     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1387         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1388         q[nextGame] = '*';
1389     }
1390     fseek(tf, -(strlen(p)+4), SEEK_END);
1391     c = fgetc(tf);
1392     if(c != '"') // depending on DOS or Unix line endings we can be one off
1393          fseek(tf, -(strlen(p)+2), SEEK_END);
1394     else fseek(tf, -(strlen(p)+3), SEEK_END);
1395     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1396     DisplayMessage(buf, "");
1397     free(p); appData.results = q;
1398     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1399        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1400       int round = appData.defaultMatchGames * appData.tourneyType;
1401       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1402          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1403         UnloadEngine(&first);  // next game belongs to other pairing;
1404         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1405     }
1406     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr);
1407 }
1408
1409 void
1410 MatchEvent (int mode)
1411 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1412         int dummy;
1413         if(matchMode) { // already in match mode: switch it off
1414             abortMatch = TRUE;
1415             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1416             return;
1417         }
1418 //      if(gameMode != BeginningOfGame) {
1419 //          DisplayError(_("You can only start a match from the initial position."), 0);
1420 //          return;
1421 //      }
1422         abortMatch = FALSE;
1423         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1424         /* Set up machine vs. machine match */
1425         nextGame = 0;
1426         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1427         if(appData.tourneyFile[0]) {
1428             ReserveGame(-1, 0);
1429             if(nextGame > appData.matchGames) {
1430                 char buf[MSG_SIZ];
1431                 if(strchr(appData.results, '*') == NULL) {
1432                     FILE *f;
1433                     appData.tourneyCycles++;
1434                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1435                         fclose(f);
1436                         NextTourneyGame(-1, &dummy);
1437                         ReserveGame(-1, 0);
1438                         if(nextGame <= appData.matchGames) {
1439                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1440                             matchMode = mode;
1441                             ScheduleDelayedEvent(NextMatchGame, 10000);
1442                             return;
1443                         }
1444                     }
1445                 }
1446                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1447                 DisplayError(buf, 0);
1448                 appData.tourneyFile[0] = 0;
1449                 return;
1450             }
1451         } else
1452         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1453             DisplayFatalError(_("Can't have a match with no chess programs"),
1454                               0, 2);
1455             return;
1456         }
1457         matchMode = mode;
1458         matchGame = roundNr = 1;
1459         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1460         NextMatchGame();
1461 }
1462
1463 void
1464 InitBackEnd3 P((void))
1465 {
1466     GameMode initialMode;
1467     char buf[MSG_SIZ];
1468     int err, len;
1469
1470     InitChessProgram(&first, startedFromSetupPosition);
1471
1472     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1473         free(programVersion);
1474         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1475         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1476     }
1477
1478     if (appData.icsActive) {
1479 #ifdef WIN32
1480         /* [DM] Make a console window if needed [HGM] merged ifs */
1481         ConsoleCreate();
1482 #endif
1483         err = establish();
1484         if (err != 0)
1485           {
1486             if (*appData.icsCommPort != NULLCHAR)
1487               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1488                              appData.icsCommPort);
1489             else
1490               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1491                         appData.icsHost, appData.icsPort);
1492
1493             if( (len >= MSG_SIZ) && appData.debugMode )
1494               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1495
1496             DisplayFatalError(buf, err, 1);
1497             return;
1498         }
1499         SetICSMode();
1500         telnetISR =
1501           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1502         fromUserISR =
1503           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1504         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1505             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1506     } else if (appData.noChessProgram) {
1507         SetNCPMode();
1508     } else {
1509         SetGNUMode();
1510     }
1511
1512     if (*appData.cmailGameName != NULLCHAR) {
1513         SetCmailMode();
1514         OpenLoopback(&cmailPR);
1515         cmailISR =
1516           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1517     }
1518
1519     ThawUI();
1520     DisplayMessage("", "");
1521     if (StrCaseCmp(appData.initialMode, "") == 0) {
1522       initialMode = BeginningOfGame;
1523       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1524         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1525         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1526         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1527         ModeHighlight();
1528       }
1529     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1530       initialMode = TwoMachinesPlay;
1531     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1532       initialMode = AnalyzeFile;
1533     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1534       initialMode = AnalyzeMode;
1535     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1536       initialMode = MachinePlaysWhite;
1537     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1538       initialMode = MachinePlaysBlack;
1539     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1540       initialMode = EditGame;
1541     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1542       initialMode = EditPosition;
1543     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1544       initialMode = Training;
1545     } else {
1546       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1547       if( (len >= MSG_SIZ) && appData.debugMode )
1548         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1549
1550       DisplayFatalError(buf, 0, 2);
1551       return;
1552     }
1553
1554     if (appData.matchMode) {
1555         if(appData.tourneyFile[0]) { // start tourney from command line
1556             FILE *f;
1557             if(f = fopen(appData.tourneyFile, "r")) {
1558                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1559                 fclose(f);
1560                 appData.clockMode = TRUE;
1561                 SetGNUMode();
1562             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1563         }
1564         MatchEvent(TRUE);
1565     } else if (*appData.cmailGameName != NULLCHAR) {
1566         /* Set up cmail mode */
1567         ReloadCmailMsgEvent(TRUE);
1568     } else {
1569         /* Set up other modes */
1570         if (initialMode == AnalyzeFile) {
1571           if (*appData.loadGameFile == NULLCHAR) {
1572             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1573             return;
1574           }
1575         }
1576         if (*appData.loadGameFile != NULLCHAR) {
1577             (void) LoadGameFromFile(appData.loadGameFile,
1578                                     appData.loadGameIndex,
1579                                     appData.loadGameFile, TRUE);
1580         } else if (*appData.loadPositionFile != NULLCHAR) {
1581             (void) LoadPositionFromFile(appData.loadPositionFile,
1582                                         appData.loadPositionIndex,
1583                                         appData.loadPositionFile);
1584             /* [HGM] try to make self-starting even after FEN load */
1585             /* to allow automatic setup of fairy variants with wtm */
1586             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1587                 gameMode = BeginningOfGame;
1588                 setboardSpoiledMachineBlack = 1;
1589             }
1590             /* [HGM] loadPos: make that every new game uses the setup */
1591             /* from file as long as we do not switch variant          */
1592             if(!blackPlaysFirst) {
1593                 startedFromPositionFile = TRUE;
1594                 CopyBoard(filePosition, boards[0]);
1595             }
1596         }
1597         if (initialMode == AnalyzeMode) {
1598           if (appData.noChessProgram) {
1599             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1600             return;
1601           }
1602           if (appData.icsActive) {
1603             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1604             return;
1605           }
1606           AnalyzeModeEvent();
1607         } else if (initialMode == AnalyzeFile) {
1608           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1609           ShowThinkingEvent();
1610           AnalyzeFileEvent();
1611           AnalysisPeriodicEvent(1);
1612         } else if (initialMode == MachinePlaysWhite) {
1613           if (appData.noChessProgram) {
1614             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1615                               0, 2);
1616             return;
1617           }
1618           if (appData.icsActive) {
1619             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1620                               0, 2);
1621             return;
1622           }
1623           MachineWhiteEvent();
1624         } else if (initialMode == MachinePlaysBlack) {
1625           if (appData.noChessProgram) {
1626             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1627                               0, 2);
1628             return;
1629           }
1630           if (appData.icsActive) {
1631             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1632                               0, 2);
1633             return;
1634           }
1635           MachineBlackEvent();
1636         } else if (initialMode == TwoMachinesPlay) {
1637           if (appData.noChessProgram) {
1638             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1639                               0, 2);
1640             return;
1641           }
1642           if (appData.icsActive) {
1643             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1644                               0, 2);
1645             return;
1646           }
1647           TwoMachinesEvent();
1648         } else if (initialMode == EditGame) {
1649           EditGameEvent();
1650         } else if (initialMode == EditPosition) {
1651           EditPositionEvent();
1652         } else if (initialMode == Training) {
1653           if (*appData.loadGameFile == NULLCHAR) {
1654             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1655             return;
1656           }
1657           TrainingEvent();
1658         }
1659     }
1660 }
1661
1662 void
1663 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1664 {
1665     DisplayBook(current+1);
1666
1667     MoveHistorySet( movelist, first, last, current, pvInfoList );
1668
1669     EvalGraphSet( first, last, current, pvInfoList );
1670
1671     MakeEngineOutputTitle();
1672 }
1673
1674 /*
1675  * Establish will establish a contact to a remote host.port.
1676  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1677  *  used to talk to the host.
1678  * Returns 0 if okay, error code if not.
1679  */
1680 int
1681 establish ()
1682 {
1683     char buf[MSG_SIZ];
1684
1685     if (*appData.icsCommPort != NULLCHAR) {
1686         /* Talk to the host through a serial comm port */
1687         return OpenCommPort(appData.icsCommPort, &icsPR);
1688
1689     } else if (*appData.gateway != NULLCHAR) {
1690         if (*appData.remoteShell == NULLCHAR) {
1691             /* Use the rcmd protocol to run telnet program on a gateway host */
1692             snprintf(buf, sizeof(buf), "%s %s %s",
1693                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1694             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1695
1696         } else {
1697             /* Use the rsh program to run telnet program on a gateway host */
1698             if (*appData.remoteUser == NULLCHAR) {
1699                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1700                         appData.gateway, appData.telnetProgram,
1701                         appData.icsHost, appData.icsPort);
1702             } else {
1703                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1704                         appData.remoteShell, appData.gateway,
1705                         appData.remoteUser, appData.telnetProgram,
1706                         appData.icsHost, appData.icsPort);
1707             }
1708             return StartChildProcess(buf, "", &icsPR);
1709
1710         }
1711     } else if (appData.useTelnet) {
1712         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1713
1714     } else {
1715         /* TCP socket interface differs somewhat between
1716            Unix and NT; handle details in the front end.
1717            */
1718         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1719     }
1720 }
1721
1722 void
1723 EscapeExpand (char *p, char *q)
1724 {       // [HGM] initstring: routine to shape up string arguments
1725         while(*p++ = *q++) if(p[-1] == '\\')
1726             switch(*q++) {
1727                 case 'n': p[-1] = '\n'; break;
1728                 case 'r': p[-1] = '\r'; break;
1729                 case 't': p[-1] = '\t'; break;
1730                 case '\\': p[-1] = '\\'; break;
1731                 case 0: *p = 0; return;
1732                 default: p[-1] = q[-1]; break;
1733             }
1734 }
1735
1736 void
1737 show_bytes (FILE *fp, char *buf, int count)
1738 {
1739     while (count--) {
1740         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1741             fprintf(fp, "\\%03o", *buf & 0xff);
1742         } else {
1743             putc(*buf, fp);
1744         }
1745         buf++;
1746     }
1747     fflush(fp);
1748 }
1749
1750 /* Returns an errno value */
1751 int
1752 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1753 {
1754     char buf[8192], *p, *q, *buflim;
1755     int left, newcount, outcount;
1756
1757     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1758         *appData.gateway != NULLCHAR) {
1759         if (appData.debugMode) {
1760             fprintf(debugFP, ">ICS: ");
1761             show_bytes(debugFP, message, count);
1762             fprintf(debugFP, "\n");
1763         }
1764         return OutputToProcess(pr, message, count, outError);
1765     }
1766
1767     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1768     p = message;
1769     q = buf;
1770     left = count;
1771     newcount = 0;
1772     while (left) {
1773         if (q >= buflim) {
1774             if (appData.debugMode) {
1775                 fprintf(debugFP, ">ICS: ");
1776                 show_bytes(debugFP, buf, newcount);
1777                 fprintf(debugFP, "\n");
1778             }
1779             outcount = OutputToProcess(pr, buf, newcount, outError);
1780             if (outcount < newcount) return -1; /* to be sure */
1781             q = buf;
1782             newcount = 0;
1783         }
1784         if (*p == '\n') {
1785             *q++ = '\r';
1786             newcount++;
1787         } else if (((unsigned char) *p) == TN_IAC) {
1788             *q++ = (char) TN_IAC;
1789             newcount ++;
1790         }
1791         *q++ = *p++;
1792         newcount++;
1793         left--;
1794     }
1795     if (appData.debugMode) {
1796         fprintf(debugFP, ">ICS: ");
1797         show_bytes(debugFP, buf, newcount);
1798         fprintf(debugFP, "\n");
1799     }
1800     outcount = OutputToProcess(pr, buf, newcount, outError);
1801     if (outcount < newcount) return -1; /* to be sure */
1802     return count;
1803 }
1804
1805 void
1806 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1807 {
1808     int outError, outCount;
1809     static int gotEof = 0;
1810
1811     /* Pass data read from player on to ICS */
1812     if (count > 0) {
1813         gotEof = 0;
1814         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1815         if (outCount < count) {
1816             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1817         }
1818     } else if (count < 0) {
1819         RemoveInputSource(isr);
1820         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1821     } else if (gotEof++ > 0) {
1822         RemoveInputSource(isr);
1823         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1824     }
1825 }
1826
1827 void
1828 KeepAlive ()
1829 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1830     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1831     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1832     SendToICS("date\n");
1833     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1834 }
1835
1836 /* added routine for printf style output to ics */
1837 void
1838 ics_printf (char *format, ...)
1839 {
1840     char buffer[MSG_SIZ];
1841     va_list args;
1842
1843     va_start(args, format);
1844     vsnprintf(buffer, sizeof(buffer), format, args);
1845     buffer[sizeof(buffer)-1] = '\0';
1846     SendToICS(buffer);
1847     va_end(args);
1848 }
1849
1850 void
1851 SendToICS (char *s)
1852 {
1853     int count, outCount, outError;
1854
1855     if (icsPR == NoProc) return;
1856
1857     count = strlen(s);
1858     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1859     if (outCount < count) {
1860         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1861     }
1862 }
1863
1864 /* This is used for sending logon scripts to the ICS. Sending
1865    without a delay causes problems when using timestamp on ICC
1866    (at least on my machine). */
1867 void
1868 SendToICSDelayed (char *s, long msdelay)
1869 {
1870     int count, outCount, outError;
1871
1872     if (icsPR == NoProc) return;
1873
1874     count = strlen(s);
1875     if (appData.debugMode) {
1876         fprintf(debugFP, ">ICS: ");
1877         show_bytes(debugFP, s, count);
1878         fprintf(debugFP, "\n");
1879     }
1880     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1881                                       msdelay);
1882     if (outCount < count) {
1883         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884     }
1885 }
1886
1887
1888 /* Remove all highlighting escape sequences in s
1889    Also deletes any suffix starting with '('
1890    */
1891 char *
1892 StripHighlightAndTitle (char *s)
1893 {
1894     static char retbuf[MSG_SIZ];
1895     char *p = retbuf;
1896
1897     while (*s != NULLCHAR) {
1898         while (*s == '\033') {
1899             while (*s != NULLCHAR && !isalpha(*s)) s++;
1900             if (*s != NULLCHAR) s++;
1901         }
1902         while (*s != NULLCHAR && *s != '\033') {
1903             if (*s == '(' || *s == '[') {
1904                 *p = NULLCHAR;
1905                 return retbuf;
1906             }
1907             *p++ = *s++;
1908         }
1909     }
1910     *p = NULLCHAR;
1911     return retbuf;
1912 }
1913
1914 /* Remove all highlighting escape sequences in s */
1915 char *
1916 StripHighlight (char *s)
1917 {
1918     static char retbuf[MSG_SIZ];
1919     char *p = retbuf;
1920
1921     while (*s != NULLCHAR) {
1922         while (*s == '\033') {
1923             while (*s != NULLCHAR && !isalpha(*s)) s++;
1924             if (*s != NULLCHAR) s++;
1925         }
1926         while (*s != NULLCHAR && *s != '\033') {
1927             *p++ = *s++;
1928         }
1929     }
1930     *p = NULLCHAR;
1931     return retbuf;
1932 }
1933
1934 char *variantNames[] = VARIANT_NAMES;
1935 char *
1936 VariantName (VariantClass v)
1937 {
1938     return variantNames[v];
1939 }
1940
1941
1942 /* Identify a variant from the strings the chess servers use or the
1943    PGN Variant tag names we use. */
1944 VariantClass
1945 StringToVariant (char *e)
1946 {
1947     char *p;
1948     int wnum = -1;
1949     VariantClass v = VariantNormal;
1950     int i, found = FALSE;
1951     char buf[MSG_SIZ];
1952     int len;
1953
1954     if (!e) return v;
1955
1956     /* [HGM] skip over optional board-size prefixes */
1957     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1958         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1959         while( *e++ != '_');
1960     }
1961
1962     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1963         v = VariantNormal;
1964         found = TRUE;
1965     } else
1966     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1967       if (StrCaseStr(e, variantNames[i])) {
1968         v = (VariantClass) i;
1969         found = TRUE;
1970         break;
1971       }
1972     }
1973
1974     if (!found) {
1975       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1976           || StrCaseStr(e, "wild/fr")
1977           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1978         v = VariantFischeRandom;
1979       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1980                  (i = 1, p = StrCaseStr(e, "w"))) {
1981         p += i;
1982         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1983         if (isdigit(*p)) {
1984           wnum = atoi(p);
1985         } else {
1986           wnum = -1;
1987         }
1988         switch (wnum) {
1989         case 0: /* FICS only, actually */
1990         case 1:
1991           /* Castling legal even if K starts on d-file */
1992           v = VariantWildCastle;
1993           break;
1994         case 2:
1995         case 3:
1996         case 4:
1997           /* Castling illegal even if K & R happen to start in
1998              normal positions. */
1999           v = VariantNoCastle;
2000           break;
2001         case 5:
2002         case 7:
2003         case 8:
2004         case 10:
2005         case 11:
2006         case 12:
2007         case 13:
2008         case 14:
2009         case 15:
2010         case 18:
2011         case 19:
2012           /* Castling legal iff K & R start in normal positions */
2013           v = VariantNormal;
2014           break;
2015         case 6:
2016         case 20:
2017         case 21:
2018           /* Special wilds for position setup; unclear what to do here */
2019           v = VariantLoadable;
2020           break;
2021         case 9:
2022           /* Bizarre ICC game */
2023           v = VariantTwoKings;
2024           break;
2025         case 16:
2026           v = VariantKriegspiel;
2027           break;
2028         case 17:
2029           v = VariantLosers;
2030           break;
2031         case 22:
2032           v = VariantFischeRandom;
2033           break;
2034         case 23:
2035           v = VariantCrazyhouse;
2036           break;
2037         case 24:
2038           v = VariantBughouse;
2039           break;
2040         case 25:
2041           v = Variant3Check;
2042           break;
2043         case 26:
2044           /* Not quite the same as FICS suicide! */
2045           v = VariantGiveaway;
2046           break;
2047         case 27:
2048           v = VariantAtomic;
2049           break;
2050         case 28:
2051           v = VariantShatranj;
2052           break;
2053
2054         /* Temporary names for future ICC types.  The name *will* change in
2055            the next xboard/WinBoard release after ICC defines it. */
2056         case 29:
2057           v = Variant29;
2058           break;
2059         case 30:
2060           v = Variant30;
2061           break;
2062         case 31:
2063           v = Variant31;
2064           break;
2065         case 32:
2066           v = Variant32;
2067           break;
2068         case 33:
2069           v = Variant33;
2070           break;
2071         case 34:
2072           v = Variant34;
2073           break;
2074         case 35:
2075           v = Variant35;
2076           break;
2077         case 36:
2078           v = Variant36;
2079           break;
2080         case 37:
2081           v = VariantShogi;
2082           break;
2083         case 38:
2084           v = VariantXiangqi;
2085           break;
2086         case 39:
2087           v = VariantCourier;
2088           break;
2089         case 40:
2090           v = VariantGothic;
2091           break;
2092         case 41:
2093           v = VariantCapablanca;
2094           break;
2095         case 42:
2096           v = VariantKnightmate;
2097           break;
2098         case 43:
2099           v = VariantFairy;
2100           break;
2101         case 44:
2102           v = VariantCylinder;
2103           break;
2104         case 45:
2105           v = VariantFalcon;
2106           break;
2107         case 46:
2108           v = VariantCapaRandom;
2109           break;
2110         case 47:
2111           v = VariantBerolina;
2112           break;
2113         case 48:
2114           v = VariantJanus;
2115           break;
2116         case 49:
2117           v = VariantSuper;
2118           break;
2119         case 50:
2120           v = VariantGreat;
2121           break;
2122         case -1:
2123           /* Found "wild" or "w" in the string but no number;
2124              must assume it's normal chess. */
2125           v = VariantNormal;
2126           break;
2127         default:
2128           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2129           if( (len >= MSG_SIZ) && appData.debugMode )
2130             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2131
2132           DisplayError(buf, 0);
2133           v = VariantUnknown;
2134           break;
2135         }
2136       }
2137     }
2138     if (appData.debugMode) {
2139       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2140               e, wnum, VariantName(v));
2141     }
2142     return v;
2143 }
2144
2145 static int leftover_start = 0, leftover_len = 0;
2146 char star_match[STAR_MATCH_N][MSG_SIZ];
2147
2148 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2149    advance *index beyond it, and set leftover_start to the new value of
2150    *index; else return FALSE.  If pattern contains the character '*', it
2151    matches any sequence of characters not containing '\r', '\n', or the
2152    character following the '*' (if any), and the matched sequence(s) are
2153    copied into star_match.
2154    */
2155 int
2156 looking_at ( char *buf, int *index, char *pattern)
2157 {
2158     char *bufp = &buf[*index], *patternp = pattern;
2159     int star_count = 0;
2160     char *matchp = star_match[0];
2161
2162     for (;;) {
2163         if (*patternp == NULLCHAR) {
2164             *index = leftover_start = bufp - buf;
2165             *matchp = NULLCHAR;
2166             return TRUE;
2167         }
2168         if (*bufp == NULLCHAR) return FALSE;
2169         if (*patternp == '*') {
2170             if (*bufp == *(patternp + 1)) {
2171                 *matchp = NULLCHAR;
2172                 matchp = star_match[++star_count];
2173                 patternp += 2;
2174                 bufp++;
2175                 continue;
2176             } else if (*bufp == '\n' || *bufp == '\r') {
2177                 patternp++;
2178                 if (*patternp == NULLCHAR)
2179                   continue;
2180                 else
2181                   return FALSE;
2182             } else {
2183                 *matchp++ = *bufp++;
2184                 continue;
2185             }
2186         }
2187         if (*patternp != *bufp) return FALSE;
2188         patternp++;
2189         bufp++;
2190     }
2191 }
2192
2193 void
2194 SendToPlayer (char *data, int length)
2195 {
2196     int error, outCount;
2197     outCount = OutputToProcess(NoProc, data, length, &error);
2198     if (outCount < length) {
2199         DisplayFatalError(_("Error writing to display"), error, 1);
2200     }
2201 }
2202
2203 void
2204 PackHolding (char packed[], char *holding)
2205 {
2206     char *p = holding;
2207     char *q = packed;
2208     int runlength = 0;
2209     int curr = 9999;
2210     do {
2211         if (*p == curr) {
2212             runlength++;
2213         } else {
2214             switch (runlength) {
2215               case 0:
2216                 break;
2217               case 1:
2218                 *q++ = curr;
2219                 break;
2220               case 2:
2221                 *q++ = curr;
2222                 *q++ = curr;
2223                 break;
2224               default:
2225                 sprintf(q, "%d", runlength);
2226                 while (*q) q++;
2227                 *q++ = curr;
2228                 break;
2229             }
2230             runlength = 1;
2231             curr = *p;
2232         }
2233     } while (*p++);
2234     *q = NULLCHAR;
2235 }
2236
2237 /* Telnet protocol requests from the front end */
2238 void
2239 TelnetRequest (unsigned char ddww, unsigned char option)
2240 {
2241     unsigned char msg[3];
2242     int outCount, outError;
2243
2244     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2245
2246     if (appData.debugMode) {
2247         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2248         switch (ddww) {
2249           case TN_DO:
2250             ddwwStr = "DO";
2251             break;
2252           case TN_DONT:
2253             ddwwStr = "DONT";
2254             break;
2255           case TN_WILL:
2256             ddwwStr = "WILL";
2257             break;
2258           case TN_WONT:
2259             ddwwStr = "WONT";
2260             break;
2261           default:
2262             ddwwStr = buf1;
2263             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2264             break;
2265         }
2266         switch (option) {
2267           case TN_ECHO:
2268             optionStr = "ECHO";
2269             break;
2270           default:
2271             optionStr = buf2;
2272             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2273             break;
2274         }
2275         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2276     }
2277     msg[0] = TN_IAC;
2278     msg[1] = ddww;
2279     msg[2] = option;
2280     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2281     if (outCount < 3) {
2282         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2283     }
2284 }
2285
2286 void
2287 DoEcho ()
2288 {
2289     if (!appData.icsActive) return;
2290     TelnetRequest(TN_DO, TN_ECHO);
2291 }
2292
2293 void
2294 DontEcho ()
2295 {
2296     if (!appData.icsActive) return;
2297     TelnetRequest(TN_DONT, TN_ECHO);
2298 }
2299
2300 void
2301 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2302 {
2303     /* put the holdings sent to us by the server on the board holdings area */
2304     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2305     char p;
2306     ChessSquare piece;
2307
2308     if(gameInfo.holdingsWidth < 2)  return;
2309     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2310         return; // prevent overwriting by pre-board holdings
2311
2312     if( (int)lowestPiece >= BlackPawn ) {
2313         holdingsColumn = 0;
2314         countsColumn = 1;
2315         holdingsStartRow = BOARD_HEIGHT-1;
2316         direction = -1;
2317     } else {
2318         holdingsColumn = BOARD_WIDTH-1;
2319         countsColumn = BOARD_WIDTH-2;
2320         holdingsStartRow = 0;
2321         direction = 1;
2322     }
2323
2324     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2325         board[i][holdingsColumn] = EmptySquare;
2326         board[i][countsColumn]   = (ChessSquare) 0;
2327     }
2328     while( (p=*holdings++) != NULLCHAR ) {
2329         piece = CharToPiece( ToUpper(p) );
2330         if(piece == EmptySquare) continue;
2331         /*j = (int) piece - (int) WhitePawn;*/
2332         j = PieceToNumber(piece);
2333         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2334         if(j < 0) continue;               /* should not happen */
2335         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2336         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2337         board[holdingsStartRow+j*direction][countsColumn]++;
2338     }
2339 }
2340
2341
2342 void
2343 VariantSwitch (Board board, VariantClass newVariant)
2344 {
2345    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2346    static Board oldBoard;
2347
2348    startedFromPositionFile = FALSE;
2349    if(gameInfo.variant == newVariant) return;
2350
2351    /* [HGM] This routine is called each time an assignment is made to
2352     * gameInfo.variant during a game, to make sure the board sizes
2353     * are set to match the new variant. If that means adding or deleting
2354     * holdings, we shift the playing board accordingly
2355     * This kludge is needed because in ICS observe mode, we get boards
2356     * of an ongoing game without knowing the variant, and learn about the
2357     * latter only later. This can be because of the move list we requested,
2358     * in which case the game history is refilled from the beginning anyway,
2359     * but also when receiving holdings of a crazyhouse game. In the latter
2360     * case we want to add those holdings to the already received position.
2361     */
2362
2363
2364    if (appData.debugMode) {
2365      fprintf(debugFP, "Switch board from %s to %s\n",
2366              VariantName(gameInfo.variant), VariantName(newVariant));
2367      setbuf(debugFP, NULL);
2368    }
2369    shuffleOpenings = 0;       /* [HGM] shuffle */
2370    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2371    switch(newVariant)
2372      {
2373      case VariantShogi:
2374        newWidth = 9;  newHeight = 9;
2375        gameInfo.holdingsSize = 7;
2376      case VariantBughouse:
2377      case VariantCrazyhouse:
2378        newHoldingsWidth = 2; break;
2379      case VariantGreat:
2380        newWidth = 10;
2381      case VariantSuper:
2382        newHoldingsWidth = 2;
2383        gameInfo.holdingsSize = 8;
2384        break;
2385      case VariantGothic:
2386      case VariantCapablanca:
2387      case VariantCapaRandom:
2388        newWidth = 10;
2389      default:
2390        newHoldingsWidth = gameInfo.holdingsSize = 0;
2391      };
2392
2393    if(newWidth  != gameInfo.boardWidth  ||
2394       newHeight != gameInfo.boardHeight ||
2395       newHoldingsWidth != gameInfo.holdingsWidth ) {
2396
2397      /* shift position to new playing area, if needed */
2398      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2399        for(i=0; i<BOARD_HEIGHT; i++)
2400          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2401            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2402              board[i][j];
2403        for(i=0; i<newHeight; i++) {
2404          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2405          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2406        }
2407      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2408        for(i=0; i<BOARD_HEIGHT; i++)
2409          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2410            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2411              board[i][j];
2412      }
2413      gameInfo.boardWidth  = newWidth;
2414      gameInfo.boardHeight = newHeight;
2415      gameInfo.holdingsWidth = newHoldingsWidth;
2416      gameInfo.variant = newVariant;
2417      InitDrawingSizes(-2, 0);
2418    } else gameInfo.variant = newVariant;
2419    CopyBoard(oldBoard, board);   // remember correctly formatted board
2420      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2421    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2422 }
2423
2424 static int loggedOn = FALSE;
2425
2426 /*-- Game start info cache: --*/
2427 int gs_gamenum;
2428 char gs_kind[MSG_SIZ];
2429 static char player1Name[128] = "";
2430 static char player2Name[128] = "";
2431 static char cont_seq[] = "\n\\   ";
2432 static int player1Rating = -1;
2433 static int player2Rating = -1;
2434 /*----------------------------*/
2435
2436 ColorClass curColor = ColorNormal;
2437 int suppressKibitz = 0;
2438
2439 // [HGM] seekgraph
2440 Boolean soughtPending = FALSE;
2441 Boolean seekGraphUp;
2442 #define MAX_SEEK_ADS 200
2443 #define SQUARE 0x80
2444 char *seekAdList[MAX_SEEK_ADS];
2445 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2446 float tcList[MAX_SEEK_ADS];
2447 char colorList[MAX_SEEK_ADS];
2448 int nrOfSeekAds = 0;
2449 int minRating = 1010, maxRating = 2800;
2450 int hMargin = 10, vMargin = 20, h, w;
2451 extern int squareSize, lineGap;
2452
2453 void
2454 PlotSeekAd (int i)
2455 {
2456         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2457         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2458         if(r < minRating+100 && r >=0 ) r = minRating+100;
2459         if(r > maxRating) r = maxRating;
2460         if(tc < 1.) tc = 1.;
2461         if(tc > 95.) tc = 95.;
2462         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2463         y = ((double)r - minRating)/(maxRating - minRating)
2464             * (h-vMargin-squareSize/8-1) + vMargin;
2465         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2466         if(strstr(seekAdList[i], " u ")) color = 1;
2467         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2468            !strstr(seekAdList[i], "bullet") &&
2469            !strstr(seekAdList[i], "blitz") &&
2470            !strstr(seekAdList[i], "standard") ) color = 2;
2471         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2472         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2473 }
2474
2475 void
2476 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2477 {
2478         char buf[MSG_SIZ], *ext = "";
2479         VariantClass v = StringToVariant(type);
2480         if(strstr(type, "wild")) {
2481             ext = type + 4; // append wild number
2482             if(v == VariantFischeRandom) type = "chess960"; else
2483             if(v == VariantLoadable) type = "setup"; else
2484             type = VariantName(v);
2485         }
2486         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2487         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2488             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2489             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2490             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2491             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2492             seekNrList[nrOfSeekAds] = nr;
2493             zList[nrOfSeekAds] = 0;
2494             seekAdList[nrOfSeekAds++] = StrSave(buf);
2495             if(plot) PlotSeekAd(nrOfSeekAds-1);
2496         }
2497 }
2498
2499 void
2500 EraseSeekDot (int i)
2501 {
2502     int x = xList[i], y = yList[i], d=squareSize/4, k;
2503     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2504     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2505     // now replot every dot that overlapped
2506     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2507         int xx = xList[k], yy = yList[k];
2508         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2509             DrawSeekDot(xx, yy, colorList[k]);
2510     }
2511 }
2512
2513 void
2514 RemoveSeekAd (int nr)
2515 {
2516         int i;
2517         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2518             EraseSeekDot(i);
2519             if(seekAdList[i]) free(seekAdList[i]);
2520             seekAdList[i] = seekAdList[--nrOfSeekAds];
2521             seekNrList[i] = seekNrList[nrOfSeekAds];
2522             ratingList[i] = ratingList[nrOfSeekAds];
2523             colorList[i]  = colorList[nrOfSeekAds];
2524             tcList[i] = tcList[nrOfSeekAds];
2525             xList[i]  = xList[nrOfSeekAds];
2526             yList[i]  = yList[nrOfSeekAds];
2527             zList[i]  = zList[nrOfSeekAds];
2528             seekAdList[nrOfSeekAds] = NULL;
2529             break;
2530         }
2531 }
2532
2533 Boolean
2534 MatchSoughtLine (char *line)
2535 {
2536     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2537     int nr, base, inc, u=0; char dummy;
2538
2539     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2540        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2541        (u=1) &&
2542        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2543         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2544         // match: compact and save the line
2545         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2546         return TRUE;
2547     }
2548     return FALSE;
2549 }
2550
2551 int
2552 DrawSeekGraph ()
2553 {
2554     int i;
2555     if(!seekGraphUp) return FALSE;
2556     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2557     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2558
2559     DrawSeekBackground(0, 0, w, h);
2560     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2561     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2562     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2563         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2564         yy = h-1-yy;
2565         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2566         if(i%500 == 0) {
2567             char buf[MSG_SIZ];
2568             snprintf(buf, MSG_SIZ, "%d", i);
2569             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2570         }
2571     }
2572     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2573     for(i=1; i<100; i+=(i<10?1:5)) {
2574         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2575         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2576         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2577             char buf[MSG_SIZ];
2578             snprintf(buf, MSG_SIZ, "%d", i);
2579             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2580         }
2581     }
2582     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2583     return TRUE;
2584 }
2585
2586 int
2587 SeekGraphClick (ClickType click, int x, int y, int moving)
2588 {
2589     static int lastDown = 0, displayed = 0, lastSecond;
2590     if(y < 0) return FALSE;
2591     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2592         if(click == Release || moving) return FALSE;
2593         nrOfSeekAds = 0;
2594         soughtPending = TRUE;
2595         SendToICS(ics_prefix);
2596         SendToICS("sought\n"); // should this be "sought all"?
2597     } else { // issue challenge based on clicked ad
2598         int dist = 10000; int i, closest = 0, second = 0;
2599         for(i=0; i<nrOfSeekAds; i++) {
2600             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2601             if(d < dist) { dist = d; closest = i; }
2602             second += (d - zList[i] < 120); // count in-range ads
2603             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2604         }
2605         if(dist < 120) {
2606             char buf[MSG_SIZ];
2607             second = (second > 1);
2608             if(displayed != closest || second != lastSecond) {
2609                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2610                 lastSecond = second; displayed = closest;
2611             }
2612             if(click == Press) {
2613                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2614                 lastDown = closest;
2615                 return TRUE;
2616             } // on press 'hit', only show info
2617             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2618             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2619             SendToICS(ics_prefix);
2620             SendToICS(buf);
2621             return TRUE; // let incoming board of started game pop down the graph
2622         } else if(click == Release) { // release 'miss' is ignored
2623             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2624             if(moving == 2) { // right up-click
2625                 nrOfSeekAds = 0; // refresh graph
2626                 soughtPending = TRUE;
2627                 SendToICS(ics_prefix);
2628                 SendToICS("sought\n"); // should this be "sought all"?
2629             }
2630             return TRUE;
2631         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2632         // press miss or release hit 'pop down' seek graph
2633         seekGraphUp = FALSE;
2634         DrawPosition(TRUE, NULL);
2635     }
2636     return TRUE;
2637 }
2638
2639 void
2640 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2641 {
2642 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2643 #define STARTED_NONE 0
2644 #define STARTED_MOVES 1
2645 #define STARTED_BOARD 2
2646 #define STARTED_OBSERVE 3
2647 #define STARTED_HOLDINGS 4
2648 #define STARTED_CHATTER 5
2649 #define STARTED_COMMENT 6
2650 #define STARTED_MOVES_NOHIDE 7
2651
2652     static int started = STARTED_NONE;
2653     static char parse[20000];
2654     static int parse_pos = 0;
2655     static char buf[BUF_SIZE + 1];
2656     static int firstTime = TRUE, intfSet = FALSE;
2657     static ColorClass prevColor = ColorNormal;
2658     static int savingComment = FALSE;
2659     static int cmatch = 0; // continuation sequence match
2660     char *bp;
2661     char str[MSG_SIZ];
2662     int i, oldi;
2663     int buf_len;
2664     int next_out;
2665     int tkind;
2666     int backup;    /* [DM] For zippy color lines */
2667     char *p;
2668     char talker[MSG_SIZ]; // [HGM] chat
2669     int channel;
2670
2671     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2672
2673     if (appData.debugMode) {
2674       if (!error) {
2675         fprintf(debugFP, "<ICS: ");
2676         show_bytes(debugFP, data, count);
2677         fprintf(debugFP, "\n");
2678       }
2679     }
2680
2681     if (appData.debugMode) { int f = forwardMostMove;
2682         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2683                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2684                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2685     }
2686     if (count > 0) {
2687         /* If last read ended with a partial line that we couldn't parse,
2688            prepend it to the new read and try again. */
2689         if (leftover_len > 0) {
2690             for (i=0; i<leftover_len; i++)
2691               buf[i] = buf[leftover_start + i];
2692         }
2693
2694     /* copy new characters into the buffer */
2695     bp = buf + leftover_len;
2696     buf_len=leftover_len;
2697     for (i=0; i<count; i++)
2698     {
2699         // ignore these
2700         if (data[i] == '\r')
2701             continue;
2702
2703         // join lines split by ICS?
2704         if (!appData.noJoin)
2705         {
2706             /*
2707                 Joining just consists of finding matches against the
2708                 continuation sequence, and discarding that sequence
2709                 if found instead of copying it.  So, until a match
2710                 fails, there's nothing to do since it might be the
2711                 complete sequence, and thus, something we don't want
2712                 copied.
2713             */
2714             if (data[i] == cont_seq[cmatch])
2715             {
2716                 cmatch++;
2717                 if (cmatch == strlen(cont_seq))
2718                 {
2719                     cmatch = 0; // complete match.  just reset the counter
2720
2721                     /*
2722                         it's possible for the ICS to not include the space
2723                         at the end of the last word, making our [correct]
2724                         join operation fuse two separate words.  the server
2725                         does this when the space occurs at the width setting.
2726                     */
2727                     if (!buf_len || buf[buf_len-1] != ' ')
2728                     {
2729                         *bp++ = ' ';
2730                         buf_len++;
2731                     }
2732                 }
2733                 continue;
2734             }
2735             else if (cmatch)
2736             {
2737                 /*
2738                     match failed, so we have to copy what matched before
2739                     falling through and copying this character.  In reality,
2740                     this will only ever be just the newline character, but
2741                     it doesn't hurt to be precise.
2742                 */
2743                 strncpy(bp, cont_seq, cmatch);
2744                 bp += cmatch;
2745                 buf_len += cmatch;
2746                 cmatch = 0;
2747             }
2748         }
2749
2750         // copy this char
2751         *bp++ = data[i];
2752         buf_len++;
2753     }
2754
2755         buf[buf_len] = NULLCHAR;
2756 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2757         next_out = 0;
2758         leftover_start = 0;
2759
2760         i = 0;
2761         while (i < buf_len) {
2762             /* Deal with part of the TELNET option negotiation
2763                protocol.  We refuse to do anything beyond the
2764                defaults, except that we allow the WILL ECHO option,
2765                which ICS uses to turn off password echoing when we are
2766                directly connected to it.  We reject this option
2767                if localLineEditing mode is on (always on in xboard)
2768                and we are talking to port 23, which might be a real
2769                telnet server that will try to keep WILL ECHO on permanently.
2770              */
2771             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2772                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2773                 unsigned char option;
2774                 oldi = i;
2775                 switch ((unsigned char) buf[++i]) {
2776                   case TN_WILL:
2777                     if (appData.debugMode)
2778                       fprintf(debugFP, "\n<WILL ");
2779                     switch (option = (unsigned char) buf[++i]) {
2780                       case TN_ECHO:
2781                         if (appData.debugMode)
2782                           fprintf(debugFP, "ECHO ");
2783                         /* Reply only if this is a change, according
2784                            to the protocol rules. */
2785                         if (remoteEchoOption) break;
2786                         if (appData.localLineEditing &&
2787                             atoi(appData.icsPort) == TN_PORT) {
2788                             TelnetRequest(TN_DONT, TN_ECHO);
2789                         } else {
2790                             EchoOff();
2791                             TelnetRequest(TN_DO, TN_ECHO);
2792                             remoteEchoOption = TRUE;
2793                         }
2794                         break;
2795                       default:
2796                         if (appData.debugMode)
2797                           fprintf(debugFP, "%d ", option);
2798                         /* Whatever this is, we don't want it. */
2799                         TelnetRequest(TN_DONT, option);
2800                         break;
2801                     }
2802                     break;
2803                   case TN_WONT:
2804                     if (appData.debugMode)
2805                       fprintf(debugFP, "\n<WONT ");
2806                     switch (option = (unsigned char) buf[++i]) {
2807                       case TN_ECHO:
2808                         if (appData.debugMode)
2809                           fprintf(debugFP, "ECHO ");
2810                         /* Reply only if this is a change, according
2811                            to the protocol rules. */
2812                         if (!remoteEchoOption) break;
2813                         EchoOn();
2814                         TelnetRequest(TN_DONT, TN_ECHO);
2815                         remoteEchoOption = FALSE;
2816                         break;
2817                       default:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "%d ", (unsigned char) option);
2820                         /* Whatever this is, it must already be turned
2821                            off, because we never agree to turn on
2822                            anything non-default, so according to the
2823                            protocol rules, we don't reply. */
2824                         break;
2825                     }
2826                     break;
2827                   case TN_DO:
2828                     if (appData.debugMode)
2829                       fprintf(debugFP, "\n<DO ");
2830                     switch (option = (unsigned char) buf[++i]) {
2831                       default:
2832                         /* Whatever this is, we refuse to do it. */
2833                         if (appData.debugMode)
2834                           fprintf(debugFP, "%d ", option);
2835                         TelnetRequest(TN_WONT, option);
2836                         break;
2837                     }
2838                     break;
2839                   case TN_DONT:
2840                     if (appData.debugMode)
2841                       fprintf(debugFP, "\n<DONT ");
2842                     switch (option = (unsigned char) buf[++i]) {
2843                       default:
2844                         if (appData.debugMode)
2845                           fprintf(debugFP, "%d ", option);
2846                         /* Whatever this is, we are already not doing
2847                            it, because we never agree to do anything
2848                            non-default, so according to the protocol
2849                            rules, we don't reply. */
2850                         break;
2851                     }
2852                     break;
2853                   case TN_IAC:
2854                     if (appData.debugMode)
2855                       fprintf(debugFP, "\n<IAC ");
2856                     /* Doubled IAC; pass it through */
2857                     i--;
2858                     break;
2859                   default:
2860                     if (appData.debugMode)
2861                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2862                     /* Drop all other telnet commands on the floor */
2863                     break;
2864                 }
2865                 if (oldi > next_out)
2866                   SendToPlayer(&buf[next_out], oldi - next_out);
2867                 if (++i > next_out)
2868                   next_out = i;
2869                 continue;
2870             }
2871
2872             /* OK, this at least will *usually* work */
2873             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2874                 loggedOn = TRUE;
2875             }
2876
2877             if (loggedOn && !intfSet) {
2878                 if (ics_type == ICS_ICC) {
2879                   snprintf(str, MSG_SIZ,
2880                           "/set-quietly interface %s\n/set-quietly style 12\n",
2881                           programVersion);
2882                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2883                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2884                 } else if (ics_type == ICS_CHESSNET) {
2885                   snprintf(str, MSG_SIZ, "/style 12\n");
2886                 } else {
2887                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2888                   strcat(str, programVersion);
2889                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2890                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2891                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2892 #ifdef WIN32
2893                   strcat(str, "$iset nohighlight 1\n");
2894 #endif
2895                   strcat(str, "$iset lock 1\n$style 12\n");
2896                 }
2897                 SendToICS(str);
2898                 NotifyFrontendLogin();
2899                 intfSet = TRUE;
2900             }
2901
2902             if (started == STARTED_COMMENT) {
2903                 /* Accumulate characters in comment */
2904                 parse[parse_pos++] = buf[i];
2905                 if (buf[i] == '\n') {
2906                     parse[parse_pos] = NULLCHAR;
2907                     if(chattingPartner>=0) {
2908                         char mess[MSG_SIZ];
2909                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2910                         OutputChatMessage(chattingPartner, mess);
2911                         chattingPartner = -1;
2912                         next_out = i+1; // [HGM] suppress printing in ICS window
2913                     } else
2914                     if(!suppressKibitz) // [HGM] kibitz
2915                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2916                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2917                         int nrDigit = 0, nrAlph = 0, j;
2918                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2919                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2920                         parse[parse_pos] = NULLCHAR;
2921                         // try to be smart: if it does not look like search info, it should go to
2922                         // ICS interaction window after all, not to engine-output window.
2923                         for(j=0; j<parse_pos; j++) { // count letters and digits
2924                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2925                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2926                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2927                         }
2928                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2929                             int depth=0; float score;
2930                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2931                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2932                                 pvInfoList[forwardMostMove-1].depth = depth;
2933                                 pvInfoList[forwardMostMove-1].score = 100*score;
2934                             }
2935                             OutputKibitz(suppressKibitz, parse);
2936                         } else {
2937                             char tmp[MSG_SIZ];
2938                             if(gameMode == IcsObserving) // restore original ICS messages
2939                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2940                             else
2941                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2942                             SendToPlayer(tmp, strlen(tmp));
2943                         }
2944                         next_out = i+1; // [HGM] suppress printing in ICS window
2945                     }
2946                     started = STARTED_NONE;
2947                 } else {
2948                     /* Don't match patterns against characters in comment */
2949                     i++;
2950                     continue;
2951                 }
2952             }
2953             if (started == STARTED_CHATTER) {
2954                 if (buf[i] != '\n') {
2955                     /* Don't match patterns against characters in chatter */
2956                     i++;
2957                     continue;
2958                 }
2959                 started = STARTED_NONE;
2960                 if(suppressKibitz) next_out = i+1;
2961             }
2962
2963             /* Kludge to deal with rcmd protocol */
2964             if (firstTime && looking_at(buf, &i, "\001*")) {
2965                 DisplayFatalError(&buf[1], 0, 1);
2966                 continue;
2967             } else {
2968                 firstTime = FALSE;
2969             }
2970
2971             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2972                 ics_type = ICS_ICC;
2973                 ics_prefix = "/";
2974                 if (appData.debugMode)
2975                   fprintf(debugFP, "ics_type %d\n", ics_type);
2976                 continue;
2977             }
2978             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2979                 ics_type = ICS_FICS;
2980                 ics_prefix = "$";
2981                 if (appData.debugMode)
2982                   fprintf(debugFP, "ics_type %d\n", ics_type);
2983                 continue;
2984             }
2985             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2986                 ics_type = ICS_CHESSNET;
2987                 ics_prefix = "/";
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, "ics_type %d\n", ics_type);
2990                 continue;
2991             }
2992
2993             if (!loggedOn &&
2994                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2995                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2996                  looking_at(buf, &i, "will be \"*\""))) {
2997               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2998               continue;
2999             }
3000
3001             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3002               char buf[MSG_SIZ];
3003               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3004               DisplayIcsInteractionTitle(buf);
3005               have_set_title = TRUE;
3006             }
3007
3008             /* skip finger notes */
3009             if (started == STARTED_NONE &&
3010                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3011                  (buf[i] == '1' && buf[i+1] == '0')) &&
3012                 buf[i+2] == ':' && buf[i+3] == ' ') {
3013               started = STARTED_CHATTER;
3014               i += 3;
3015               continue;
3016             }
3017
3018             oldi = i;
3019             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3020             if(appData.seekGraph) {
3021                 if(soughtPending && MatchSoughtLine(buf+i)) {
3022                     i = strstr(buf+i, "rated") - buf;
3023                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3024                     next_out = leftover_start = i;
3025                     started = STARTED_CHATTER;
3026                     suppressKibitz = TRUE;
3027                     continue;
3028                 }
3029                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3030                         && looking_at(buf, &i, "* ads displayed")) {
3031                     soughtPending = FALSE;
3032                     seekGraphUp = TRUE;
3033                     DrawSeekGraph();
3034                     continue;
3035                 }
3036                 if(appData.autoRefresh) {
3037                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3038                         int s = (ics_type == ICS_ICC); // ICC format differs
3039                         if(seekGraphUp)
3040                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3041                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3042                         looking_at(buf, &i, "*% "); // eat prompt
3043                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3044                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3045                         next_out = i; // suppress
3046                         continue;
3047                     }
3048                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3049                         char *p = star_match[0];
3050                         while(*p) {
3051                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3052                             while(*p && *p++ != ' '); // next
3053                         }
3054                         looking_at(buf, &i, "*% "); // eat prompt
3055                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3056                         next_out = i;
3057                         continue;
3058                     }
3059                 }
3060             }
3061
3062             /* skip formula vars */
3063             if (started == STARTED_NONE &&
3064                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3065               started = STARTED_CHATTER;
3066               i += 3;
3067               continue;
3068             }
3069
3070             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3071             if (appData.autoKibitz && started == STARTED_NONE &&
3072                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3073                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3074                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3075                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3076                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3077                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3078                         suppressKibitz = TRUE;
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i;
3081                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3082                                 && (gameMode == IcsPlayingWhite)) ||
3083                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3084                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3085                             started = STARTED_CHATTER; // own kibitz we simply discard
3086                         else {
3087                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3088                             parse_pos = 0; parse[0] = NULLCHAR;
3089                             savingComment = TRUE;
3090                             suppressKibitz = gameMode != IcsObserving ? 2 :
3091                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3092                         }
3093                         continue;
3094                 } else
3095                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3096                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3097                          && atoi(star_match[0])) {
3098                     // suppress the acknowledgements of our own autoKibitz
3099                     char *p;
3100                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3102                     SendToPlayer(star_match[0], strlen(star_match[0]));
3103                     if(looking_at(buf, &i, "*% ")) // eat prompt
3104                         suppressKibitz = FALSE;
3105                     next_out = i;
3106                     continue;
3107                 }
3108             } // [HGM] kibitz: end of patch
3109
3110             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3111
3112             // [HGM] chat: intercept tells by users for which we have an open chat window
3113             channel = -1;
3114             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3115                                            looking_at(buf, &i, "* whispers:") ||
3116                                            looking_at(buf, &i, "* kibitzes:") ||
3117                                            looking_at(buf, &i, "* shouts:") ||
3118                                            looking_at(buf, &i, "* c-shouts:") ||
3119                                            looking_at(buf, &i, "--> * ") ||
3120                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3121                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3122                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3123                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3124                 int p;
3125                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3126                 chattingPartner = -1;
3127
3128                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3129                 for(p=0; p<MAX_CHAT; p++) {
3130                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3131                     talker[0] = '['; strcat(talker, "] ");
3132                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3133                     chattingPartner = p; break;
3134                     }
3135                 } else
3136                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3137                 for(p=0; p<MAX_CHAT; p++) {
3138                     if(!strcmp("kibitzes", chatPartner[p])) {
3139                         talker[0] = '['; strcat(talker, "] ");
3140                         chattingPartner = p; break;
3141                     }
3142                 } else
3143                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3144                 for(p=0; p<MAX_CHAT; p++) {
3145                     if(!strcmp("whispers", chatPartner[p])) {
3146                         talker[0] = '['; strcat(talker, "] ");
3147                         chattingPartner = p; break;
3148                     }
3149                 } else
3150                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3151                   if(buf[i-8] == '-' && buf[i-3] == 't')
3152                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3153                     if(!strcmp("c-shouts", chatPartner[p])) {
3154                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3155                         chattingPartner = p; break;
3156                     }
3157                   }
3158                   if(chattingPartner < 0)
3159                   for(p=0; p<MAX_CHAT; p++) {
3160                     if(!strcmp("shouts", chatPartner[p])) {
3161                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3162                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3163                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3164                         chattingPartner = p; break;
3165                     }
3166                   }
3167                 }
3168                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3169                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3170                     talker[0] = 0; Colorize(ColorTell, FALSE);
3171                     chattingPartner = p; break;
3172                 }
3173                 if(chattingPartner<0) i = oldi; else {
3174                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3175                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3176                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3177                     started = STARTED_COMMENT;
3178                     parse_pos = 0; parse[0] = NULLCHAR;
3179                     savingComment = 3 + chattingPartner; // counts as TRUE
3180                     suppressKibitz = TRUE;
3181                     continue;
3182                 }
3183             } // [HGM] chat: end of patch
3184
3185           backup = i;
3186             if (appData.zippyTalk || appData.zippyPlay) {
3187                 /* [DM] Backup address for color zippy lines */
3188 #if ZIPPY
3189                if (loggedOn == TRUE)
3190                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3191                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3192 #endif
3193             } // [DM] 'else { ' deleted
3194                 if (
3195                     /* Regular tells and says */
3196                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3197                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3198                     looking_at(buf, &i, "* says: ") ||
3199                     /* Don't color "message" or "messages" output */
3200                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3201                     looking_at(buf, &i, "*. * at *:*: ") ||
3202                     looking_at(buf, &i, "--* (*:*): ") ||
3203                     /* Message notifications (same color as tells) */
3204                     looking_at(buf, &i, "* has left a message ") ||
3205                     looking_at(buf, &i, "* just sent you a message:\n") ||
3206                     /* Whispers and kibitzes */
3207                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3208                     looking_at(buf, &i, "* kibitzes: ") ||
3209                     /* Channel tells */
3210                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3211
3212                   if (tkind == 1 && strchr(star_match[0], ':')) {
3213                       /* Avoid "tells you:" spoofs in channels */
3214                      tkind = 3;
3215                   }
3216                   if (star_match[0][0] == NULLCHAR ||
3217                       strchr(star_match[0], ' ') ||
3218                       (tkind == 3 && strchr(star_match[1], ' '))) {
3219                     /* Reject bogus matches */
3220                     i = oldi;
3221                   } else {
3222                     if (appData.colorize) {
3223                       if (oldi > next_out) {
3224                         SendToPlayer(&buf[next_out], oldi - next_out);
3225                         next_out = oldi;
3226                       }
3227                       switch (tkind) {
3228                       case 1:
3229                         Colorize(ColorTell, FALSE);
3230                         curColor = ColorTell;
3231                         break;
3232                       case 2:
3233                         Colorize(ColorKibitz, FALSE);
3234                         curColor = ColorKibitz;
3235                         break;
3236                       case 3:
3237                         p = strrchr(star_match[1], '(');
3238                         if (p == NULL) {
3239                           p = star_match[1];
3240                         } else {
3241                           p++;
3242                         }
3243                         if (atoi(p) == 1) {
3244                           Colorize(ColorChannel1, FALSE);
3245                           curColor = ColorChannel1;
3246                         } else {
3247                           Colorize(ColorChannel, FALSE);
3248                           curColor = ColorChannel;
3249                         }
3250                         break;
3251                       case 5:
3252                         curColor = ColorNormal;
3253                         break;
3254                       }
3255                     }
3256                     if (started == STARTED_NONE && appData.autoComment &&
3257                         (gameMode == IcsObserving ||
3258                          gameMode == IcsPlayingWhite ||
3259                          gameMode == IcsPlayingBlack)) {
3260                       parse_pos = i - oldi;
3261                       memcpy(parse, &buf[oldi], parse_pos);
3262                       parse[parse_pos] = NULLCHAR;
3263                       started = STARTED_COMMENT;
3264                       savingComment = TRUE;
3265                     } else {
3266                       started = STARTED_CHATTER;
3267                       savingComment = FALSE;
3268                     }
3269                     loggedOn = TRUE;
3270                     continue;
3271                   }
3272                 }
3273
3274                 if (looking_at(buf, &i, "* s-shouts: ") ||
3275                     looking_at(buf, &i, "* c-shouts: ")) {
3276                     if (appData.colorize) {
3277                         if (oldi > next_out) {
3278                             SendToPlayer(&buf[next_out], oldi - next_out);
3279                             next_out = oldi;
3280                         }
3281                         Colorize(ColorSShout, FALSE);
3282                         curColor = ColorSShout;
3283                     }
3284                     loggedOn = TRUE;
3285                     started = STARTED_CHATTER;
3286                     continue;
3287                 }
3288
3289                 if (looking_at(buf, &i, "--->")) {
3290                     loggedOn = TRUE;
3291                     continue;
3292                 }
3293
3294                 if (looking_at(buf, &i, "* shouts: ") ||
3295                     looking_at(buf, &i, "--> ")) {
3296                     if (appData.colorize) {
3297                         if (oldi > next_out) {
3298                             SendToPlayer(&buf[next_out], oldi - next_out);
3299                             next_out = oldi;
3300                         }
3301                         Colorize(ColorShout, FALSE);
3302                         curColor = ColorShout;
3303                     }
3304                     loggedOn = TRUE;
3305                     started = STARTED_CHATTER;
3306                     continue;
3307                 }
3308
3309                 if (looking_at( buf, &i, "Challenge:")) {
3310                     if (appData.colorize) {
3311                         if (oldi > next_out) {
3312                             SendToPlayer(&buf[next_out], oldi - next_out);
3313                             next_out = oldi;
3314                         }
3315                         Colorize(ColorChallenge, FALSE);
3316                         curColor = ColorChallenge;
3317                     }
3318                     loggedOn = TRUE;
3319                     continue;
3320                 }
3321
3322                 if (looking_at(buf, &i, "* offers you") ||
3323                     looking_at(buf, &i, "* offers to be") ||
3324                     looking_at(buf, &i, "* would like to") ||
3325                     looking_at(buf, &i, "* requests to") ||
3326                     looking_at(buf, &i, "Your opponent offers") ||
3327                     looking_at(buf, &i, "Your opponent requests")) {
3328
3329                     if (appData.colorize) {
3330                         if (oldi > next_out) {
3331                             SendToPlayer(&buf[next_out], oldi - next_out);
3332                             next_out = oldi;
3333                         }
3334                         Colorize(ColorRequest, FALSE);
3335                         curColor = ColorRequest;
3336                     }
3337                     continue;
3338                 }
3339
3340                 if (looking_at(buf, &i, "* (*) seeking")) {
3341                     if (appData.colorize) {
3342                         if (oldi > next_out) {
3343                             SendToPlayer(&buf[next_out], oldi - next_out);
3344                             next_out = oldi;
3345                         }
3346                         Colorize(ColorSeek, FALSE);
3347                         curColor = ColorSeek;
3348                     }
3349                     continue;
3350             }
3351
3352           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3353
3354             if (looking_at(buf, &i, "\\   ")) {
3355                 if (prevColor != ColorNormal) {
3356                     if (oldi > next_out) {
3357                         SendToPlayer(&buf[next_out], oldi - next_out);
3358                         next_out = oldi;
3359                     }
3360                     Colorize(prevColor, TRUE);
3361                     curColor = prevColor;
3362                 }
3363                 if (savingComment) {
3364                     parse_pos = i - oldi;
3365                     memcpy(parse, &buf[oldi], parse_pos);
3366                     parse[parse_pos] = NULLCHAR;
3367                     started = STARTED_COMMENT;
3368                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3369                         chattingPartner = savingComment - 3; // kludge to remember the box
3370                 } else {
3371                     started = STARTED_CHATTER;
3372                 }
3373                 continue;
3374             }
3375
3376             if (looking_at(buf, &i, "Black Strength :") ||
3377                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3378                 looking_at(buf, &i, "<10>") ||
3379                 looking_at(buf, &i, "#@#")) {
3380                 /* Wrong board style */
3381                 loggedOn = TRUE;
3382                 SendToICS(ics_prefix);
3383                 SendToICS("set style 12\n");
3384                 SendToICS(ics_prefix);
3385                 SendToICS("refresh\n");
3386                 continue;
3387             }
3388
3389             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3390                 ICSInitScript();
3391                 have_sent_ICS_logon = 1;
3392                 continue;
3393             }
3394
3395             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3396                 (looking_at(buf, &i, "\n<12> ") ||
3397                  looking_at(buf, &i, "<12> "))) {
3398                 loggedOn = TRUE;
3399                 if (oldi > next_out) {
3400                     SendToPlayer(&buf[next_out], oldi - next_out);
3401                 }
3402                 next_out = i;
3403                 started = STARTED_BOARD;
3404                 parse_pos = 0;
3405                 continue;
3406             }
3407
3408             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3409                 looking_at(buf, &i, "<b1> ")) {
3410                 if (oldi > next_out) {
3411                     SendToPlayer(&buf[next_out], oldi - next_out);
3412                 }
3413                 next_out = i;
3414                 started = STARTED_HOLDINGS;
3415                 parse_pos = 0;
3416                 continue;
3417             }
3418
3419             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3420                 loggedOn = TRUE;
3421                 /* Header for a move list -- first line */
3422
3423                 switch (ics_getting_history) {
3424                   case H_FALSE:
3425                     switch (gameMode) {
3426                       case IcsIdle:
3427                       case BeginningOfGame:
3428                         /* User typed "moves" or "oldmoves" while we
3429                            were idle.  Pretend we asked for these
3430                            moves and soak them up so user can step
3431                            through them and/or save them.
3432                            */
3433                         Reset(FALSE, TRUE);
3434                         gameMode = IcsObserving;
3435                         ModeHighlight();
3436                         ics_gamenum = -1;
3437                         ics_getting_history = H_GOT_UNREQ_HEADER;
3438                         break;
3439                       case EditGame: /*?*/
3440                       case EditPosition: /*?*/
3441                         /* Should above feature work in these modes too? */
3442                         /* For now it doesn't */
3443                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3444                         break;
3445                       default:
3446                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3447                         break;
3448                     }
3449                     break;
3450                   case H_REQUESTED:
3451                     /* Is this the right one? */
3452                     if (gameInfo.white && gameInfo.black &&
3453                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3454                         strcmp(gameInfo.black, star_match[2]) == 0) {
3455                         /* All is well */
3456                         ics_getting_history = H_GOT_REQ_HEADER;
3457                     }
3458                     break;
3459                   case H_GOT_REQ_HEADER:
3460                   case H_GOT_UNREQ_HEADER:
3461                   case H_GOT_UNWANTED_HEADER:
3462                   case H_GETTING_MOVES:
3463                     /* Should not happen */
3464                     DisplayError(_("Error gathering move list: two headers"), 0);
3465                     ics_getting_history = H_FALSE;
3466                     break;
3467                 }
3468
3469                 /* Save player ratings into gameInfo if needed */
3470                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3471                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3472                     (gameInfo.whiteRating == -1 ||
3473                      gameInfo.blackRating == -1)) {
3474
3475                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3476                     gameInfo.blackRating = string_to_rating(star_match[3]);
3477                     if (appData.debugMode)
3478                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3479                               gameInfo.whiteRating, gameInfo.blackRating);
3480                 }
3481                 continue;
3482             }
3483
3484             if (looking_at(buf, &i,
3485               "* * match, initial time: * minute*, increment: * second")) {
3486                 /* Header for a move list -- second line */
3487                 /* Initial board will follow if this is a wild game */
3488                 if (gameInfo.event != NULL) free(gameInfo.event);
3489                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3490                 gameInfo.event = StrSave(str);
3491                 /* [HGM] we switched variant. Translate boards if needed. */
3492                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3493                 continue;
3494             }
3495
3496             if (looking_at(buf, &i, "Move  ")) {
3497                 /* Beginning of a move list */
3498                 switch (ics_getting_history) {
3499                   case H_FALSE:
3500                     /* Normally should not happen */
3501                     /* Maybe user hit reset while we were parsing */
3502                     break;
3503                   case H_REQUESTED:
3504                     /* Happens if we are ignoring a move list that is not
3505                      * the one we just requested.  Common if the user
3506                      * tries to observe two games without turning off
3507                      * getMoveList */
3508                     break;
3509                   case H_GETTING_MOVES:
3510                     /* Should not happen */
3511                     DisplayError(_("Error gathering move list: nested"), 0);
3512                     ics_getting_history = H_FALSE;
3513                     break;
3514                   case H_GOT_REQ_HEADER:
3515                     ics_getting_history = H_GETTING_MOVES;
3516                     started = STARTED_MOVES;
3517                     parse_pos = 0;
3518                     if (oldi > next_out) {
3519                         SendToPlayer(&buf[next_out], oldi - next_out);
3520                     }
3521                     break;
3522                   case H_GOT_UNREQ_HEADER:
3523                     ics_getting_history = H_GETTING_MOVES;
3524                     started = STARTED_MOVES_NOHIDE;
3525                     parse_pos = 0;
3526                     break;
3527                   case H_GOT_UNWANTED_HEADER:
3528                     ics_getting_history = H_FALSE;
3529                     break;
3530                 }
3531                 continue;
3532             }
3533
3534             if (looking_at(buf, &i, "% ") ||
3535                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3536                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3537                 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3538                     soughtPending = FALSE;
3539                     seekGraphUp = TRUE;
3540                     DrawSeekGraph();
3541                 }
3542                 if(suppressKibitz) next_out = i;
3543                 savingComment = FALSE;
3544                 suppressKibitz = 0;
3545                 switch (started) {
3546                   case STARTED_MOVES:
3547                   case STARTED_MOVES_NOHIDE:
3548                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3549                     parse[parse_pos + i - oldi] = NULLCHAR;
3550                     ParseGameHistory(parse);
3551 #if ZIPPY
3552                     if (appData.zippyPlay && first.initDone) {
3553                         FeedMovesToProgram(&first, forwardMostMove);
3554                         if (gameMode == IcsPlayingWhite) {
3555                             if (WhiteOnMove(forwardMostMove)) {
3556                                 if (first.sendTime) {
3557                                   if (first.useColors) {
3558                                     SendToProgram("black\n", &first);
3559                                   }
3560                                   SendTimeRemaining(&first, TRUE);
3561                                 }
3562                                 if (first.useColors) {
3563                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3564                                 }
3565                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3566                                 first.maybeThinking = TRUE;
3567                             } else {
3568                                 if (first.usePlayother) {
3569                                   if (first.sendTime) {
3570                                     SendTimeRemaining(&first, TRUE);
3571                                   }
3572                                   SendToProgram("playother\n", &first);
3573                                   firstMove = FALSE;
3574                                 } else {
3575                                   firstMove = TRUE;
3576                                 }
3577                             }
3578                         } else if (gameMode == IcsPlayingBlack) {
3579                             if (!WhiteOnMove(forwardMostMove)) {
3580                                 if (first.sendTime) {
3581                                   if (first.useColors) {
3582                                     SendToProgram("white\n", &first);
3583                                   }
3584                                   SendTimeRemaining(&first, FALSE);
3585                                 }
3586                                 if (first.useColors) {
3587                                   SendToProgram("black\n", &first);
3588                                 }
3589                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3590                                 first.maybeThinking = TRUE;
3591                             } else {
3592                                 if (first.usePlayother) {
3593                                   if (first.sendTime) {
3594                                     SendTimeRemaining(&first, FALSE);
3595                                   }
3596                                   SendToProgram("playother\n", &first);
3597                                   firstMove = FALSE;
3598                                 } else {
3599                                   firstMove = TRUE;
3600                                 }
3601                             }
3602                         }
3603                     }
3604 #endif
3605                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3606                         /* Moves came from oldmoves or moves command
3607                            while we weren't doing anything else.
3608                            */
3609                         currentMove = forwardMostMove;
3610                         ClearHighlights();/*!!could figure this out*/
3611                         flipView = appData.flipView;
3612                         DrawPosition(TRUE, boards[currentMove]);
3613                         DisplayBothClocks();
3614                         snprintf(str, MSG_SIZ, "%s %s %s",
3615                                 gameInfo.white, _("vs."),  gameInfo.black);
3616                         DisplayTitle(str);
3617                         gameMode = IcsIdle;
3618                     } else {
3619                         /* Moves were history of an active game */
3620                         if (gameInfo.resultDetails != NULL) {
3621                             free(gameInfo.resultDetails);
3622                             gameInfo.resultDetails = NULL;
3623                         }
3624                     }
3625                     HistorySet(parseList, backwardMostMove,
3626                                forwardMostMove, currentMove-1);
3627                     DisplayMove(currentMove - 1);
3628                     if (started == STARTED_MOVES) next_out = i;
3629                     started = STARTED_NONE;
3630                     ics_getting_history = H_FALSE;
3631                     break;
3632
3633                   case STARTED_OBSERVE:
3634                     started = STARTED_NONE;
3635                     SendToICS(ics_prefix);
3636                     SendToICS("refresh\n");
3637                     break;
3638
3639                   default:
3640                     break;
3641                 }
3642                 if(bookHit) { // [HGM] book: simulate book reply
3643                     static char bookMove[MSG_SIZ]; // a bit generous?
3644
3645                     programStats.nodes = programStats.depth = programStats.time =
3646                     programStats.score = programStats.got_only_move = 0;
3647                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3648
3649                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3650                     strcat(bookMove, bookHit);
3651                     HandleMachineMove(bookMove, &first);
3652                 }
3653                 continue;
3654             }
3655
3656             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3657                  started == STARTED_HOLDINGS ||
3658                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3659                 /* Accumulate characters in move list or board */
3660                 parse[parse_pos++] = buf[i];
3661             }
3662
3663             /* Start of game messages.  Mostly we detect start of game
3664                when the first board image arrives.  On some versions
3665                of the ICS, though, we need to do a "refresh" after starting
3666                to observe in order to get the current board right away. */
3667             if (looking_at(buf, &i, "Adding game * to observation list")) {
3668                 started = STARTED_OBSERVE;
3669                 continue;
3670             }
3671
3672             /* Handle auto-observe */
3673             if (appData.autoObserve &&
3674                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3675                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3676                 char *player;
3677                 /* Choose the player that was highlighted, if any. */
3678                 if (star_match[0][0] == '\033' ||
3679                     star_match[1][0] != '\033') {
3680                     player = star_match[0];
3681                 } else {
3682                     player = star_match[2];
3683                 }
3684                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3685                         ics_prefix, StripHighlightAndTitle(player));
3686                 SendToICS(str);
3687
3688                 /* Save ratings from notify string */
3689                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3690                 player1Rating = string_to_rating(star_match[1]);
3691                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3692                 player2Rating = string_to_rating(star_match[3]);
3693
3694                 if (appData.debugMode)
3695                   fprintf(debugFP,
3696                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3697                           player1Name, player1Rating,
3698                           player2Name, player2Rating);
3699
3700                 continue;
3701             }
3702
3703             /* Deal with automatic examine mode after a game,
3704                and with IcsObserving -> IcsExamining transition */
3705             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3706                 looking_at(buf, &i, "has made you an examiner of game *")) {
3707
3708                 int gamenum = atoi(star_match[0]);
3709                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3710                     gamenum == ics_gamenum) {
3711                     /* We were already playing or observing this game;
3712                        no need to refetch history */
3713                     gameMode = IcsExamining;
3714                     if (pausing) {
3715                         pauseExamForwardMostMove = forwardMostMove;
3716                     } else if (currentMove < forwardMostMove) {
3717                         ForwardInner(forwardMostMove);
3718                     }
3719                 } else {
3720                     /* I don't think this case really can happen */
3721                     SendToICS(ics_prefix);
3722                     SendToICS("refresh\n");
3723                 }
3724                 continue;
3725             }
3726
3727             /* Error messages */
3728 //          if (ics_user_moved) {
3729             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3730                 if (looking_at(buf, &i, "Illegal move") ||
3731                     looking_at(buf, &i, "Not a legal move") ||
3732                     looking_at(buf, &i, "Your king is in check") ||
3733                     looking_at(buf, &i, "It isn't your turn") ||
3734                     looking_at(buf, &i, "It is not your move")) {
3735                     /* Illegal move */
3736                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3737                         currentMove = forwardMostMove-1;
3738                         DisplayMove(currentMove - 1); /* before DMError */
3739                         DrawPosition(FALSE, boards[currentMove]);
3740                         SwitchClocks(forwardMostMove-1); // [HGM] race
3741                         DisplayBothClocks();
3742                     }
3743                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3744                     ics_user_moved = 0;
3745                     continue;
3746                 }
3747             }
3748
3749             if (looking_at(buf, &i, "still have time") ||
3750                 looking_at(buf, &i, "not out of time") ||
3751                 looking_at(buf, &i, "either player is out of time") ||
3752                 looking_at(buf, &i, "has timeseal; checking")) {
3753                 /* We must have called his flag a little too soon */
3754                 whiteFlag = blackFlag = FALSE;
3755                 continue;
3756             }
3757
3758             if (looking_at(buf, &i, "added * seconds to") ||
3759                 looking_at(buf, &i, "seconds were added to")) {
3760                 /* Update the clocks */
3761                 SendToICS(ics_prefix);
3762                 SendToICS("refresh\n");
3763                 continue;
3764             }
3765
3766             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3767                 ics_clock_paused = TRUE;
3768                 StopClocks();
3769                 continue;
3770             }
3771
3772             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3773                 ics_clock_paused = FALSE;
3774                 StartClocks();
3775                 continue;
3776             }
3777
3778             /* Grab player ratings from the Creating: message.
3779                Note we have to check for the special case when
3780                the ICS inserts things like [white] or [black]. */
3781             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3782                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3783                 /* star_matches:
3784                    0    player 1 name (not necessarily white)
3785                    1    player 1 rating
3786                    2    empty, white, or black (IGNORED)
3787                    3    player 2 name (not necessarily black)
3788                    4    player 2 rating
3789
3790                    The names/ratings are sorted out when the game
3791                    actually starts (below).
3792                 */
3793                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3794                 player1Rating = string_to_rating(star_match[1]);
3795                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3796                 player2Rating = string_to_rating(star_match[4]);
3797
3798                 if (appData.debugMode)
3799                   fprintf(debugFP,
3800                           "Ratings from 'Creating:' %s %d, %s %d\n",
3801                           player1Name, player1Rating,
3802                           player2Name, player2Rating);
3803
3804                 continue;
3805             }
3806
3807             /* Improved generic start/end-of-game messages */
3808             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3809                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3810                 /* If tkind == 0: */
3811                 /* star_match[0] is the game number */
3812                 /*           [1] is the white player's name */
3813                 /*           [2] is the black player's name */
3814                 /* For end-of-game: */
3815                 /*           [3] is the reason for the game end */
3816                 /*           [4] is a PGN end game-token, preceded by " " */
3817                 /* For start-of-game: */
3818                 /*           [3] begins with "Creating" or "Continuing" */
3819                 /*           [4] is " *" or empty (don't care). */
3820                 int gamenum = atoi(star_match[0]);
3821                 char *whitename, *blackname, *why, *endtoken;
3822                 ChessMove endtype = EndOfFile;
3823
3824                 if (tkind == 0) {
3825                   whitename = star_match[1];
3826                   blackname = star_match[2];
3827                   why = star_match[3];
3828                   endtoken = star_match[4];
3829                 } else {
3830                   whitename = star_match[1];
3831                   blackname = star_match[3];
3832                   why = star_match[5];
3833                   endtoken = star_match[6];
3834                 }
3835
3836                 /* Game start messages */
3837                 if (strncmp(why, "Creating ", 9) == 0 ||
3838                     strncmp(why, "Continuing ", 11) == 0) {
3839                     gs_gamenum = gamenum;
3840                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3841                     if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3842                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3843 #if ZIPPY
3844                     if (appData.zippyPlay) {
3845                         ZippyGameStart(whitename, blackname);
3846                     }
3847 #endif /*ZIPPY*/
3848                     partnerBoardValid = FALSE; // [HGM] bughouse
3849                     continue;
3850                 }
3851
3852                 /* Game end messages */
3853                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3854                     ics_gamenum != gamenum) {
3855                     continue;
3856                 }
3857                 while (endtoken[0] == ' ') endtoken++;
3858                 switch (endtoken[0]) {
3859                   case '*':
3860                   default:
3861                     endtype = GameUnfinished;
3862                     break;
3863                   case '0':
3864                     endtype = BlackWins;
3865                     break;
3866                   case '1':
3867                     if (endtoken[1] == '/')
3868                       endtype = GameIsDrawn;
3869                     else
3870                       endtype = WhiteWins;
3871                     break;
3872                 }
3873                 GameEnds(endtype, why, GE_ICS);
3874 #if ZIPPY
3875                 if (appData.zippyPlay && first.initDone) {
3876                     ZippyGameEnd(endtype, why);
3877                     if (first.pr == NoProc) {
3878                       /* Start the next process early so that we'll
3879                          be ready for the next challenge */
3880                       StartChessProgram(&first);
3881                     }
3882                     /* Send "new" early, in case this command takes
3883                        a long time to finish, so that we'll be ready
3884                        for the next challenge. */
3885                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3886                     Reset(TRUE, TRUE);
3887                 }
3888 #endif /*ZIPPY*/
3889                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3890                 continue;
3891             }
3892
3893             if (looking_at(buf, &i, "Removing game * from observation") ||
3894                 looking_at(buf, &i, "no longer observing game *") ||
3895                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3896                 if (gameMode == IcsObserving &&
3897                     atoi(star_match[0]) == ics_gamenum)
3898                   {
3899                       /* icsEngineAnalyze */
3900                       if (appData.icsEngineAnalyze) {
3901                             ExitAnalyzeMode();
3902                             ModeHighlight();
3903                       }
3904                       StopClocks();
3905                       gameMode = IcsIdle;
3906                       ics_gamenum = -1;
3907                       ics_user_moved = FALSE;
3908                   }
3909                 continue;
3910             }
3911
3912             if (looking_at(buf, &i, "no longer examining game *")) {
3913                 if (gameMode == IcsExamining &&
3914                     atoi(star_match[0]) == ics_gamenum)
3915                   {
3916                       gameMode = IcsIdle;
3917                       ics_gamenum = -1;
3918                       ics_user_moved = FALSE;
3919                   }
3920                 continue;
3921             }
3922
3923             /* Advance leftover_start past any newlines we find,
3924                so only partial lines can get reparsed */
3925             if (looking_at(buf, &i, "\n")) {
3926                 prevColor = curColor;
3927                 if (curColor != ColorNormal) {
3928                     if (oldi > next_out) {
3929                         SendToPlayer(&buf[next_out], oldi - next_out);
3930                         next_out = oldi;
3931                     }
3932                     Colorize(ColorNormal, FALSE);
3933                     curColor = ColorNormal;
3934                 }
3935                 if (started == STARTED_BOARD) {
3936                     started = STARTED_NONE;
3937                     parse[parse_pos] = NULLCHAR;
3938                     ParseBoard12(parse);
3939                     ics_user_moved = 0;
3940
3941                     /* Send premove here */
3942                     if (appData.premove) {
3943                       char str[MSG_SIZ];
3944                       if (currentMove == 0 &&
3945                           gameMode == IcsPlayingWhite &&
3946                           appData.premoveWhite) {
3947                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3948                         if (appData.debugMode)
3949                           fprintf(debugFP, "Sending premove:\n");
3950                         SendToICS(str);
3951                       } else if (currentMove == 1 &&
3952                                  gameMode == IcsPlayingBlack &&
3953                                  appData.premoveBlack) {
3954                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3955                         if (appData.debugMode)
3956                           fprintf(debugFP, "Sending premove:\n");
3957                         SendToICS(str);
3958                       } else if (gotPremove) {
3959                         gotPremove = 0;
3960                         ClearPremoveHighlights();
3961                         if (appData.debugMode)
3962                           fprintf(debugFP, "Sending premove:\n");
3963                           UserMoveEvent(premoveFromX, premoveFromY,
3964                                         premoveToX, premoveToY,
3965                                         premovePromoChar);
3966                       }
3967                     }
3968
3969                     /* Usually suppress following prompt */
3970                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3971                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3972                         if (looking_at(buf, &i, "*% ")) {
3973                             savingComment = FALSE;
3974                             suppressKibitz = 0;
3975                         }
3976                     }
3977                     next_out = i;
3978                 } else if (started == STARTED_HOLDINGS) {
3979                     int gamenum;
3980                     char new_piece[MSG_SIZ];
3981                     started = STARTED_NONE;
3982                     parse[parse_pos] = NULLCHAR;
3983                     if (appData.debugMode)
3984                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3985                                                         parse, currentMove);
3986                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3987                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3988                         if (gameInfo.variant == VariantNormal) {
3989                           /* [HGM] We seem to switch variant during a game!
3990                            * Presumably no holdings were displayed, so we have
3991                            * to move the position two files to the right to
3992                            * create room for them!
3993                            */
3994                           VariantClass newVariant;
3995                           switch(gameInfo.boardWidth) { // base guess on board width
3996                                 case 9:  newVariant = VariantShogi; break;
3997                                 case 10: newVariant = VariantGreat; break;
3998                                 default: newVariant = VariantCrazyhouse; break;
3999                           }
4000                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4001                           /* Get a move list just to see the header, which
4002                              will tell us whether this is really bug or zh */
4003                           if (ics_getting_history == H_FALSE) {
4004                             ics_getting_history = H_REQUESTED;
4005                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4006                             SendToICS(str);
4007                           }
4008                         }
4009                         new_piece[0] = NULLCHAR;
4010                         sscanf(parse, "game %d white [%s black [%s <- %s",
4011                                &gamenum, white_holding, black_holding,
4012                                new_piece);
4013                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4014                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4015                         /* [HGM] copy holdings to board holdings area */
4016                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4017                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4018                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4019 #if ZIPPY
4020                         if (appData.zippyPlay && first.initDone) {
4021                             ZippyHoldings(white_holding, black_holding,
4022                                           new_piece);
4023                         }
4024 #endif /*ZIPPY*/
4025                         if (tinyLayout || smallLayout) {
4026                             char wh[16], bh[16];
4027                             PackHolding(wh, white_holding);
4028                             PackHolding(bh, black_holding);
4029                             snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh,
4030                                     gameInfo.white, gameInfo.black);
4031                         } else {
4032                           snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]",
4033                                     gameInfo.white, white_holding, _("vs."),
4034                                     gameInfo.black, black_holding);
4035                         }
4036                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4037                         DrawPosition(FALSE, boards[currentMove]);
4038                         DisplayTitle(str);
4039                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4040                         sscanf(parse, "game %d white [%s black [%s <- %s",
4041                                &gamenum, white_holding, black_holding,
4042                                new_piece);
4043                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4044                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4045                         /* [HGM] copy holdings to partner-board holdings area */
4046                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4047                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4048                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4049                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4050                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4051                       }
4052                     }
4053                     /* Suppress following prompt */
4054                     if (looking_at(buf, &i, "*% ")) {
4055                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4056                         savingComment = FALSE;
4057                         suppressKibitz = 0;
4058                     }
4059                     next_out = i;
4060                 }
4061                 continue;
4062             }
4063
4064             i++;                /* skip unparsed character and loop back */
4065         }
4066
4067         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4068 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4069 //          SendToPlayer(&buf[next_out], i - next_out);
4070             started != STARTED_HOLDINGS && leftover_start > next_out) {
4071             SendToPlayer(&buf[next_out], leftover_start - next_out);
4072             next_out = i;
4073         }
4074
4075         leftover_len = buf_len - leftover_start;
4076         /* if buffer ends with something we couldn't parse,
4077            reparse it after appending the next read */
4078
4079     } else if (count == 0) {
4080         RemoveInputSource(isr);
4081         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4082     } else {
4083         DisplayFatalError(_("Error reading from ICS"), error, 1);
4084     }
4085 }
4086
4087
4088 /* Board style 12 looks like this:
4089
4090    <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
4091
4092  * The "<12> " is stripped before it gets to this routine.  The two
4093  * trailing 0's (flip state and clock ticking) are later addition, and
4094  * some chess servers may not have them, or may have only the first.
4095  * Additional trailing fields may be added in the future.
4096  */
4097
4098 #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"
4099
4100 #define RELATION_OBSERVING_PLAYED    0
4101 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4102 #define RELATION_PLAYING_MYMOVE      1
4103 #define RELATION_PLAYING_NOTMYMOVE  -1
4104 #define RELATION_EXAMINING           2
4105 #define RELATION_ISOLATED_BOARD     -3
4106 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4107
4108 void
4109 ParseBoard12 (char *string)
4110 {
4111     GameMode newGameMode;
4112     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4113     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4114     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4115     char to_play, board_chars[200];
4116     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4117     char black[32], white[32];
4118     Board board;
4119     int prevMove = currentMove;
4120     int ticking = 2;
4121     ChessMove moveType;
4122     int fromX, fromY, toX, toY;
4123     char promoChar;
4124     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4125     char *bookHit = NULL; // [HGM] book
4126     Boolean weird = FALSE, reqFlag = FALSE;
4127
4128     fromX = fromY = toX = toY = -1;
4129
4130     newGame = FALSE;
4131
4132     if (appData.debugMode)
4133       fprintf(debugFP, _("Parsing board: %s\n"), string);
4134
4135     move_str[0] = NULLCHAR;
4136     elapsed_time[0] = NULLCHAR;
4137     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4138         int  i = 0, j;
4139         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4140             if(string[i] == ' ') { ranks++; files = 0; }
4141             else files++;
4142             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4143             i++;
4144         }
4145         for(j = 0; j <i; j++) board_chars[j] = string[j];
4146         board_chars[i] = '\0';
4147         string += i + 1;
4148     }
4149     n = sscanf(string, PATTERN, &to_play, &double_push,
4150                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4151                &gamenum, white, black, &relation, &basetime, &increment,
4152                &white_stren, &black_stren, &white_time, &black_time,
4153                &moveNum, str, elapsed_time, move_str, &ics_flip,
4154                &ticking);
4155
4156     if (n < 21) {
4157         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4158         DisplayError(str, 0);
4159         return;
4160     }
4161
4162     /* Convert the move number to internal form */
4163     moveNum = (moveNum - 1) * 2;
4164     if (to_play == 'B') moveNum++;
4165     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4166       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4167                         0, 1);
4168       return;
4169     }
4170
4171     switch (relation) {
4172       case RELATION_OBSERVING_PLAYED:
4173       case RELATION_OBSERVING_STATIC:
4174         if (gamenum == -1) {
4175             /* Old ICC buglet */
4176             relation = RELATION_OBSERVING_STATIC;
4177         }
4178         newGameMode = IcsObserving;
4179         break;
4180       case RELATION_PLAYING_MYMOVE:
4181       case RELATION_PLAYING_NOTMYMOVE:
4182         newGameMode =
4183           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4184             IcsPlayingWhite : IcsPlayingBlack;
4185         break;
4186       case RELATION_EXAMINING:
4187         newGameMode = IcsExamining;
4188         break;
4189       case RELATION_ISOLATED_BOARD:
4190       default:
4191         /* Just display this board.  If user was doing something else,
4192            we will forget about it until the next board comes. */
4193         newGameMode = IcsIdle;
4194         break;
4195       case RELATION_STARTING_POSITION:
4196         newGameMode = gameMode;
4197         break;
4198     }
4199
4200     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4201          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4202       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4203       char *toSqr;
4204       for (k = 0; k < ranks; k++) {
4205         for (j = 0; j < files; j++)
4206           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4207         if(gameInfo.holdingsWidth > 1) {
4208              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4209              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4210         }
4211       }
4212       CopyBoard(partnerBoard, board);
4213       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4214         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4215         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4216       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4217       if(toSqr = strchr(str, '-')) {
4218         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4219         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4220       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4221       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4222       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4223       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4224       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4225       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4226                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4227       DisplayMessage(partnerStatus, "");
4228         partnerBoardValid = TRUE;
4229       return;
4230     }
4231
4232     /* Modify behavior for initial board display on move listing
4233        of wild games.
4234        */
4235     switch (ics_getting_history) {
4236       case H_FALSE:
4237       case H_REQUESTED:
4238         break;
4239       case H_GOT_REQ_HEADER:
4240       case H_GOT_UNREQ_HEADER:
4241         /* This is the initial position of the current game */
4242         gamenum = ics_gamenum;
4243         moveNum = 0;            /* old ICS bug workaround */
4244         if (to_play == 'B') {
4245           startedFromSetupPosition = TRUE;
4246           blackPlaysFirst = TRUE;
4247           moveNum = 1;
4248           if (forwardMostMove == 0) forwardMostMove = 1;
4249           if (backwardMostMove == 0) backwardMostMove = 1;
4250           if (currentMove == 0) currentMove = 1;
4251         }
4252         newGameMode = gameMode;
4253         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4254         break;
4255       case H_GOT_UNWANTED_HEADER:
4256         /* This is an initial board that we don't want */
4257         return;
4258       case H_GETTING_MOVES:
4259         /* Should not happen */
4260         DisplayError(_("Error gathering move list: extra board"), 0);
4261         ics_getting_history = H_FALSE;
4262         return;
4263     }
4264
4265    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4266                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4267      /* [HGM] We seem to have switched variant unexpectedly
4268       * Try to guess new variant from board size
4269       */
4270           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4271           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4272           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4273           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4274           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4275           if(!weird) newVariant = VariantNormal;
4276           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4277           /* Get a move list just to see the header, which
4278              will tell us whether this is really bug or zh */
4279           if (ics_getting_history == H_FALSE) {
4280             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4281             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4282             SendToICS(str);
4283           }
4284     }
4285
4286     /* Take action if this is the first board of a new game, or of a
4287        different game than is currently being displayed.  */
4288     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4289         relation == RELATION_ISOLATED_BOARD) {
4290
4291         /* Forget the old game and get the history (if any) of the new one */
4292         if (gameMode != BeginningOfGame) {
4293           Reset(TRUE, TRUE);
4294         }
4295         newGame = TRUE;
4296         if (appData.autoRaiseBoard) BoardToTop();
4297         prevMove = -3;
4298         if (gamenum == -1) {
4299             newGameMode = IcsIdle;
4300         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4301                    appData.getMoveList && !reqFlag) {
4302             /* Need to get game history */
4303             ics_getting_history = H_REQUESTED;
4304             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4305             SendToICS(str);
4306         }
4307
4308         /* Initially flip the board to have black on the bottom if playing
4309            black or if the ICS flip flag is set, but let the user change
4310            it with the Flip View button. */
4311         flipView = appData.autoFlipView ?
4312           (newGameMode == IcsPlayingBlack) || ics_flip :
4313           appData.flipView;
4314
4315         /* Done with values from previous mode; copy in new ones */
4316         gameMode = newGameMode;
4317         ModeHighlight();
4318         ics_gamenum = gamenum;
4319         if (gamenum == gs_gamenum) {
4320             int klen = strlen(gs_kind);
4321             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4322             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4323             gameInfo.event = StrSave(str);
4324         } else {
4325             gameInfo.event = StrSave("ICS game");
4326         }
4327         gameInfo.site = StrSave(appData.icsHost);
4328         gameInfo.date = PGNDate();
4329         gameInfo.round = StrSave("-");
4330         gameInfo.white = StrSave(white);
4331         gameInfo.black = StrSave(black);
4332         timeControl = basetime * 60 * 1000;
4333         timeControl_2 = 0;
4334         timeIncrement = increment * 1000;
4335         movesPerSession = 0;
4336         gameInfo.timeControl = TimeControlTagValue();
4337         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4338   if (appData.debugMode) {
4339     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4340     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4341     setbuf(debugFP, NULL);
4342   }
4343
4344         gameInfo.outOfBook = NULL;
4345
4346         /* Do we have the ratings? */
4347         if (strcmp(player1Name, white) == 0 &&
4348             strcmp(player2Name, black) == 0) {
4349             if (appData.debugMode)
4350               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4351                       player1Rating, player2Rating);
4352             gameInfo.whiteRating = player1Rating;
4353             gameInfo.blackRating = player2Rating;
4354         } else if (strcmp(player2Name, white) == 0 &&
4355                    strcmp(player1Name, black) == 0) {
4356             if (appData.debugMode)
4357               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4358                       player2Rating, player1Rating);
4359             gameInfo.whiteRating = player2Rating;
4360             gameInfo.blackRating = player1Rating;
4361         }
4362         player1Name[0] = player2Name[0] = NULLCHAR;
4363
4364         /* Silence shouts if requested */
4365         if (appData.quietPlay &&
4366             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4367             SendToICS(ics_prefix);
4368             SendToICS("set shout 0\n");
4369         }
4370     }
4371
4372     /* Deal with midgame name changes */
4373     if (!newGame) {
4374         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4375             if (gameInfo.white) free(gameInfo.white);
4376             gameInfo.white = StrSave(white);
4377         }
4378         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4379             if (gameInfo.black) free(gameInfo.black);
4380             gameInfo.black = StrSave(black);
4381         }
4382     }
4383
4384     /* Throw away game result if anything actually changes in examine mode */
4385     if (gameMode == IcsExamining && !newGame) {
4386         gameInfo.result = GameUnfinished;
4387         if (gameInfo.resultDetails != NULL) {
4388             free(gameInfo.resultDetails);
4389             gameInfo.resultDetails = NULL;
4390         }
4391     }
4392
4393     /* In pausing && IcsExamining mode, we ignore boards coming
4394        in if they are in a different variation than we are. */
4395     if (pauseExamInvalid) return;
4396     if (pausing && gameMode == IcsExamining) {
4397         if (moveNum <= pauseExamForwardMostMove) {
4398             pauseExamInvalid = TRUE;
4399             forwardMostMove = pauseExamForwardMostMove;
4400             return;
4401         }
4402     }
4403
4404   if (appData.debugMode) {
4405     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4406   }
4407     /* Parse the board */
4408     for (k = 0; k < ranks; k++) {
4409       for (j = 0; j < files; j++)
4410         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4411       if(gameInfo.holdingsWidth > 1) {
4412            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4413            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4414       }
4415     }
4416     if(moveNum==0 && gameInfo.variant == VariantSChess) {
4417       board[5][BOARD_RGHT+1] = WhiteAngel;
4418       board[6][BOARD_RGHT+1] = WhiteMarshall;
4419       board[1][0] = BlackMarshall;
4420       board[2][0] = BlackAngel;
4421       board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1;
4422     }
4423     CopyBoard(boards[moveNum], board);
4424     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4425     if (moveNum == 0) {
4426         startedFromSetupPosition =
4427           !CompareBoards(board, initialPosition);
4428         if(startedFromSetupPosition)
4429             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4430     }
4431
4432     /* [HGM] Set castling rights. Take the outermost Rooks,
4433        to make it also work for FRC opening positions. Note that board12
4434        is really defective for later FRC positions, as it has no way to
4435        indicate which Rook can castle if they are on the same side of King.
4436        For the initial position we grant rights to the outermost Rooks,
4437        and remember thos rights, and we then copy them on positions
4438        later in an FRC game. This means WB might not recognize castlings with
4439        Rooks that have moved back to their original position as illegal,
4440        but in ICS mode that is not its job anyway.
4441     */
4442     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4443     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4444
4445         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4446             if(board[0][i] == WhiteRook) j = i;
4447         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4449             if(board[0][i] == WhiteRook) j = i;
4450         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4451         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4452             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4453         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4454         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4455             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4456         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4457
4458         boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights;
4459         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4460         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4461             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4462         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4463             if(board[BOARD_HEIGHT-1][k] == bKing)
4464                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4465         if(gameInfo.variant == VariantTwoKings) {
4466             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4467             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4468             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4469         }
4470     } else { int r;
4471         r = boards[moveNum][CASTLING][0] = initialRights[0];
4472         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4473         r = boards[moveNum][CASTLING][1] = initialRights[1];
4474         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4475         r = boards[moveNum][CASTLING][3] = initialRights[3];
4476         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4477         r = boards[moveNum][CASTLING][4] = initialRights[4];
4478         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4479         /* wildcastle kludge: always assume King has rights */
4480         r = boards[moveNum][CASTLING][2] = initialRights[2];
4481         r = boards[moveNum][CASTLING][5] = initialRights[5];
4482     }
4483     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4484     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4485
4486
4487     if (ics_getting_history == H_GOT_REQ_HEADER ||
4488         ics_getting_history == H_GOT_UNREQ_HEADER) {
4489         /* This was an initial position from a move list, not
4490            the current position */
4491         return;
4492     }
4493
4494     /* Update currentMove and known move number limits */
4495     newMove = newGame || moveNum > forwardMostMove;
4496
4497     if (newGame) {
4498         forwardMostMove = backwardMostMove = currentMove = moveNum;
4499         if (gameMode == IcsExamining && moveNum == 0) {
4500           /* Workaround for ICS limitation: we are not told the wild
4501              type when starting to examine a game.  But if we ask for
4502              the move list, the move list header will tell us */
4503             ics_getting_history = H_REQUESTED;
4504             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4505             SendToICS(str);
4506         }
4507     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4508                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4509 #if ZIPPY
4510         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4511         /* [HGM] applied this also to an engine that is silently watching        */
4512         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4513             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4514             gameInfo.variant == currentlyInitializedVariant) {
4515           takeback = forwardMostMove - moveNum;
4516           for (i = 0; i < takeback; i++) {
4517             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4518             SendToProgram("undo\n", &first);
4519           }
4520         }
4521 #endif
4522
4523         forwardMostMove = moveNum;
4524         if (!pausing || currentMove > forwardMostMove)
4525           currentMove = forwardMostMove;
4526     } else {
4527         /* New part of history that is not contiguous with old part */
4528         if (pausing && gameMode == IcsExamining) {
4529             pauseExamInvalid = TRUE;
4530             forwardMostMove = pauseExamForwardMostMove;
4531             return;
4532         }
4533         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4534 #if ZIPPY
4535             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4536                 // [HGM] when we will receive the move list we now request, it will be
4537                 // fed to the engine from the first move on. So if the engine is not
4538                 // in the initial position now, bring it there.
4539                 InitChessProgram(&first, 0);
4540             }
4541 #endif
4542             ics_getting_history = H_REQUESTED;
4543             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4544             SendToICS(str);
4545         }
4546         forwardMostMove = backwardMostMove = currentMove = moveNum;
4547     }
4548
4549     /* Update the clocks */
4550     if (strchr(elapsed_time, '.')) {
4551       /* Time is in ms */
4552       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4553       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4554     } else {
4555       /* Time is in seconds */
4556       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4557       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4558     }
4559
4560
4561 #if ZIPPY
4562     if (appData.zippyPlay && newGame &&
4563         gameMode != IcsObserving && gameMode != IcsIdle &&
4564         gameMode != IcsExamining)
4565       ZippyFirstBoard(moveNum, basetime, increment);
4566 #endif
4567
4568     /* Put the move on the move list, first converting
4569        to canonical algebraic form. */
4570     if (moveNum > 0) {
4571   if (appData.debugMode) {
4572     if (appData.debugMode) { int f = forwardMostMove;
4573         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4574                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4575                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4576     }
4577     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4578     fprintf(debugFP, "moveNum = %d\n", moveNum);
4579     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4580     setbuf(debugFP, NULL);
4581   }
4582         if (moveNum <= backwardMostMove) {
4583             /* We don't know what the board looked like before
4584                this move.  Punt. */
4585           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4586             strcat(parseList[moveNum - 1], " ");
4587             strcat(parseList[moveNum - 1], elapsed_time);
4588             moveList[moveNum - 1][0] = NULLCHAR;
4589         } else if (strcmp(move_str, "none") == 0) {
4590             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4591             /* Again, we don't know what the board looked like;
4592                this is really the start of the game. */
4593             parseList[moveNum - 1][0] = NULLCHAR;
4594             moveList[moveNum - 1][0] = NULLCHAR;
4595             backwardMostMove = moveNum;
4596             startedFromSetupPosition = TRUE;
4597             fromX = fromY = toX = toY = -1;
4598         } else {
4599           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4600           //                 So we parse the long-algebraic move string in stead of the SAN move
4601           int valid; char buf[MSG_SIZ], *prom;
4602
4603           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4604                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4605           // str looks something like "Q/a1-a2"; kill the slash
4606           if(str[1] == '/')
4607             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4608           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4609           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4610                 strcat(buf, prom); // long move lacks promo specification!
4611           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4612                 if(appData.debugMode)
4613                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4614                 safeStrCpy(move_str, buf, MSG_SIZ);
4615           }
4616           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4617                                 &fromX, &fromY, &toX, &toY, &promoChar)
4618                || ParseOneMove(buf, moveNum - 1, &moveType,
4619                                 &fromX, &fromY, &toX, &toY, &promoChar);
4620           // end of long SAN patch
4621           if (valid) {
4622             (void) CoordsToAlgebraic(boards[moveNum - 1],
4623                                      PosFlags(moveNum - 1),
4624                                      fromY, fromX, toY, toX, promoChar,
4625                                      parseList[moveNum-1]);
4626             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4627               case MT_NONE:
4628               case MT_STALEMATE:
4629               default:
4630                 break;
4631               case MT_CHECK:
4632                 if(gameInfo.variant != VariantShogi)
4633                     strcat(parseList[moveNum - 1], "+");
4634                 break;
4635               case MT_CHECKMATE:
4636               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4637                 strcat(parseList[moveNum - 1], "#");
4638                 break;
4639             }
4640             strcat(parseList[moveNum - 1], " ");
4641             strcat(parseList[moveNum - 1], elapsed_time);
4642             /* currentMoveString is set as a side-effect of ParseOneMove */
4643             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4644             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4645             strcat(moveList[moveNum - 1], "\n");
4646
4647             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4648                && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board
4649               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4650                 ChessSquare old, new = boards[moveNum][k][j];
4651                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4652                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4653                   if(old == new) continue;
4654                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4655                   else if(new == WhiteWazir || new == BlackWazir) {
4656                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4657                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4658                       else boards[moveNum][k][j] = old; // preserve type of Gold
4659                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4660                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4661               }
4662           } else {
4663             /* Move from ICS was illegal!?  Punt. */
4664             if (appData.debugMode) {
4665               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4666               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4667             }
4668             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4669             strcat(parseList[moveNum - 1], " ");
4670             strcat(parseList[moveNum - 1], elapsed_time);
4671             moveList[moveNum - 1][0] = NULLCHAR;
4672             fromX = fromY = toX = toY = -1;
4673           }
4674         }
4675   if (appData.debugMode) {
4676     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4677     setbuf(debugFP, NULL);
4678   }
4679
4680 #if ZIPPY
4681         /* Send move to chess program (BEFORE animating it). */
4682         if (appData.zippyPlay && !newGame && newMove &&
4683            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4684
4685             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4686                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4687                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4688                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4689                             move_str);
4690                     DisplayError(str, 0);
4691                 } else {
4692                     if (first.sendTime) {
4693                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4694                     }
4695                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4696                     if (firstMove && !bookHit) {
4697                         firstMove = FALSE;
4698                         if (first.useColors) {
4699                           SendToProgram(gameMode == IcsPlayingWhite ?
4700                                         "white\ngo\n" :
4701                                         "black\ngo\n", &first);
4702                         } else {
4703                           SendToProgram("go\n", &first);
4704                         }
4705                         first.maybeThinking = TRUE;
4706                     }
4707                 }
4708             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4709               if (moveList[moveNum - 1][0] == NULLCHAR) {
4710                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4711                 DisplayError(str, 0);
4712               } else {
4713                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4714                 SendMoveToProgram(moveNum - 1, &first);
4715               }
4716             }
4717         }
4718 #endif
4719     }
4720
4721     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4722         /* If move comes from a remote source, animate it.  If it
4723            isn't remote, it will have already been animated. */
4724         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4725             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4726         }
4727         if (!pausing && appData.highlightLastMove) {
4728             SetHighlights(fromX, fromY, toX, toY);
4729         }
4730     }
4731
4732     /* Start the clocks */
4733     whiteFlag = blackFlag = FALSE;
4734     appData.clockMode = !(basetime == 0 && increment == 0);
4735     if (ticking == 0) {
4736       ics_clock_paused = TRUE;
4737       StopClocks();
4738     } else if (ticking == 1) {
4739       ics_clock_paused = FALSE;
4740     }
4741     if (gameMode == IcsIdle ||
4742         relation == RELATION_OBSERVING_STATIC ||
4743         relation == RELATION_EXAMINING ||
4744         ics_clock_paused)
4745       DisplayBothClocks();
4746     else
4747       StartClocks();
4748
4749     /* Display opponents and material strengths */
4750     if (gameInfo.variant != VariantBughouse &&
4751         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4752         if (tinyLayout || smallLayout) {
4753             if(gameInfo.variant == VariantNormal)
4754               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4755                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4756                     basetime, increment);
4757             else
4758               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4759                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4760                     basetime, increment, (int) gameInfo.variant);
4761         } else {
4762             if(gameInfo.variant == VariantNormal)
4763               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}",
4764                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4765                     basetime, increment);
4766             else
4767               snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}",
4768                     gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren,
4769                     basetime, increment, VariantName(gameInfo.variant));
4770         }
4771         DisplayTitle(str);
4772   if (appData.debugMode) {
4773     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4774   }
4775     }
4776
4777
4778     /* Display the board */
4779     if (!pausing && !appData.noGUI) {
4780
4781       if (appData.premove)
4782           if (!gotPremove ||
4783              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4784              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4785               ClearPremoveHighlights();
4786
4787       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4788         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4789       DrawPosition(j, boards[currentMove]);
4790
4791       DisplayMove(moveNum - 1);
4792       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4793             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4794               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4795         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4796       }
4797     }
4798
4799     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4800 #if ZIPPY
4801     if(bookHit) { // [HGM] book: simulate book reply
4802         static char bookMove[MSG_SIZ]; // a bit generous?
4803
4804         programStats.nodes = programStats.depth = programStats.time =
4805         programStats.score = programStats.got_only_move = 0;
4806         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4807
4808         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4809         strcat(bookMove, bookHit);
4810         HandleMachineMove(bookMove, &first);
4811     }
4812 #endif
4813 }
4814
4815 void
4816 GetMoveListEvent ()
4817 {
4818     char buf[MSG_SIZ];
4819     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4820         ics_getting_history = H_REQUESTED;
4821         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4822         SendToICS(buf);
4823     }
4824 }
4825
4826 void
4827 AnalysisPeriodicEvent (int force)
4828 {
4829     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4830          && !force) || !appData.periodicUpdates)
4831       return;
4832
4833     /* Send . command to Crafty to collect stats */
4834     SendToProgram(".\n", &first);
4835
4836     /* Don't send another until we get a response (this makes
4837        us stop sending to old Crafty's which don't understand
4838        the "." command (sending illegal cmds resets node count & time,
4839        which looks bad)) */
4840     programStats.ok_to_send = 0;
4841 }
4842
4843 void
4844 ics_update_width (int new_width)
4845 {
4846         ics_printf("set width %d\n", new_width);
4847 }
4848
4849 void
4850 SendMoveToProgram (int moveNum, ChessProgramState *cps)
4851 {
4852     char buf[MSG_SIZ];
4853
4854     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
4855         // null move in variant where engine does not understand it (for analysis purposes)
4856         SendBoard(cps, moveNum + 1); // send position after move in stead.
4857         return;
4858     }
4859     if (cps->useUsermove) {
4860       SendToProgram("usermove ", cps);
4861     }
4862     if (cps->useSAN) {
4863       char *space;
4864       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4865         int len = space - parseList[moveNum];
4866         memcpy(buf, parseList[moveNum], len);
4867         buf[len++] = '\n';
4868         buf[len] = NULLCHAR;
4869       } else {
4870         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4871       }
4872       SendToProgram(buf, cps);
4873     } else {
4874       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4875         AlphaRank(moveList[moveNum], 4);
4876         SendToProgram(moveList[moveNum], cps);
4877         AlphaRank(moveList[moveNum], 4); // and back
4878       } else
4879       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4880        * the engine. It would be nice to have a better way to identify castle
4881        * moves here. */
4882       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4883                                                                          && cps->useOOCastle) {
4884         int fromX = moveList[moveNum][0] - AAA;
4885         int fromY = moveList[moveNum][1] - ONE;
4886         int toX = moveList[moveNum][2] - AAA;
4887         int toY = moveList[moveNum][3] - ONE;
4888         if((boards[moveNum][fromY][fromX] == WhiteKing
4889             && boards[moveNum][toY][toX] == WhiteRook)
4890            || (boards[moveNum][fromY][fromX] == BlackKing
4891                && boards[moveNum][toY][toX] == BlackRook)) {
4892           if(toX > fromX) SendToProgram("O-O\n", cps);
4893           else SendToProgram("O-O-O\n", cps);
4894         }
4895         else SendToProgram(moveList[moveNum], cps);
4896       } else
4897       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4898         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4899           if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
4900           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4901                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4902         } else
4903           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4904                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4905         SendToProgram(buf, cps);
4906       }
4907       else SendToProgram(moveList[moveNum], cps);
4908       /* End of additions by Tord */
4909     }
4910
4911     /* [HGM] setting up the opening has brought engine in force mode! */
4912     /*       Send 'go' if we are in a mode where machine should play. */
4913     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4914         (gameMode == TwoMachinesPlay   ||
4915 #if ZIPPY
4916          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4917 #endif
4918          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4919         SendToProgram("go\n", cps);
4920   if (appData.debugMode) {
4921     fprintf(debugFP, "(extra)\n");
4922   }
4923     }
4924     setboardSpoiledMachineBlack = 0;
4925 }
4926
4927 void
4928 SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)
4929 {
4930     char user_move[MSG_SIZ];
4931     char suffix[4];
4932
4933     if(gameInfo.variant == VariantSChess && promoChar) {
4934         snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar));
4935         if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating
4936     } else suffix[0] = NULLCHAR;
4937
4938     switch (moveType) {
4939       default:
4940         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4941                 (int)moveType, fromX, fromY, toX, toY);
4942         DisplayError(user_move + strlen("say "), 0);
4943         break;
4944       case WhiteKingSideCastle:
4945       case BlackKingSideCastle:
4946       case WhiteQueenSideCastleWild:
4947       case BlackQueenSideCastleWild:
4948       /* PUSH Fabien */
4949       case WhiteHSideCastleFR:
4950       case BlackHSideCastleFR:
4951       /* POP Fabien */
4952         snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix);
4953         break;
4954       case WhiteQueenSideCastle:
4955       case BlackQueenSideCastle:
4956       case WhiteKingSideCastleWild:
4957       case BlackKingSideCastleWild:
4958       /* PUSH Fabien */
4959       case WhiteASideCastleFR:
4960       case BlackASideCastleFR:
4961       /* POP Fabien */
4962         snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix);
4963         break;
4964       case WhiteNonPromotion:
4965       case BlackNonPromotion:
4966         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4967         break;
4968       case WhitePromotion:
4969       case BlackPromotion:
4970         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4971           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4972                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4973                 PieceToChar(WhiteFerz));
4974         else if(gameInfo.variant == VariantGreat)
4975           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4976                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4977                 PieceToChar(WhiteMan));
4978         else
4979           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4980                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4981                 promoChar);
4982         break;
4983       case WhiteDrop:
4984       case BlackDrop:
4985       drop:
4986         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4987                  ToUpper(PieceToChar((ChessSquare) fromX)),
4988                  AAA + toX, ONE + toY);
4989         break;
4990       case IllegalMove:  /* could be a variant we don't quite understand */
4991         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4992       case NormalMove:
4993       case WhiteCapturesEnPassant:
4994       case BlackCapturesEnPassant:
4995         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4997         break;
4998     }
4999     SendToICS(user_move);
5000     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5001         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5002 }
5003
5004 void
5005 UploadGameEvent ()
5006 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5007     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5008     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5009     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5010       DisplayError(_("You cannot do this while you are playing or observing"), 0);
5011       return;
5012     }
5013     if(gameMode != IcsExamining) { // is this ever not the case?
5014         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5015
5016         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5017           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5018         } else { // on FICS we must first go to general examine mode
5019           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5020         }
5021         if(gameInfo.variant != VariantNormal) {
5022             // try figure out wild number, as xboard names are not always valid on ICS
5023             for(i=1; i<=36; i++) {
5024               snprintf(buf, MSG_SIZ, "wild/%d", i);
5025                 if(StringToVariant(buf) == gameInfo.variant) break;
5026             }
5027             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5028             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5029             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5030         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5031         SendToICS(ics_prefix);
5032         SendToICS(buf);
5033         if(startedFromSetupPosition || backwardMostMove != 0) {
5034           fen = PositionToFEN(backwardMostMove, NULL);
5035           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5036             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5037             SendToICS(buf);
5038           } else { // FICS: everything has to set by separate bsetup commands
5039             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5040             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5041             SendToICS(buf);
5042             if(!WhiteOnMove(backwardMostMove)) {
5043                 SendToICS("bsetup tomove black\n");
5044             }
5045             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5046             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5047             SendToICS(buf);
5048             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5049             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5050             SendToICS(buf);
5051             i = boards[backwardMostMove][EP_STATUS];
5052             if(i >= 0) { // set e.p.
5053               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5054                 SendToICS(buf);
5055             }
5056             bsetup++;
5057           }
5058         }
5059       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5060             SendToICS("bsetup done\n"); // switch to normal examining.
5061     }
5062     for(i = backwardMostMove; i<last; i++) {
5063         char buf[20];
5064         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5065         SendToICS(buf);
5066     }
5067     SendToICS(ics_prefix);
5068     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5069 }
5070
5071 void
5072 CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
5073 {
5074     if (rf == DROP_RANK) {
5075       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
5076       sprintf(move, "%c@%c%c\n",
5077                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5078     } else {
5079         if (promoChar == 'x' || promoChar == NULLCHAR) {
5080           sprintf(move, "%c%c%c%c\n",
5081                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5082         } else {
5083             sprintf(move, "%c%c%c%c%c\n",
5084                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5085         }
5086     }
5087 }
5088
5089 void
5090 ProcessICSInitScript (FILE *f)
5091 {
5092     char buf[MSG_SIZ];
5093
5094     while (fgets(buf, MSG_SIZ, f)) {
5095         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5096     }
5097
5098     fclose(f);
5099 }
5100
5101
5102 static int lastX, lastY, selectFlag, dragging;
5103
5104 void
5105 Sweep (int step)
5106 {
5107     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5108     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5109     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5110     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5111     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5112     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5113     do {
5114         promoSweep -= step;
5115         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5116         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5117         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5118         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5119         if(!step) step = -1;
5120     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5121             appData.testLegality && (promoSweep == king ||
5122             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5123     ChangeDragPiece(promoSweep);
5124 }
5125
5126 int
5127 PromoScroll (int x, int y)
5128 {
5129   int step = 0;
5130
5131   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5132   if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE;
5133   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5134   if(!step) return FALSE;
5135   lastX = x; lastY = y;
5136   if((promoSweep < BlackPawn) == flipView) step = -step;
5137   if(step > 0) selectFlag = 1;
5138   if(!selectFlag) Sweep(step);
5139   return FALSE;
5140 }
5141
5142 void
5143 NextPiece (int step)
5144 {
5145     ChessSquare piece = boards[currentMove][toY][toX];
5146     do {
5147         pieceSweep -= step;
5148         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5149         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5150         if(!step) step = -1;
5151     } while(PieceToChar(pieceSweep) == '.');
5152     boards[currentMove][toY][toX] = pieceSweep;
5153     DrawPosition(FALSE, boards[currentMove]);
5154     boards[currentMove][toY][toX] = piece;
5155 }
5156 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5157 void
5158 AlphaRank (char *move, int n)
5159 {
5160 //    char *p = move, c; int x, y;
5161
5162     if (appData.debugMode) {
5163         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5164     }
5165
5166     if(move[1]=='*' &&
5167        move[2]>='0' && move[2]<='9' &&
5168        move[3]>='a' && move[3]<='x'    ) {
5169         move[1] = '@';
5170         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5171         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5172     } else
5173     if(move[0]>='0' && move[0]<='9' &&
5174        move[1]>='a' && move[1]<='x' &&
5175        move[2]>='0' && move[2]<='9' &&
5176        move[3]>='a' && move[3]<='x'    ) {
5177         /* input move, Shogi -> normal */
5178         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5179         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5180         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5182     } else
5183     if(move[1]=='@' &&
5184        move[3]>='0' && move[3]<='9' &&
5185        move[2]>='a' && move[2]<='x'    ) {
5186         move[1] = '*';
5187         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5188         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5189     } else
5190     if(
5191        move[0]>='a' && move[0]<='x' &&
5192        move[3]>='0' && move[3]<='9' &&
5193        move[2]>='a' && move[2]<='x'    ) {
5194          /* output move, normal -> Shogi */
5195         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5196         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5197         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5198         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5199         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5200     }
5201     if (appData.debugMode) {
5202         fprintf(debugFP, "   out = '%s'\n", move);
5203     }
5204 }
5205
5206 char yy_textstr[8000];
5207
5208 /* Parser for moves from gnuchess, ICS, or user typein box */
5209 Boolean
5210 ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar)
5211 {
5212     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5213
5214     switch (*moveType) {
5215       case WhitePromotion:
5216       case BlackPromotion:
5217       case WhiteNonPromotion:
5218       case BlackNonPromotion:
5219       case NormalMove:
5220       case WhiteCapturesEnPassant:
5221       case BlackCapturesEnPassant:
5222       case WhiteKingSideCastle:
5223       case WhiteQueenSideCastle:
5224       case BlackKingSideCastle:
5225       case BlackQueenSideCastle:
5226       case WhiteKingSideCastleWild:
5227       case WhiteQueenSideCastleWild:
5228       case BlackKingSideCastleWild:
5229       case BlackQueenSideCastleWild:
5230       /* Code added by Tord: */
5231       case WhiteHSideCastleFR:
5232       case WhiteASideCastleFR:
5233       case BlackHSideCastleFR:
5234       case BlackASideCastleFR:
5235       /* End of code added by Tord */
5236       case IllegalMove:         /* bug or odd chess variant */
5237         *fromX = currentMoveString[0] - AAA;
5238         *fromY = currentMoveString[1] - ONE;
5239         *toX = currentMoveString[2] - AAA;
5240         *toY = currentMoveString[3] - ONE;
5241         *promoChar = currentMoveString[4];
5242         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5243             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5244     if (appData.debugMode) {
5245         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5246     }
5247             *fromX = *fromY = *toX = *toY = 0;
5248             return FALSE;
5249         }
5250         if (appData.testLegality) {
5251           return (*moveType != IllegalMove);
5252         } else {
5253           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5254                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5255         }
5256
5257       case WhiteDrop:
5258       case BlackDrop:
5259         *fromX = *moveType == WhiteDrop ?
5260           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5261           (int) CharToPiece(ToLower(currentMoveString[0]));
5262         *fromY = DROP_RANK;
5263         *toX = currentMoveString[2] - AAA;
5264         *toY = currentMoveString[3] - ONE;
5265         *promoChar = NULLCHAR;
5266         return TRUE;
5267
5268       case AmbiguousMove:
5269       case ImpossibleMove:
5270       case EndOfFile:
5271       case ElapsedTime:
5272       case Comment:
5273       case PGNTag:
5274       case NAG:
5275       case WhiteWins:
5276       case BlackWins:
5277       case GameIsDrawn:
5278       default:
5279     if (appData.debugMode) {
5280         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5281     }
5282         /* bug? */
5283         *fromX = *fromY = *toX = *toY = 0;
5284         *promoChar = NULLCHAR;
5285         return FALSE;
5286     }
5287 }
5288
5289 Boolean pushed = FALSE;
5290 char *lastParseAttempt;
5291
5292 void
5293 ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
5294 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5295   int fromX, fromY, toX, toY; char promoChar;
5296   ChessMove moveType;
5297   Boolean valid;
5298   int nr = 0;
5299
5300   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5301     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5302     pushed = TRUE;
5303   }
5304   endPV = forwardMostMove;
5305   do {
5306     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5307     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5308     lastParseAttempt = pv;
5309     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5310 if(appData.debugMode){
5311 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);
5312 }
5313     if(!valid && nr == 0 &&
5314        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5315         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5316         // Hande case where played move is different from leading PV move
5317         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5318         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5319         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5320         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5321           endPV += 2; // if position different, keep this
5322           moveList[endPV-1][0] = fromX + AAA;
5323           moveList[endPV-1][1] = fromY + ONE;
5324           moveList[endPV-1][2] = toX + AAA;
5325           moveList[endPV-1][3] = toY + ONE;
5326           parseList[endPV-1][0] = NULLCHAR;
5327           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5328         }
5329       }
5330     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5331     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5332     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5333     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5334         valid++; // allow comments in PV
5335         continue;
5336     }
5337     nr++;
5338     if(endPV+1 > framePtr) break; // no space, truncate
5339     if(!valid) break;
5340     endPV++;
5341     CopyBoard(boards[endPV], boards[endPV-1]);
5342     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5343     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5344     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5345     CoordsToAlgebraic(boards[endPV - 1],
5346                              PosFlags(endPV - 1),
5347                              fromY, fromX, toY, toX, promoChar,
5348                              parseList[endPV - 1]);
5349   } while(valid);
5350   if(atEnd == 2) return; // used hidden, for PV conversion
5351   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5352   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5353   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5354                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5355   DrawPosition(TRUE, boards[currentMove]);
5356 }
5357
5358 int
5359 MultiPV (ChessProgramState *cps)
5360 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5361         int i;
5362         for(i=0; i<cps->nrOptions; i++)
5363             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5364                 return i;
5365         return -1;
5366 }
5367
5368 Boolean
5369 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
5370 {
5371         int startPV, multi, lineStart, origIndex = index;
5372         char *p, buf2[MSG_SIZ];
5373
5374         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5375         lastX = x; lastY = y;
5376         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5377         lineStart = startPV = index;
5378         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5379         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5380         index = startPV;
5381         do{ while(buf[index] && buf[index] != '\n') index++;
5382         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5383         buf[index] = 0;
5384         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5385                 int n = first.option[multi].value;
5386                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5387                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5388                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5389                 first.option[multi].value = n;
5390                 *start = *end = 0;
5391                 return FALSE;
5392         }
5393         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5394         *start = startPV; *end = index-1;
5395         return TRUE;
5396 }
5397
5398 char *
5399 PvToSAN (char *pv)
5400 {
5401         static char buf[10*MSG_SIZ];
5402         int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
5403         *buf = NULLCHAR;
5404         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5405         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5406         for(i = forwardMostMove; i<endPV; i++){
5407             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5408             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5409             k += strlen(buf+k);
5410         }
5411         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5412         if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
5413         endPV = savedEnd;
5414         return buf;
5415 }
5416
5417 Boolean
5418 LoadPV (int x, int y)
5419 { // called on right mouse click to load PV
5420   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5421   lastX = x; lastY = y;
5422   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5423   return TRUE;
5424 }
5425
5426 void
5427 UnLoadPV ()
5428 {
5429   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5430   if(endPV < 0) return;
5431   endPV = -1;
5432   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5433         Boolean saveAnimate = appData.animate;
5434         if(pushed) {
5435             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5436                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5437             } else storedGames--; // abandon shelved tail of original game
5438         }
5439         pushed = FALSE;
5440         forwardMostMove = currentMove;
5441         currentMove = oldFMM;
5442         appData.animate = FALSE;
5443         ToNrEvent(forwardMostMove);
5444         appData.animate = saveAnimate;
5445   }
5446   currentMove = forwardMostMove;
5447   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5448   ClearPremoveHighlights();
5449   DrawPosition(TRUE, boards[currentMove]);
5450 }
5451
5452 void
5453 MovePV (int x, int y, int h)
5454 { // step through PV based on mouse coordinates (called on mouse move)
5455   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
5456
5457   // we must somehow check if right button is still down (might be released off board!)
5458   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5459   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
5460   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5461   if(!step) return;
5462   lastX = x; lastY = y;
5463
5464   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5465   if(endPV < 0) return;
5466   if(y < margin) step = 1; else
5467   if(y > h - margin) step = -1;
5468   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5469   currentMove += step;
5470   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5471   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5472                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5473   DrawPosition(FALSE, boards[currentMove]);
5474 }
5475
5476
5477 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5478 // All positions will have equal probability, but the current method will not provide a unique
5479 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5480 #define DARK 1
5481 #define LITE 2
5482 #define ANY 3
5483
5484 int squaresLeft[4];
5485 int piecesLeft[(int)BlackPawn];
5486 int seed, nrOfShuffles;
5487
5488 void
5489 GetPositionNumber ()
5490 {       // sets global variable seed
5491         int i;
5492
5493         seed = appData.defaultFrcPosition;
5494         if(seed < 0) { // randomize based on time for negative FRC position numbers
5495                 for(i=0; i<50; i++) seed += random();
5496                 seed = random() ^ random() >> 8 ^ random() << 8;
5497                 if(seed<0) seed = -seed;
5498         }
5499 }
5500
5501 int
5502 put (Board board, int pieceType, int rank, int n, int shade)
5503 // put the piece on the (n-1)-th empty squares of the given shade
5504 {
5505         int i;
5506
5507         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5508                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5509                         board[rank][i] = (ChessSquare) pieceType;
5510                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5511                         squaresLeft[ANY]--;
5512                         piecesLeft[pieceType]--;
5513                         return i;
5514                 }
5515         }
5516         return -1;
5517 }
5518
5519
5520 void
5521 AddOnePiece (Board board, int pieceType, int rank, int shade)
5522 // calculate where the next piece goes, (any empty square), and put it there
5523 {
5524         int i;
5525
5526         i = seed % squaresLeft[shade];
5527         nrOfShuffles *= squaresLeft[shade];
5528         seed /= squaresLeft[shade];
5529         put(board, pieceType, rank, i, shade);
5530 }
5531
5532 void
5533 AddTwoPieces (Board board, int pieceType, int rank)
5534 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5535 {
5536         int i, n=squaresLeft[ANY], j=n-1, k;
5537
5538         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5539         i = seed % k;  // pick one
5540         nrOfShuffles *= k;
5541         seed /= k;
5542         while(i >= j) i -= j--;
5543         j = n - 1 - j; i += j;
5544         put(board, pieceType, rank, j, ANY);
5545         put(board, pieceType, rank, i, ANY);
5546 }
5547
5548 void
5549 SetUpShuffle (Board board, int number)
5550 {
5551         int i, p, first=1;
5552
5553         GetPositionNumber(); nrOfShuffles = 1;
5554
5555         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5556         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5557         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5558
5559         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5560
5561         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5562             p = (int) board[0][i];
5563             if(p < (int) BlackPawn) piecesLeft[p] ++;
5564             board[0][i] = EmptySquare;
5565         }
5566
5567         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5568             // shuffles restricted to allow normal castling put KRR first
5569             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5570                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5571             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5572                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5573             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5574                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5575             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5576                 put(board, WhiteRook, 0, 0, ANY);
5577             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5578         }
5579
5580         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5581             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5582             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5583                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5584                 while(piecesLeft[p] >= 2) {
5585                     AddOnePiece(board, p, 0, LITE);
5586                     AddOnePiece(board, p, 0, DARK);
5587                 }
5588                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5589             }
5590
5591         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5592             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5593             // but we leave King and Rooks for last, to possibly obey FRC restriction
5594             if(p == (int)WhiteRook) continue;
5595             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5596             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5597         }
5598
5599         // now everything is placed, except perhaps King (Unicorn) and Rooks
5600
5601         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5602             // Last King gets castling rights
5603             while(piecesLeft[(int)WhiteUnicorn]) {
5604                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5605                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5606             }
5607
5608             while(piecesLeft[(int)WhiteKing]) {
5609                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5610                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5611             }
5612
5613
5614         } else {
5615             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5616             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5617         }
5618
5619         // Only Rooks can be left; simply place them all
5620         while(piecesLeft[(int)WhiteRook]) {
5621                 i = put(board, WhiteRook, 0, 0, ANY);
5622                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5623                         if(first) {
5624                                 first=0;
5625                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5626                         }
5627                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5628                 }
5629         }
5630         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5631             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5632         }
5633
5634         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5635 }
5636
5637 int
5638 SetCharTable (char *table, const char * map)
5639 /* [HGM] moved here from winboard.c because of its general usefulness */
5640 /*       Basically a safe strcpy that uses the last character as King */
5641 {
5642     int result = FALSE; int NrPieces;
5643
5644     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5645                     && NrPieces >= 12 && !(NrPieces&1)) {
5646         int i; /* [HGM] Accept even length from 12 to 34 */
5647
5648         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5649         for( i=0; i<NrPieces/2-1; i++ ) {
5650             table[i] = map[i];
5651             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5652         }
5653         table[(int) WhiteKing]  = map[NrPieces/2-1];
5654         table[(int) BlackKing]  = map[NrPieces-1];
5655
5656         result = TRUE;
5657     }
5658
5659     return result;
5660 }
5661
5662 void
5663 Prelude (Board board)
5664 {       // [HGM] superchess: random selection of exo-pieces
5665         int i, j, k; ChessSquare p;
5666         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5667
5668         GetPositionNumber(); // use FRC position number
5669
5670         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5671             SetCharTable(pieceToChar, appData.pieceToCharTable);
5672             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5673                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5674         }
5675
5676         j = seed%4;                 seed /= 4;
5677         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5678         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5679         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5680         j = seed%3 + (seed%3 >= j); seed /= 3;
5681         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5682         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5683         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5684         j = seed%3;                 seed /= 3;
5685         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%2 + (seed%2 >= j); seed /= 2;
5689         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5690         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5691         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5692         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5693         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5694         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5695         put(board, exoPieces[0],    0, 0, ANY);
5696         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5697 }
5698
5699 void
5700 InitPosition (int redraw)
5701 {
5702     ChessSquare (* pieces)[BOARD_FILES];
5703     int i, j, pawnRow, overrule,
5704     oldx = gameInfo.boardWidth,
5705     oldy = gameInfo.boardHeight,
5706     oldh = gameInfo.holdingsWidth;
5707     static int oldv;
5708
5709     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5710
5711     /* [AS] Initialize pv info list [HGM] and game status */
5712     {
5713         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5714             pvInfoList[i].depth = 0;
5715             boards[i][EP_STATUS] = EP_NONE;
5716             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5717         }
5718
5719         initialRulePlies = 0; /* 50-move counter start */
5720
5721         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5722         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5723     }
5724
5725
5726     /* [HGM] logic here is completely changed. In stead of full positions */
5727     /* the initialized data only consist of the two backranks. The switch */
5728     /* selects which one we will use, which is than copied to the Board   */
5729     /* initialPosition, which for the rest is initialized by Pawns and    */
5730     /* empty squares. This initial position is then copied to boards[0],  */
5731     /* possibly after shuffling, so that it remains available.            */
5732
5733     gameInfo.holdingsWidth = 0; /* default board sizes */
5734     gameInfo.boardWidth    = 8;
5735     gameInfo.boardHeight   = 8;
5736     gameInfo.holdingsSize  = 0;
5737     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5738     for(i=0; i<BOARD_FILES-2; i++)
5739       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5740     initialPosition[EP_STATUS] = EP_NONE;
5741     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5742     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5743          SetCharTable(pieceNickName, appData.pieceNickNames);
5744     else SetCharTable(pieceNickName, "............");
5745     pieces = FIDEArray;
5746
5747     switch (gameInfo.variant) {
5748     case VariantFischeRandom:
5749       shuffleOpenings = TRUE;
5750     default:
5751       break;
5752     case VariantShatranj:
5753       pieces = ShatranjArray;
5754       nrCastlingRights = 0;
5755       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5756       break;
5757     case VariantMakruk:
5758       pieces = makrukArray;
5759       nrCastlingRights = 0;
5760       startedFromSetupPosition = TRUE;
5761       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5762       break;
5763     case VariantTwoKings:
5764       pieces = twoKingsArray;
5765       break;
5766     case VariantGrand:
5767       pieces = GrandArray;
5768       nrCastlingRights = 0;
5769       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5770       gameInfo.boardWidth = 10;
5771       gameInfo.boardHeight = 10;
5772       gameInfo.holdingsSize = 7;
5773       break;
5774     case VariantCapaRandom:
5775       shuffleOpenings = TRUE;
5776     case VariantCapablanca:
5777       pieces = CapablancaArray;
5778       gameInfo.boardWidth = 10;
5779       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5780       break;
5781     case VariantGothic:
5782       pieces = GothicArray;
5783       gameInfo.boardWidth = 10;
5784       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5785       break;
5786     case VariantSChess:
5787       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5788       gameInfo.holdingsSize = 7;
5789       break;
5790     case VariantJanus:
5791       pieces = JanusArray;
5792       gameInfo.boardWidth = 10;
5793       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5794       nrCastlingRights = 6;
5795         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5796         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5797         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5798         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5799         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5800         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5801       break;
5802     case VariantFalcon:
5803       pieces = FalconArray;
5804       gameInfo.boardWidth = 10;
5805       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5806       break;
5807     case VariantXiangqi:
5808       pieces = XiangqiArray;
5809       gameInfo.boardWidth  = 9;
5810       gameInfo.boardHeight = 10;
5811       nrCastlingRights = 0;
5812       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5813       break;
5814     case VariantShogi:
5815       pieces = ShogiArray;
5816       gameInfo.boardWidth  = 9;
5817       gameInfo.boardHeight = 9;
5818       gameInfo.holdingsSize = 7;
5819       nrCastlingRights = 0;
5820       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5821       break;
5822     case VariantCourier:
5823       pieces = CourierArray;
5824       gameInfo.boardWidth  = 12;
5825       nrCastlingRights = 0;
5826       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5827       break;
5828     case VariantKnightmate:
5829       pieces = KnightmateArray;
5830       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5831       break;
5832     case VariantSpartan:
5833       pieces = SpartanArray;
5834       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5835       break;
5836     case VariantFairy:
5837       pieces = fairyArray;
5838       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5839       break;
5840     case VariantGreat:
5841       pieces = GreatArray;
5842       gameInfo.boardWidth = 10;
5843       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5844       gameInfo.holdingsSize = 8;
5845       break;
5846     case VariantSuper:
5847       pieces = FIDEArray;
5848       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5849       gameInfo.holdingsSize = 8;
5850       startedFromSetupPosition = TRUE;
5851       break;
5852     case VariantCrazyhouse:
5853     case VariantBughouse:
5854       pieces = FIDEArray;
5855       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5856       gameInfo.holdingsSize = 5;
5857       break;
5858     case VariantWildCastle:
5859       pieces = FIDEArray;
5860       /* !!?shuffle with kings guaranteed to be on d or e file */
5861       shuffleOpenings = 1;
5862       break;
5863     case VariantNoCastle:
5864       pieces = FIDEArray;
5865       nrCastlingRights = 0;
5866       /* !!?unconstrained back-rank shuffle */
5867       shuffleOpenings = 1;
5868       break;
5869     }
5870
5871     overrule = 0;
5872     if(appData.NrFiles >= 0) {
5873         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5874         gameInfo.boardWidth = appData.NrFiles;
5875     }
5876     if(appData.NrRanks >= 0) {
5877         gameInfo.boardHeight = appData.NrRanks;
5878     }
5879     if(appData.holdingsSize >= 0) {
5880         i = appData.holdingsSize;
5881         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5882         gameInfo.holdingsSize = i;
5883     }
5884     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5885     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5886         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5887
5888     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5889     if(pawnRow < 1) pawnRow = 1;
5890     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5891
5892     /* User pieceToChar list overrules defaults */
5893     if(appData.pieceToCharTable != NULL)
5894         SetCharTable(pieceToChar, appData.pieceToCharTable);
5895
5896     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5897
5898         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5899             s = (ChessSquare) 0; /* account holding counts in guard band */
5900         for( i=0; i<BOARD_HEIGHT; i++ )
5901             initialPosition[i][j] = s;
5902
5903         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5904         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5905         initialPosition[pawnRow][j] = WhitePawn;
5906         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5907         if(gameInfo.variant == VariantXiangqi) {
5908             if(j&1) {
5909                 initialPosition[pawnRow][j] =
5910                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5911                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5912                    initialPosition[2][j] = WhiteCannon;
5913                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5914                 }
5915             }
5916         }
5917         if(gameInfo.variant == VariantGrand) {
5918             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5919                initialPosition[0][j] = WhiteRook;
5920                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5921             }
5922         }
5923         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5924     }
5925     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5926
5927             j=BOARD_LEFT+1;
5928             initialPosition[1][j] = WhiteBishop;
5929             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5930             j=BOARD_RGHT-2;
5931             initialPosition[1][j] = WhiteRook;
5932             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5933     }
5934
5935     if( nrCastlingRights == -1) {
5936         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5937         /*       This sets default castling rights from none to normal corners   */
5938         /* Variants with other castling rights must set them themselves above    */
5939         nrCastlingRights = 6;
5940
5941         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5942         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5943         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5944         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5945         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5946         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5947      }
5948
5949      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5950      if(gameInfo.variant == VariantGreat) { // promotion commoners
5951         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5952         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5953         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5954         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5955      }
5956      if( gameInfo.variant == VariantSChess ) {
5957       initialPosition[1][0] = BlackMarshall;
5958       initialPosition[2][0] = BlackAngel;
5959       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5960       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5961       initialPosition[1][1] = initialPosition[2][1] = 
5962       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5963      }
5964   if (appData.debugMode) {
5965     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5966   }
5967     if(shuffleOpenings) {
5968         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5969         startedFromSetupPosition = TRUE;
5970     }
5971     if(startedFromPositionFile) {
5972       /* [HGM] loadPos: use PositionFile for every new game */
5973       CopyBoard(initialPosition, filePosition);
5974       for(i=0; i<nrCastlingRights; i++)
5975           initialRights[i] = filePosition[CASTLING][i];
5976       startedFromSetupPosition = TRUE;
5977     }
5978
5979     CopyBoard(boards[0], initialPosition);
5980
5981     if(oldx != gameInfo.boardWidth ||
5982        oldy != gameInfo.boardHeight ||
5983        oldv != gameInfo.variant ||
5984        oldh != gameInfo.holdingsWidth
5985                                          )
5986             InitDrawingSizes(-2 ,0);
5987
5988     oldv = gameInfo.variant;
5989     if (redraw)
5990       DrawPosition(TRUE, boards[currentMove]);
5991 }
5992
5993 void
5994 SendBoard (ChessProgramState *cps, int moveNum)
5995 {
5996     char message[MSG_SIZ];
5997
5998     if (cps->useSetboard) {
5999       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6000       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6001       SendToProgram(message, cps);
6002       free(fen);
6003
6004     } else {
6005       ChessSquare *bp;
6006       int i, j, left=0, right=BOARD_WIDTH;
6007       /* Kludge to set black to move, avoiding the troublesome and now
6008        * deprecated "black" command.
6009        */
6010       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6011         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6012
6013       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
6014
6015       SendToProgram("edit\n", cps);
6016       SendToProgram("#\n", cps);
6017       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6018         bp = &boards[moveNum][i][left];
6019         for (j = left; j < right; j++, bp++) {
6020           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6021           if ((int) *bp < (int) BlackPawn) {
6022             if(j == BOARD_RGHT+1)
6023                  snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
6024             else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
6025             if(message[0] == '+' || message[0] == '~') {
6026               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6027                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6028                         AAA + j, ONE + i);
6029             }
6030             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6031                 message[1] = BOARD_RGHT   - 1 - j + '1';
6032                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6033             }
6034             SendToProgram(message, cps);
6035           }
6036         }
6037       }
6038
6039       SendToProgram("c\n", cps);
6040       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6041         bp = &boards[moveNum][i][left];
6042         for (j = left; j < right; j++, bp++) {
6043           if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue;
6044           if (((int) *bp != (int) EmptySquare)
6045               && ((int) *bp >= (int) BlackPawn)) {
6046             if(j == BOARD_LEFT-2)
6047                  snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
6048             else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6049                     AAA + j, ONE + i);
6050             if(message[0] == '+' || message[0] == '~') {
6051               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6052                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6053                         AAA + j, ONE + i);
6054             }
6055             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6056                 message[1] = BOARD_RGHT   - 1 - j + '1';
6057                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6058             }
6059             SendToProgram(message, cps);
6060           }
6061         }
6062       }
6063
6064       SendToProgram(".\n", cps);
6065     }
6066     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6067 }
6068
6069 ChessSquare
6070 DefaultPromoChoice (int white)
6071 {
6072     ChessSquare result;
6073     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6074         result = WhiteFerz; // no choice
6075     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6076         result= WhiteKing; // in Suicide Q is the last thing we want
6077     else if(gameInfo.variant == VariantSpartan)
6078         result = white ? WhiteQueen : WhiteAngel;
6079     else result = WhiteQueen;
6080     if(!white) result = WHITE_TO_BLACK result;
6081     return result;
6082 }
6083
6084 static int autoQueen; // [HGM] oneclick
6085
6086 int
6087 HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6088 {
6089     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6090     /* [HGM] add Shogi promotions */
6091     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6092     ChessSquare piece;
6093     ChessMove moveType;
6094     Boolean premove;
6095
6096     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6097     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6098
6099     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6100       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6101         return FALSE;
6102
6103     piece = boards[currentMove][fromY][fromX];
6104     if(gameInfo.variant == VariantShogi) {
6105         promotionZoneSize = BOARD_HEIGHT/3;
6106         highestPromotingPiece = (int)WhiteFerz;
6107     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6108         promotionZoneSize = 3;
6109     }
6110
6111     // Treat Lance as Pawn when it is not representing Amazon
6112     if(gameInfo.variant != VariantSuper) {
6113         if(piece == WhiteLance) piece = WhitePawn; else
6114         if(piece == BlackLance) piece = BlackPawn;
6115     }
6116
6117     // next weed out all moves that do not touch the promotion zone at all
6118     if((int)piece >= BlackPawn) {
6119         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6120              return FALSE;
6121         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6122     } else {
6123         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6124            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6125     }
6126
6127     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6128
6129     // weed out mandatory Shogi promotions
6130     if(gameInfo.variant == VariantShogi) {
6131         if(piece >= BlackPawn) {
6132             if(toY == 0 && piece == BlackPawn ||
6133                toY == 0 && piece == BlackQueen ||
6134                toY <= 1 && piece == BlackKnight) {
6135                 *promoChoice = '+';
6136                 return FALSE;
6137             }
6138         } else {
6139             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6140                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6141                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6142                 *promoChoice = '+';
6143                 return FALSE;
6144             }
6145         }
6146     }
6147
6148     // weed out obviously illegal Pawn moves
6149     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6150         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6151         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6152         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6153         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6154         // note we are not allowed to test for valid (non-)capture, due to premove
6155     }
6156
6157     // we either have a choice what to promote to, or (in Shogi) whether to promote
6158     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6159         *promoChoice = PieceToChar(BlackFerz);  // no choice
6160         return FALSE;
6161     }
6162     // no sense asking what we must promote to if it is going to explode...
6163     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6164         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6165         return FALSE;
6166     }
6167     // give caller the default choice even if we will not make it
6168     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6169     if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6170     if(        sweepSelect && gameInfo.variant != VariantGreat
6171                            && gameInfo.variant != VariantGrand
6172                            && gameInfo.variant != VariantSuper) return FALSE;
6173     if(autoQueen) return FALSE; // predetermined
6174
6175     // suppress promotion popup on illegal moves that are not premoves
6176     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6177               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6178     if(appData.testLegality && !premove) {
6179         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6180                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6181         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6182             return FALSE;
6183     }
6184
6185     return TRUE;
6186 }
6187
6188 int
6189 InPalace (int row, int column)
6190 {   /* [HGM] for Xiangqi */
6191     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6192          column < (BOARD_WIDTH + 4)/2 &&
6193          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6194     return FALSE;
6195 }
6196
6197 int
6198 PieceForSquare (int x, int y)
6199 {
6200   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6201      return -1;
6202   else
6203      return boards[currentMove][y][x];
6204 }
6205
6206 int
6207 OKToStartUserMove (int x, int y)
6208 {
6209     ChessSquare from_piece;
6210     int white_piece;
6211
6212     if (matchMode) return FALSE;
6213     if (gameMode == EditPosition) return TRUE;
6214
6215     if (x >= 0 && y >= 0)
6216       from_piece = boards[currentMove][y][x];
6217     else
6218       from_piece = EmptySquare;
6219
6220     if (from_piece == EmptySquare) return FALSE;
6221
6222     white_piece = (int)from_piece >= (int)WhitePawn &&
6223       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6224
6225     switch (gameMode) {
6226       case AnalyzeFile:
6227       case TwoMachinesPlay:
6228       case EndOfGame:
6229         return FALSE;
6230
6231       case IcsObserving:
6232       case IcsIdle:
6233         return FALSE;
6234
6235       case MachinePlaysWhite:
6236       case IcsPlayingBlack:
6237         if (appData.zippyPlay) return FALSE;
6238         if (white_piece) {
6239             DisplayMoveError(_("You are playing Black"));
6240             return FALSE;
6241         }
6242         break;
6243
6244       case MachinePlaysBlack:
6245       case IcsPlayingWhite:
6246         if (appData.zippyPlay) return FALSE;
6247         if (!white_piece) {
6248             DisplayMoveError(_("You are playing White"));
6249             return FALSE;
6250         }
6251         break;
6252
6253       case PlayFromGameFile:
6254             if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
6255       case EditGame:
6256         if (!white_piece && WhiteOnMove(currentMove)) {
6257             DisplayMoveError(_("It is White's turn"));
6258             return FALSE;
6259         }
6260         if (white_piece && !WhiteOnMove(currentMove)) {
6261             DisplayMoveError(_("It is Black's turn"));
6262             return FALSE;
6263         }
6264         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6265             /* Editing correspondence game history */
6266             /* Could disallow this or prompt for confirmation */
6267             cmailOldMove = -1;
6268         }
6269         break;
6270
6271       case BeginningOfGame:
6272         if (appData.icsActive) return FALSE;
6273         if (!appData.noChessProgram) {
6274             if (!white_piece) {
6275                 DisplayMoveError(_("You are playing White"));
6276                 return FALSE;
6277             }
6278         }
6279         break;
6280
6281       case Training:
6282         if (!white_piece && WhiteOnMove(currentMove)) {
6283             DisplayMoveError(_("It is White's turn"));
6284             return FALSE;
6285         }
6286         if (white_piece && !WhiteOnMove(currentMove)) {
6287             DisplayMoveError(_("It is Black's turn"));
6288             return FALSE;
6289         }
6290         break;
6291
6292       default:
6293       case IcsExamining:
6294         break;
6295     }
6296     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6297         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6298         && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
6299         && gameMode != AnalyzeFile && gameMode != Training) {
6300         DisplayMoveError(_("Displayed position is not current"));
6301         return FALSE;
6302     }
6303     return TRUE;
6304 }
6305
6306 Boolean
6307 OnlyMove (int *x, int *y, Boolean captures) 
6308 {
6309     DisambiguateClosure cl;
6310     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6311     switch(gameMode) {
6312       case MachinePlaysBlack:
6313       case IcsPlayingWhite:
6314       case BeginningOfGame:
6315         if(!WhiteOnMove(currentMove)) return FALSE;
6316         break;
6317       case MachinePlaysWhite:
6318       case IcsPlayingBlack:
6319         if(WhiteOnMove(currentMove)) return FALSE;
6320         break;
6321       case EditGame:
6322         break;
6323       default:
6324         return FALSE;
6325     }
6326     cl.pieceIn = EmptySquare;
6327     cl.rfIn = *y;
6328     cl.ffIn = *x;
6329     cl.rtIn = -1;
6330     cl.ftIn = -1;
6331     cl.promoCharIn = NULLCHAR;
6332     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6333     if( cl.kind == NormalMove ||
6334         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6335         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6336         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6337       fromX = cl.ff;
6338       fromY = cl.rf;
6339       *x = cl.ft;
6340       *y = cl.rt;
6341       return TRUE;
6342     }
6343     if(cl.kind != ImpossibleMove) return FALSE;
6344     cl.pieceIn = EmptySquare;
6345     cl.rfIn = -1;
6346     cl.ffIn = -1;
6347     cl.rtIn = *y;
6348     cl.ftIn = *x;
6349     cl.promoCharIn = NULLCHAR;
6350     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6351     if( cl.kind == NormalMove ||
6352         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6353         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6354         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6355       fromX = cl.ff;
6356       fromY = cl.rf;
6357       *x = cl.ft;
6358       *y = cl.rt;
6359       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6360       return TRUE;
6361     }
6362     return FALSE;
6363 }
6364
6365 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6366 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6367 int lastLoadGameUseList = FALSE;
6368 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6369 ChessMove lastLoadGameStart = EndOfFile;
6370
6371 void
6372 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
6373 {
6374     ChessMove moveType;
6375     ChessSquare pdown, pup;
6376
6377     /* Check if the user is playing in turn.  This is complicated because we
6378        let the user "pick up" a piece before it is his turn.  So the piece he
6379        tried to pick up may have been captured by the time he puts it down!
6380        Therefore we use the color the user is supposed to be playing in this
6381        test, not the color of the piece that is currently on the starting
6382        square---except in EditGame mode, where the user is playing both
6383        sides; fortunately there the capture race can't happen.  (It can
6384        now happen in IcsExamining mode, but that's just too bad.  The user
6385        will get a somewhat confusing message in that case.)
6386        */
6387
6388     switch (gameMode) {
6389       case AnalyzeFile:
6390       case TwoMachinesPlay:
6391       case EndOfGame:
6392       case IcsObserving:
6393       case IcsIdle:
6394         /* We switched into a game mode where moves are not accepted,
6395            perhaps while the mouse button was down. */
6396         return;
6397
6398       case MachinePlaysWhite:
6399         /* User is moving for Black */
6400         if (WhiteOnMove(currentMove)) {
6401             DisplayMoveError(_("It is White's turn"));
6402             return;
6403         }
6404         break;
6405
6406       case MachinePlaysBlack:
6407         /* User is moving for White */
6408         if (!WhiteOnMove(currentMove)) {
6409             DisplayMoveError(_("It is Black's turn"));
6410             return;
6411         }
6412         break;
6413
6414       case PlayFromGameFile:
6415             if(!shiftKey ||!appData.variations) return; // [HGM] only variations
6416       case EditGame:
6417       case IcsExamining:
6418       case BeginningOfGame:
6419       case AnalyzeMode:
6420       case Training:
6421         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6422         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6423             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6424             /* User is moving for Black */
6425             if (WhiteOnMove(currentMove)) {
6426                 DisplayMoveError(_("It is White's turn"));
6427                 return;
6428             }
6429         } else {
6430             /* User is moving for White */
6431             if (!WhiteOnMove(currentMove)) {
6432                 DisplayMoveError(_("It is Black's turn"));
6433                 return;
6434             }
6435         }
6436         break;
6437
6438       case IcsPlayingBlack:
6439         /* User is moving for Black */
6440         if (WhiteOnMove(currentMove)) {
6441             if (!appData.premove) {
6442                 DisplayMoveError(_("It is White's turn"));
6443             } else if (toX >= 0 && toY >= 0) {
6444                 premoveToX = toX;
6445                 premoveToY = toY;
6446                 premoveFromX = fromX;
6447                 premoveFromY = fromY;
6448                 premovePromoChar = promoChar;
6449                 gotPremove = 1;
6450                 if (appData.debugMode)
6451                     fprintf(debugFP, "Got premove: fromX %d,"
6452                             "fromY %d, toX %d, toY %d\n",
6453                             fromX, fromY, toX, toY);
6454             }
6455             return;
6456         }
6457         break;
6458
6459       case IcsPlayingWhite:
6460         /* User is moving for White */
6461         if (!WhiteOnMove(currentMove)) {
6462             if (!appData.premove) {
6463                 DisplayMoveError(_("It is Black's turn"));
6464             } else if (toX >= 0 && toY >= 0) {
6465                 premoveToX = toX;
6466                 premoveToY = toY;
6467                 premoveFromX = fromX;
6468                 premoveFromY = fromY;
6469                 premovePromoChar = promoChar;
6470                 gotPremove = 1;
6471                 if (appData.debugMode)
6472                     fprintf(debugFP, "Got premove: fromX %d,"
6473                             "fromY %d, toX %d, toY %d\n",
6474                             fromX, fromY, toX, toY);
6475             }
6476             return;
6477         }
6478         break;
6479
6480       default:
6481         break;
6482
6483       case EditPosition:
6484         /* EditPosition, empty square, or different color piece;
6485            click-click move is possible */
6486         if (toX == -2 || toY == -2) {
6487             boards[0][fromY][fromX] = EmptySquare;
6488             DrawPosition(FALSE, boards[currentMove]);
6489             return;
6490         } else if (toX >= 0 && toY >= 0) {
6491             boards[0][toY][toX] = boards[0][fromY][fromX];
6492             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6493                 if(boards[0][fromY][0] != EmptySquare) {
6494                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6495                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6496                 }
6497             } else
6498             if(fromX == BOARD_RGHT+1) {
6499                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6500                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6501                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6502                 }
6503             } else
6504             boards[0][fromY][fromX] = EmptySquare;
6505             DrawPosition(FALSE, boards[currentMove]);
6506             return;
6507         }
6508         return;
6509     }
6510
6511     if(toX < 0 || toY < 0) return;
6512     pdown = boards[currentMove][fromY][fromX];
6513     pup = boards[currentMove][toY][toX];
6514
6515     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6516     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6517          if( pup != EmptySquare ) return;
6518          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6519            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6520                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6521            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6522            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6523            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6524            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6525          fromY = DROP_RANK;
6526     }
6527
6528     /* [HGM] always test for legality, to get promotion info */
6529     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6530                                          fromY, fromX, toY, toX, promoChar);
6531
6532     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
6533
6534     /* [HGM] but possibly ignore an IllegalMove result */
6535     if (appData.testLegality) {
6536         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6537             DisplayMoveError(_("Illegal move"));
6538             return;
6539         }
6540     }
6541
6542     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6543 }
6544
6545 /* Common tail of UserMoveEvent and DropMenuEvent */
6546 int
6547 FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar)
6548 {
6549     char *bookHit = 0;
6550
6551     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6552         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6553         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6554         if(WhiteOnMove(currentMove)) {
6555             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6556         } else {
6557             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6558         }
6559     }
6560
6561     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6562        move type in caller when we know the move is a legal promotion */
6563     if(moveType == NormalMove && promoChar)
6564         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6565
6566     /* [HGM] <popupFix> The following if has been moved here from
6567        UserMoveEvent(). Because it seemed to belong here (why not allow
6568        piece drops in training games?), and because it can only be
6569        performed after it is known to what we promote. */
6570     if (gameMode == Training) {
6571       /* compare the move played on the board to the next move in the
6572        * game. If they match, display the move and the opponent's response.
6573        * If they don't match, display an error message.
6574        */
6575       int saveAnimate;
6576       Board testBoard;
6577       CopyBoard(testBoard, boards[currentMove]);
6578       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6579
6580       if (CompareBoards(testBoard, boards[currentMove+1])) {
6581         ForwardInner(currentMove+1);
6582
6583         /* Autoplay the opponent's response.
6584          * if appData.animate was TRUE when Training mode was entered,
6585          * the response will be animated.
6586          */
6587         saveAnimate = appData.animate;
6588         appData.animate = animateTraining;
6589         ForwardInner(currentMove+1);
6590         appData.animate = saveAnimate;
6591
6592         /* check for the end of the game */
6593         if (currentMove >= forwardMostMove) {
6594           gameMode = PlayFromGameFile;
6595           ModeHighlight();
6596           SetTrainingModeOff();
6597           DisplayInformation(_("End of game"));
6598         }
6599       } else {
6600         DisplayError(_("Incorrect move"), 0);
6601       }
6602       return 1;
6603     }
6604
6605   /* Ok, now we know that the move is good, so we can kill
6606      the previous line in Analysis Mode */
6607   if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
6608                                 && currentMove < forwardMostMove) {
6609     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6610     else forwardMostMove = currentMove;
6611   }
6612
6613   /* If we need the chess program but it's dead, restart it */
6614   ResurrectChessProgram();
6615
6616   /* A user move restarts a paused game*/
6617   if (pausing)
6618     PauseEvent();
6619
6620   thinkOutput[0] = NULLCHAR;
6621
6622   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6623
6624   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6625     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6626     return 1;
6627   }
6628
6629   if (gameMode == BeginningOfGame) {
6630     if (appData.noChessProgram) {
6631       gameMode = EditGame;
6632       SetGameInfo();
6633     } else {
6634       char buf[MSG_SIZ];
6635       gameMode = MachinePlaysBlack;
6636       StartClocks();
6637       SetGameInfo();
6638       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
6639       DisplayTitle(buf);
6640       if (first.sendName) {
6641         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6642         SendToProgram(buf, &first);
6643       }
6644       StartClocks();
6645     }
6646     ModeHighlight();
6647   }
6648
6649   /* Relay move to ICS or chess engine */
6650   if (appData.icsActive) {
6651     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6652         gameMode == IcsExamining) {
6653       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6654         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6655         SendToICS("draw ");
6656         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6657       }
6658       // also send plain move, in case ICS does not understand atomic claims
6659       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6660       ics_user_moved = 1;
6661     }
6662   } else {
6663     if (first.sendTime && (gameMode == BeginningOfGame ||
6664                            gameMode == MachinePlaysWhite ||
6665                            gameMode == MachinePlaysBlack)) {
6666       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6667     }
6668     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6669          // [HGM] book: if program might be playing, let it use book
6670         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6671         first.maybeThinking = TRUE;
6672     } else if(fromY == DROP_RANK && fromX == EmptySquare) {
6673         if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard
6674         SendBoard(&first, currentMove+1);
6675     } else SendMoveToProgram(forwardMostMove-1, &first);
6676     if (currentMove == cmailOldMove + 1) {
6677       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6678     }
6679   }
6680
6681   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6682
6683   switch (gameMode) {
6684   case EditGame:
6685     if(appData.testLegality)
6686     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6687     case MT_NONE:
6688     case MT_CHECK:
6689       break;
6690     case MT_CHECKMATE:
6691     case MT_STAINMATE:
6692       if (WhiteOnMove(currentMove)) {
6693         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6694       } else {
6695         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6696       }
6697       break;
6698     case MT_STALEMATE:
6699       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6700       break;
6701     }
6702     break;
6703
6704   case MachinePlaysBlack:
6705   case MachinePlaysWhite:
6706     /* disable certain menu options while machine is thinking */
6707     SetMachineThinkingEnables();
6708     break;
6709
6710   default:
6711     break;
6712   }
6713
6714   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6715   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6716
6717   if(bookHit) { // [HGM] book: simulate book reply
6718         static char bookMove[MSG_SIZ]; // a bit generous?
6719
6720         programStats.nodes = programStats.depth = programStats.time =
6721         programStats.score = programStats.got_only_move = 0;
6722         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6723
6724         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6725         strcat(bookMove, bookHit);
6726         HandleMachineMove(bookMove, &first);
6727   }
6728   return 1;
6729 }
6730
6731 void
6732 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
6733 {
6734     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6735     Markers *m = (Markers *) closure;
6736     if(rf == fromY && ff == fromX)
6737         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6738                          || kind == WhiteCapturesEnPassant
6739                          || kind == BlackCapturesEnPassant);
6740     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6741 }
6742
6743 void
6744 MarkTargetSquares (int clear)
6745 {
6746   int x, y;
6747   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6748      !appData.testLegality || gameMode == EditPosition) return;
6749   if(clear) {
6750     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6751   } else {
6752     int capt = 0;
6753     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6754     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6755       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6756       if(capt)
6757       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6758     }
6759   }
6760   DrawPosition(TRUE, NULL);
6761 }
6762
6763 int
6764 Explode (Board board, int fromX, int fromY, int toX, int toY)
6765 {
6766     if(gameInfo.variant == VariantAtomic &&
6767        (board[toY][toX] != EmptySquare ||                     // capture?
6768         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6769                          board[fromY][fromX] == BlackPawn   )
6770       )) {
6771         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6772         return TRUE;
6773     }
6774     return FALSE;
6775 }
6776
6777 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6778
6779 int
6780 CanPromote (ChessSquare piece, int y)
6781 {
6782         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6783         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6784         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6785            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6786            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6787                                                   gameInfo.variant == VariantMakruk) return FALSE;
6788         return (piece == BlackPawn && y == 1 ||
6789                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6790                 piece == BlackLance && y == 1 ||
6791                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6792 }
6793
6794 void
6795 LeftClick (ClickType clickType, int xPix, int yPix)
6796 {
6797     int x, y;
6798     Boolean saveAnimate;
6799     static int second = 0, promotionChoice = 0, clearFlag = 0;
6800     char promoChoice = NULLCHAR;
6801     ChessSquare piece;
6802
6803     if(appData.seekGraph && appData.icsActive && loggedOn &&
6804         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6805         SeekGraphClick(clickType, xPix, yPix, 0);
6806         return;
6807     }
6808
6809     if (clickType == Press) ErrorPopDown();
6810
6811     x = EventToSquare(xPix, BOARD_WIDTH);
6812     y = EventToSquare(yPix, BOARD_HEIGHT);
6813     if (!flipView && y >= 0) {
6814         y = BOARD_HEIGHT - 1 - y;
6815     }
6816     if (flipView && x >= 0) {
6817         x = BOARD_WIDTH - 1 - x;
6818     }
6819
6820     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6821         defaultPromoChoice = promoSweep;
6822         promoSweep = EmptySquare;   // terminate sweep
6823         promoDefaultAltered = TRUE;
6824         if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6825     }
6826
6827     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6828         if(clickType == Release) return; // ignore upclick of click-click destination
6829         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6830         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6831         if(gameInfo.holdingsWidth &&
6832                 (WhiteOnMove(currentMove)
6833                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6834                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6835             // click in right holdings, for determining promotion piece
6836             ChessSquare p = boards[currentMove][y][x];
6837             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6838             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6839             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6840                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6841                 fromX = fromY = -1;
6842                 return;
6843             }
6844         }
6845         DrawPosition(FALSE, boards[currentMove]);
6846         return;
6847     }
6848
6849     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6850     if(clickType == Press
6851             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6852               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6853               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6854         return;
6855
6856     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6857         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6858
6859     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6860         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6861                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6862         defaultPromoChoice = DefaultPromoChoice(side);
6863     }
6864
6865     autoQueen = appData.alwaysPromoteToQueen;
6866
6867     if (fromX == -1) {
6868       int originalY = y;
6869       gatingPiece = EmptySquare;
6870       if (clickType != Press) {
6871         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6872             DragPieceEnd(xPix, yPix); dragging = 0;
6873             DrawPosition(FALSE, NULL);
6874         }
6875         return;
6876       }
6877       fromX = x; fromY = y; toX = toY = -1;
6878       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6879          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6880          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6881             /* First square */
6882             if (OKToStartUserMove(fromX, fromY)) {
6883                 second = 0;
6884                 MarkTargetSquares(0);
6885                 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6886                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6887                     promoSweep = defaultPromoChoice;
6888                     selectFlag = 0; lastX = xPix; lastY = yPix;
6889                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6890                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6891                 }
6892                 if (appData.highlightDragging) {
6893                     SetHighlights(fromX, fromY, -1, -1);
6894                 }
6895             } else fromX = fromY = -1;
6896             return;
6897         }
6898     }
6899
6900     /* fromX != -1 */
6901     if (clickType == Press && gameMode != EditPosition) {
6902         ChessSquare fromP;
6903         ChessSquare toP;
6904         int frc;
6905
6906         // ignore off-board to clicks
6907         if(y < 0 || x < 0) return;
6908
6909         /* Check if clicking again on the same color piece */
6910         fromP = boards[currentMove][fromY][fromX];
6911         toP = boards[currentMove][y][x];
6912         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6913         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6914              WhitePawn <= toP && toP <= WhiteKing &&
6915              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6916              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6917             (BlackPawn <= fromP && fromP <= BlackKing &&
6918              BlackPawn <= toP && toP <= BlackKing &&
6919              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6920              !(fromP == BlackKing && toP == BlackRook && frc))) {
6921             /* Clicked again on same color piece -- changed his mind */
6922             second = (x == fromX && y == fromY);
6923             promoDefaultAltered = FALSE;
6924             MarkTargetSquares(1);
6925            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6926             if (appData.highlightDragging) {
6927                 SetHighlights(x, y, -1, -1);
6928             } else {
6929                 ClearHighlights();
6930             }
6931             if (OKToStartUserMove(x, y)) {
6932                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6933                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6934                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6935                  gatingPiece = boards[currentMove][fromY][fromX];
6936                 else gatingPiece = EmptySquare;
6937                 fromX = x;
6938                 fromY = y; dragging = 1;
6939                 MarkTargetSquares(0);
6940                 DragPieceBegin(xPix, yPix, FALSE);
6941                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6942                     promoSweep = defaultPromoChoice;
6943                     selectFlag = 0; lastX = xPix; lastY = yPix;
6944                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6945                 }
6946             }
6947            }
6948            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6949            second = FALSE; 
6950         }
6951         // ignore clicks on holdings
6952         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6953     }
6954
6955     if (clickType == Release && x == fromX && y == fromY) {
6956         DragPieceEnd(xPix, yPix); dragging = 0;
6957         if(clearFlag) {
6958             // a deferred attempt to click-click move an empty square on top of a piece
6959             boards[currentMove][y][x] = EmptySquare;
6960             ClearHighlights();
6961             DrawPosition(FALSE, boards[currentMove]);
6962             fromX = fromY = -1; clearFlag = 0;
6963             return;
6964         }
6965         if (appData.animateDragging) {
6966             /* Undo animation damage if any */
6967             DrawPosition(FALSE, NULL);
6968         }
6969         if (second) {
6970             /* Second up/down in same square; just abort move */
6971             second = 0;
6972             fromX = fromY = -1;
6973             gatingPiece = EmptySquare;
6974             ClearHighlights();
6975             gotPremove = 0;
6976             ClearPremoveHighlights();
6977         } else {
6978             /* First upclick in same square; start click-click mode */
6979             SetHighlights(x, y, -1, -1);
6980         }
6981         return;
6982     }
6983
6984     clearFlag = 0;
6985
6986     /* we now have a different from- and (possibly off-board) to-square */
6987     /* Completed move */
6988     toX = x;
6989     toY = y;
6990     saveAnimate = appData.animate;
6991     MarkTargetSquares(1);
6992     if (clickType == Press) {
6993         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6994             // must be Edit Position mode with empty-square selected
6995             fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6996             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6997             return;
6998         }
6999         if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
7000             ChessSquare piece = boards[currentMove][fromY][fromX];
7001             DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7002             promoSweep = defaultPromoChoice;
7003             if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7004             selectFlag = 0; lastX = xPix; lastY = yPix;
7005             Sweep(0); // Pawn that is going to promote: preview promotion piece
7006             DisplayMessage("", _("Pull pawn backwards to under-promote"));
7007             DrawPosition(FALSE, boards[currentMove]);
7008             return;
7009         }
7010         /* Finish clickclick move */
7011         if (appData.animate || appData.highlightLastMove) {
7012             SetHighlights(fromX, fromY, toX, toY);
7013         } else {
7014             ClearHighlights();
7015         }
7016     } else {
7017         /* Finish drag move */
7018         if (appData.highlightLastMove) {
7019             SetHighlights(fromX, fromY, toX, toY);
7020         } else {
7021             ClearHighlights();
7022         }
7023         DragPieceEnd(xPix, yPix); dragging = 0;
7024         /* Don't animate move and drag both */
7025         appData.animate = FALSE;
7026     }
7027
7028     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7029     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7030         ChessSquare piece = boards[currentMove][fromY][fromX];
7031         if(gameMode == EditPosition && piece != EmptySquare &&
7032            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7033             int n;
7034
7035             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7036                 n = PieceToNumber(piece - (int)BlackPawn);
7037                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7038                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7039                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7040             } else
7041             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7042                 n = PieceToNumber(piece);
7043                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7044                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7045                 boards[currentMove][n][BOARD_WIDTH-2]++;
7046             }
7047             boards[currentMove][fromY][fromX] = EmptySquare;
7048         }
7049         ClearHighlights();
7050         fromX = fromY = -1;
7051         DrawPosition(TRUE, boards[currentMove]);
7052         return;
7053     }
7054
7055     // off-board moves should not be highlighted
7056     if(x < 0 || y < 0) ClearHighlights();
7057
7058     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7059
7060     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7061         SetHighlights(fromX, fromY, toX, toY);
7062         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7063             // [HGM] super: promotion to captured piece selected from holdings
7064             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7065             promotionChoice = TRUE;
7066             // kludge follows to temporarily execute move on display, without promoting yet
7067             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7068             boards[currentMove][toY][toX] = p;
7069             DrawPosition(FALSE, boards[currentMove]);
7070             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7071             boards[currentMove][toY][toX] = q;
7072             DisplayMessage("Click in holdings to choose piece", "");
7073             return;
7074         }
7075         PromotionPopUp();
7076     } else {
7077         int oldMove = currentMove;
7078         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7079         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7080         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7081         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7082            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7083             DrawPosition(TRUE, boards[currentMove]);
7084         fromX = fromY = -1;
7085     }
7086     appData.animate = saveAnimate;
7087     if (appData.animate || appData.animateDragging) {
7088         /* Undo animation damage if needed */
7089         DrawPosition(FALSE, NULL);
7090     }
7091 }
7092
7093 int
7094 RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
7095 {   // front-end-free part taken out of PieceMenuPopup
7096     int whichMenu; int xSqr, ySqr;
7097
7098     if(seekGraphUp) { // [HGM] seekgraph
7099         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7100         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7101         return -2;
7102     }
7103
7104     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7105          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7106         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7107         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7108         if(action == Press)   {
7109             originalFlip = flipView;
7110             flipView = !flipView; // temporarily flip board to see game from partners perspective
7111             DrawPosition(TRUE, partnerBoard);
7112             DisplayMessage(partnerStatus, "");
7113             partnerUp = TRUE;
7114         } else if(action == Release) {
7115             flipView = originalFlip;
7116             DrawPosition(TRUE, boards[currentMove]);
7117             partnerUp = FALSE;
7118         }
7119         return -2;
7120     }
7121
7122     xSqr = EventToSquare(x, BOARD_WIDTH);
7123     ySqr = EventToSquare(y, BOARD_HEIGHT);
7124     if (action == Release) {
7125         if(pieceSweep != EmptySquare) {
7126             EditPositionMenuEvent(pieceSweep, toX, toY);
7127             pieceSweep = EmptySquare;
7128         } else UnLoadPV(); // [HGM] pv
7129     }
7130     if (action != Press) return -2; // return code to be ignored
7131     switch (gameMode) {
7132       case IcsExamining:
7133         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7134       case EditPosition:
7135         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7136         if (xSqr < 0 || ySqr < 0) return -1;
7137         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7138         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7139         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7140         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7141         NextPiece(0);
7142         return 2; // grab
7143       case IcsObserving:
7144         if(!appData.icsEngineAnalyze) return -1;
7145       case IcsPlayingWhite:
7146       case IcsPlayingBlack:
7147         if(!appData.zippyPlay) goto noZip;
7148       case AnalyzeMode:
7149       case AnalyzeFile:
7150       case MachinePlaysWhite:
7151       case MachinePlaysBlack:
7152       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7153         if (!appData.dropMenu) {
7154           LoadPV(x, y);
7155           return 2; // flag front-end to grab mouse events
7156         }
7157         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7158            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7159       case EditGame:
7160       noZip:
7161         if (xSqr < 0 || ySqr < 0) return -1;
7162         if (!appData.dropMenu || appData.testLegality &&
7163             gameInfo.variant != VariantBughouse &&
7164             gameInfo.variant != VariantCrazyhouse) return -1;
7165         whichMenu = 1; // drop menu
7166         break;
7167       default:
7168         return -1;
7169     }
7170
7171     if (((*fromX = xSqr) < 0) ||
7172         ((*fromY = ySqr) < 0)) {
7173         *fromX = *fromY = -1;
7174         return -1;
7175     }
7176     if (flipView)
7177       *fromX = BOARD_WIDTH - 1 - *fromX;
7178     else
7179       *fromY = BOARD_HEIGHT - 1 - *fromY;
7180
7181     return whichMenu;
7182 }
7183
7184 void
7185 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
7186 {
7187 //    char * hint = lastHint;
7188     FrontEndProgramStats stats;
7189
7190     stats.which = cps == &first ? 0 : 1;
7191     stats.depth = cpstats->depth;
7192     stats.nodes = cpstats->nodes;
7193     stats.score = cpstats->score;
7194     stats.time = cpstats->time;
7195     stats.pv = cpstats->movelist;
7196     stats.hint = lastHint;
7197     stats.an_move_index = 0;
7198     stats.an_move_count = 0;
7199
7200     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7201         stats.hint = cpstats->move_name;
7202         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7203         stats.an_move_count = cpstats->nr_moves;
7204     }
7205
7206     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
7207
7208     SetProgramStats( &stats );
7209 }
7210
7211 void
7212 ClearEngineOutputPane (int which)
7213 {
7214     static FrontEndProgramStats dummyStats;
7215     dummyStats.which = which;
7216     dummyStats.pv = "#";
7217     SetProgramStats( &dummyStats );
7218 }
7219
7220 #define MAXPLAYERS 500
7221
7222 char *
7223 TourneyStandings (int display)
7224 {
7225     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7226     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7227     char result, *p, *names[MAXPLAYERS];
7228
7229     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7230         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7231     names[0] = p = strdup(appData.participants);
7232     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7233
7234     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7235
7236     while(result = appData.results[nr]) {
7237         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7238         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7239         wScore = bScore = 0;
7240         switch(result) {
7241           case '+': wScore = 2; break;
7242           case '-': bScore = 2; break;
7243           case '=': wScore = bScore = 1; break;
7244           case ' ':
7245           case '*': return strdup("busy"); // tourney not finished
7246         }
7247         score[w] += wScore;
7248         score[b] += bScore;
7249         games[w]++;
7250         games[b]++;
7251         nr++;
7252     }
7253     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7254     for(w=0; w<nPlayers; w++) {
7255         bScore = -1;
7256         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7257         ranking[w] = b; points[w] = bScore; score[b] = -2;
7258     }
7259     p = malloc(nPlayers*34+1);
7260     for(w=0; w<nPlayers && w<display; w++)
7261         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7262     free(names[0]);
7263     return p;
7264 }
7265
7266 void
7267 Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7268 {       // count all piece types
7269         int p, f, r;
7270         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7271         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7272         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7273                 p = board[r][f];
7274                 pCnt[p]++;
7275                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7276                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7277                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7278                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7279                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7280                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7281         }
7282 }
7283
7284 int
7285 SufficientDefence (int pCnt[], int side, int nMine, int nHis)
7286 {
7287         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7288         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7289
7290         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7291         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7292         if(myPawns == 2 && nMine == 3) // KPP
7293             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7294         if(myPawns == 1 && nMine == 2) // KP
7295             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7296         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7297             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7298         if(myPawns) return FALSE;
7299         if(pCnt[WhiteRook+side])
7300             return pCnt[BlackRook-side] ||
7301                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7302                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7303                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7304         if(pCnt[WhiteCannon+side]) {
7305             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7306             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7307         }
7308         if(pCnt[WhiteKnight+side])
7309             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7310         return FALSE;
7311 }
7312
7313 int
7314 MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7315 {
7316         VariantClass v = gameInfo.variant;
7317
7318         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7319         if(v == VariantShatranj) return TRUE; // always winnable through baring
7320         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7321         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7322
7323         if(v == VariantXiangqi) {
7324                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7325
7326                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7327                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7328                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7329                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7330                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7331                 if(stale) // we have at least one last-rank P plus perhaps C
7332                     return majors // KPKX
7333                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7334                 else // KCA*E*
7335                     return pCnt[WhiteFerz+side] // KCAK
7336                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7337                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7338                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7339
7340         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7341                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7342
7343                 if(nMine == 1) return FALSE; // bare King
7344                 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
7345                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7346                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7347                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7348                 if(pCnt[WhiteKnight+side])
7349                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7350                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7351                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7352                 if(nBishops)
7353                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7354                 if(pCnt[WhiteAlfil+side])
7355                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7356                 if(pCnt[WhiteWazir+side])
7357                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7358         }
7359
7360         return TRUE;
7361 }
7362
7363 int
7364 CompareWithRights (Board b1, Board b2)
7365 {
7366     int rights = 0;
7367     if(!CompareBoards(b1, b2)) return FALSE;
7368     if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
7369     /* compare castling rights */
7370     if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
7371            rights++; /* King lost rights, while rook still had them */
7372     if( b1[CASTLING][2] != NoRights ) { /* king has rights */
7373         if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
7374            rights++; /* but at least one rook lost them */
7375     }
7376     if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
7377            rights++;
7378     if( b1[CASTLING][5] != NoRights ) {
7379         if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
7380            rights++;
7381     }
7382     return rights == 0;
7383 }
7384
7385 int
7386 Adjudicate (ChessProgramState *cps)
7387 {       // [HGM] some adjudications useful with buggy engines
7388         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7389         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7390         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7391         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7392         int k, count = 0; static int bare = 1;
7393         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7394         Boolean canAdjudicate = !appData.icsActive;
7395
7396         // most tests only when we understand the game, i.e. legality-checking on
7397             if( appData.testLegality )
7398             {   /* [HGM] Some more adjudications for obstinate engines */
7399                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7400                 static int moveCount = 6;
7401                 ChessMove result;
7402                 char *reason = NULL;
7403
7404                 /* Count what is on board. */
7405                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7406
7407                 /* Some material-based adjudications that have to be made before stalemate test */
7408                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7409                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7410                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7411                      if(canAdjudicate && appData.checkMates) {
7412                          if(engineOpponent)
7413                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7414                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7415                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7416                          return 1;
7417                      }
7418                 }
7419
7420                 /* Bare King in Shatranj (loses) or Losers (wins) */
7421                 if( nrW == 1 || nrB == 1) {
7422                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7423                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7424                      if(canAdjudicate && appData.checkMates) {
7425                          if(engineOpponent)
7426                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7427                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7428                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7429                          return 1;
7430                      }
7431                   } else
7432                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7433                   {    /* bare King */
7434                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7435                         if(canAdjudicate && appData.checkMates) {
7436                             /* but only adjudicate if adjudication enabled */
7437                             if(engineOpponent)
7438                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7439                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7440                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7441                             return 1;
7442                         }
7443                   }
7444                 } else bare = 1;
7445
7446
7447             // don't wait for engine to announce game end if we can judge ourselves
7448             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7449               case MT_CHECK:
7450                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7451                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7452                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7453                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7454                             checkCnt++;
7455                         if(checkCnt >= 2) {
7456                             reason = "Xboard adjudication: 3rd check";
7457                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7458                             break;
7459                         }
7460                     }
7461                 }
7462               case MT_NONE:
7463               default:
7464                 break;
7465               case MT_STALEMATE:
7466               case MT_STAINMATE:
7467                 reason = "Xboard adjudication: Stalemate";
7468                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7469                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7470                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7471                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7472                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7473                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7474                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7475                                                                         EP_CHECKMATE : EP_WINS);
7476                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7477                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7478                 }
7479                 break;
7480               case MT_CHECKMATE:
7481                 reason = "Xboard adjudication: Checkmate";
7482                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7483                 break;
7484             }
7485
7486                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7487                     case EP_STALEMATE:
7488                         result = GameIsDrawn; break;
7489                     case EP_CHECKMATE:
7490                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7491                     case EP_WINS:
7492                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7493                     default:
7494                         result = EndOfFile;
7495                 }
7496                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7497                     if(engineOpponent)
7498                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7499                     GameEnds( result, reason, GE_XBOARD );
7500                     return 1;
7501                 }
7502
7503                 /* Next absolutely insufficient mating material. */
7504                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7505                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7506                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7507
7508                      /* always flag draws, for judging claims */
7509                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7510
7511                      if(canAdjudicate && appData.materialDraws) {
7512                          /* but only adjudicate them if adjudication enabled */
7513                          if(engineOpponent) {
7514                            SendToProgram("force\n", engineOpponent); // suppress reply
7515                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7516                          }
7517                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7518                          return 1;
7519                      }
7520                 }
7521
7522                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7523                 if(gameInfo.variant == VariantXiangqi ?
7524                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7525                  : nrW + nrB == 4 &&
7526                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7527                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7528                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7529                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7530                    ) ) {
7531                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7532                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7533                           if(engineOpponent) {
7534                             SendToProgram("force\n", engineOpponent); // suppress reply
7535                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7536                           }
7537                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7538                           return 1;
7539                      }
7540                 } else moveCount = 6;
7541             }
7542         if (appData.debugMode) { int i;
7543             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7544                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7545                     appData.drawRepeats);
7546             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7547               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7548
7549         }
7550
7551         // Repetition draws and 50-move rule can be applied independently of legality testing
7552
7553                 /* Check for rep-draws */
7554                 count = 0;
7555                 for(k = forwardMostMove-2;
7556                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7557                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7558                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7559                     k-=2)
7560                 {   int rights=0;
7561                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7562                         /* compare castling rights */
7563                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7564                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7565                                 rights++; /* King lost rights, while rook still had them */
7566                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7567                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7568                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7569                                    rights++; /* but at least one rook lost them */
7570                         }
7571                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7572                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7573                                 rights++;
7574                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7575                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7576                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7577                                    rights++;
7578                         }
7579                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7580                             && appData.drawRepeats > 1) {
7581                              /* adjudicate after user-specified nr of repeats */
7582                              int result = GameIsDrawn;
7583                              char *details = "XBoard adjudication: repetition draw";
7584                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7585                                 // [HGM] xiangqi: check for forbidden perpetuals
7586                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7587                                 for(m=forwardMostMove; m>k; m-=2) {
7588                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7589                                         ourPerpetual = 0; // the current mover did not always check
7590                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7591                                         hisPerpetual = 0; // the opponent did not always check
7592                                 }
7593                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7594                                                                         ourPerpetual, hisPerpetual);
7595                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7596                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7597                                     details = "Xboard adjudication: perpetual checking";
7598                                 } else
7599                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7600                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7601                                 } else
7602                                 // Now check for perpetual chases
7603                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7604                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7605                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7606                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7607                                         static char resdet[MSG_SIZ];
7608                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7609                                         details = resdet;
7610                                         snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255);
7611                                     } else
7612                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7613                                         break; // Abort repetition-checking loop.
7614                                 }
7615                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7616                              }
7617                              if(engineOpponent) {
7618                                SendToProgram("force\n", engineOpponent); // suppress reply
7619                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7620                              }
7621                              GameEnds( result, details, GE_XBOARD );
7622                              return 1;
7623                         }
7624                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7625                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7626                     }
7627                 }
7628
7629                 /* Now we test for 50-move draws. Determine ply count */
7630                 count = forwardMostMove;
7631                 /* look for last irreversble move */
7632                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7633                     count--;
7634                 /* if we hit starting position, add initial plies */
7635                 if( count == backwardMostMove )
7636                     count -= initialRulePlies;
7637                 count = forwardMostMove - count;
7638                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7639                         // adjust reversible move counter for checks in Xiangqi
7640                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7641                         if(i < backwardMostMove) i = backwardMostMove;
7642                         while(i <= forwardMostMove) {
7643                                 lastCheck = inCheck; // check evasion does not count
7644                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7645                                 if(inCheck || lastCheck) count--; // check does not count
7646                                 i++;
7647                         }
7648                 }
7649                 if( count >= 100)
7650                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7651                          /* this is used to judge if draw claims are legal */
7652                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7658                          return 1;
7659                 }
7660
7661                 /* if draw offer is pending, treat it as a draw claim
7662                  * when draw condition present, to allow engines a way to
7663                  * claim draws before making their move to avoid a race
7664                  * condition occurring after their move
7665                  */
7666                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7667                          char *p = NULL;
7668                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7669                              p = "Draw claim: 50-move rule";
7670                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7671                              p = "Draw claim: 3-fold repetition";
7672                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7673                              p = "Draw claim: insufficient mating material";
7674                          if( p != NULL && canAdjudicate) {
7675                              if(engineOpponent) {
7676                                SendToProgram("force\n", engineOpponent); // suppress reply
7677                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7678                              }
7679                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7680                              return 1;
7681                          }
7682                 }
7683
7684                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7685                     if(engineOpponent) {
7686                       SendToProgram("force\n", engineOpponent); // suppress reply
7687                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7688                     }
7689                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7690                     return 1;
7691                 }
7692         return 0;
7693 }
7694
7695 char *
7696 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
7697 {   // [HGM] book: this routine intercepts moves to simulate book replies
7698     char *bookHit = NULL;
7699
7700     //first determine if the incoming move brings opponent into his book
7701     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7702         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7703     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7704     if(bookHit != NULL && !cps->bookSuspend) {
7705         // make sure opponent is not going to reply after receiving move to book position
7706         SendToProgram("force\n", cps);
7707         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7708     }
7709     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7710     // now arrange restart after book miss
7711     if(bookHit) {
7712         // after a book hit we never send 'go', and the code after the call to this routine
7713         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7714         char buf[MSG_SIZ], *move = bookHit;
7715         if(cps->useSAN) {
7716             int fromX, fromY, toX, toY;
7717             char promoChar;
7718             ChessMove moveType;
7719             move = buf + 30;
7720             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7721                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7722                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7723                                     PosFlags(forwardMostMove),
7724                                     fromY, fromX, toY, toX, promoChar, move);
7725             } else {
7726                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7727                 bookHit = NULL;
7728             }
7729         }
7730         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7731         SendToProgram(buf, cps);
7732         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7733     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7734         SendToProgram("go\n", cps);
7735         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7736     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7737         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7738             SendToProgram("go\n", cps);
7739         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7740     }
7741     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7742 }
7743
7744 char *savedMessage;
7745 ChessProgramState *savedState;
7746 void
7747 DeferredBookMove (void)
7748 {
7749         if(savedState->lastPing != savedState->lastPong)
7750                     ScheduleDelayedEvent(DeferredBookMove, 10);
7751         else
7752         HandleMachineMove(savedMessage, savedState);
7753 }
7754
7755 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7756
7757 void
7758 HandleMachineMove (char *message, ChessProgramState *cps)
7759 {
7760     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7761     char realname[MSG_SIZ];
7762     int fromX, fromY, toX, toY;
7763     ChessMove moveType;
7764     char promoChar;
7765     char *p, *pv=buf1;
7766     int machineWhite;
7767     char *bookHit;
7768
7769     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7770         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7771         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7772             DisplayError(_("Invalid pairing from pairing engine"), 0);
7773             return;
7774         }
7775         pairingReceived = 1;
7776         NextMatchGame();
7777         return; // Skim the pairing messages here.
7778     }
7779
7780     cps->userError = 0;
7781
7782 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7783     /*
7784      * Kludge to ignore BEL characters
7785      */
7786     while (*message == '\007') message++;
7787
7788     /*
7789      * [HGM] engine debug message: ignore lines starting with '#' character
7790      */
7791     if(cps->debug && *message == '#') return;
7792
7793     /*
7794      * Look for book output
7795      */
7796     if (cps == &first && bookRequested) {
7797         if (message[0] == '\t' || message[0] == ' ') {
7798             /* Part of the book output is here; append it */
7799             strcat(bookOutput, message);
7800             strcat(bookOutput, "  \n");
7801             return;
7802         } else if (bookOutput[0] != NULLCHAR) {
7803             /* All of book output has arrived; display it */
7804             char *p = bookOutput;
7805             while (*p != NULLCHAR) {
7806                 if (*p == '\t') *p = ' ';
7807                 p++;
7808             }
7809             DisplayInformation(bookOutput);
7810             bookRequested = FALSE;
7811             /* Fall through to parse the current output */
7812         }
7813     }
7814
7815     /*
7816      * Look for machine move.
7817      */
7818     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7819         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7820     {
7821         /* This method is only useful on engines that support ping */
7822         if (cps->lastPing != cps->lastPong) {
7823           if (gameMode == BeginningOfGame) {
7824             /* Extra move from before last new; ignore */
7825             if (appData.debugMode) {
7826                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7827             }
7828           } else {
7829             if (appData.debugMode) {
7830                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7831                         cps->which, gameMode);
7832             }
7833
7834             SendToProgram("undo\n", cps);
7835           }
7836           return;
7837         }
7838
7839         switch (gameMode) {
7840           case BeginningOfGame:
7841             /* Extra move from before last reset; ignore */
7842             if (appData.debugMode) {
7843                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7844             }
7845             return;
7846
7847           case EndOfGame:
7848           case IcsIdle:
7849           default:
7850             /* Extra move after we tried to stop.  The mode test is
7851                not a reliable way of detecting this problem, but it's
7852                the best we can do on engines that don't support ping.
7853             */
7854             if (appData.debugMode) {
7855                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7856                         cps->which, gameMode);
7857             }
7858             SendToProgram("undo\n", cps);
7859             return;
7860
7861           case MachinePlaysWhite:
7862           case IcsPlayingWhite:
7863             machineWhite = TRUE;
7864             break;
7865
7866           case MachinePlaysBlack:
7867           case IcsPlayingBlack:
7868             machineWhite = FALSE;
7869             break;
7870
7871           case TwoMachinesPlay:
7872             machineWhite = (cps->twoMachinesColor[0] == 'w');
7873             break;
7874         }
7875         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7876             if (appData.debugMode) {
7877                 fprintf(debugFP,
7878                         "Ignoring move out of turn by %s, gameMode %d"
7879                         ", forwardMost %d\n",
7880                         cps->which, gameMode, forwardMostMove);
7881             }
7882             return;
7883         }
7884
7885     if (appData.debugMode) { int f = forwardMostMove;
7886         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7887                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7888                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7889     }
7890         if(cps->alphaRank) AlphaRank(machineMove, 4);
7891         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7892                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7893             /* Machine move could not be parsed; ignore it. */
7894           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7895                     machineMove, _(cps->which));
7896             DisplayError(buf1, 0);
7897             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7898                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7899             if (gameMode == TwoMachinesPlay) {
7900               GameEnds(machineWhite ? BlackWins : WhiteWins,
7901                        buf1, GE_XBOARD);
7902             }
7903             return;
7904         }
7905
7906         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7907         /* So we have to redo legality test with true e.p. status here,  */
7908         /* to make sure an illegal e.p. capture does not slip through,   */
7909         /* to cause a forfeit on a justified illegal-move complaint      */
7910         /* of the opponent.                                              */
7911         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7912            ChessMove moveType;
7913            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7914                              fromY, fromX, toY, toX, promoChar);
7915             if (appData.debugMode) {
7916                 int i;
7917                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7918                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7919                 fprintf(debugFP, "castling rights\n");
7920             }
7921             if(moveType == IllegalMove) {
7922               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7923                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7924                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7925                            buf1, GE_XBOARD);
7926                 return;
7927            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7928            /* [HGM] Kludge to handle engines that send FRC-style castling
7929               when they shouldn't (like TSCP-Gothic) */
7930            switch(moveType) {
7931              case WhiteASideCastleFR:
7932              case BlackASideCastleFR:
7933                toX+=2;
7934                currentMoveString[2]++;
7935                break;
7936              case WhiteHSideCastleFR:
7937              case BlackHSideCastleFR:
7938                toX--;
7939                currentMoveString[2]--;
7940                break;
7941              default: ; // nothing to do, but suppresses warning of pedantic compilers
7942            }
7943         }
7944         hintRequested = FALSE;
7945         lastHint[0] = NULLCHAR;
7946         bookRequested = FALSE;
7947         /* Program may be pondering now */
7948         cps->maybeThinking = TRUE;
7949         if (cps->sendTime == 2) cps->sendTime = 1;
7950         if (cps->offeredDraw) cps->offeredDraw--;
7951
7952         /* [AS] Save move info*/
7953         pvInfoList[ forwardMostMove ].score = programStats.score;
7954         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7955         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7956
7957         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7958
7959         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7960         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7961             int count = 0;
7962
7963             while( count < adjudicateLossPlies ) {
7964                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7965
7966                 if( count & 1 ) {
7967                     score = -score; /* Flip score for winning side */
7968                 }
7969
7970                 if( score > adjudicateLossThreshold ) {
7971                     break;
7972                 }
7973
7974                 count++;
7975             }
7976
7977             if( count >= adjudicateLossPlies ) {
7978                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7979
7980                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7981                     "Xboard adjudication",
7982                     GE_XBOARD );
7983
7984                 return;
7985             }
7986         }
7987
7988         if(Adjudicate(cps)) {
7989             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7990             return; // [HGM] adjudicate: for all automatic game ends
7991         }
7992
7993 #if ZIPPY
7994         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7995             first.initDone) {
7996           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7997                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7998                 SendToICS("draw ");
7999                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8000           }
8001           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
8002           ics_user_moved = 1;
8003           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
8004                 char buf[3*MSG_SIZ];
8005
8006                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
8007                         programStats.score / 100.,
8008                         programStats.depth,
8009                         programStats.time / 100.,
8010                         (unsigned int)programStats.nodes,
8011                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
8012                         programStats.movelist);
8013                 SendToICS(buf);
8014 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
8015           }
8016         }
8017 #endif
8018
8019         /* [AS] Clear stats for next move */
8020         ClearProgramStats();
8021         thinkOutput[0] = NULLCHAR;
8022         hiddenThinkOutputState = 0;
8023
8024         bookHit = NULL;
8025         if (gameMode == TwoMachinesPlay) {
8026             /* [HGM] relaying draw offers moved to after reception of move */
8027             /* and interpreting offer as claim if it brings draw condition */
8028             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8029                 SendToProgram("draw\n", cps->other);
8030             }
8031             if (cps->other->sendTime) {
8032                 SendTimeRemaining(cps->other,
8033                                   cps->other->twoMachinesColor[0] == 'w');
8034             }
8035             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8036             if (firstMove && !bookHit) {
8037                 firstMove = FALSE;
8038                 if (cps->other->useColors) {
8039                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8040                 }
8041                 SendToProgram("go\n", cps->other);
8042             }
8043             cps->other->maybeThinking = TRUE;
8044         }
8045
8046         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8047
8048         if (!pausing && appData.ringBellAfterMoves) {
8049             RingBell();
8050         }
8051
8052         /*
8053          * Reenable menu items that were disabled while
8054          * machine was thinking
8055          */
8056         if (gameMode != TwoMachinesPlay)
8057             SetUserThinkingEnables();
8058
8059         // [HGM] book: after book hit opponent has received move and is now in force mode
8060         // force the book reply into it, and then fake that it outputted this move by jumping
8061         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8062         if(bookHit) {
8063                 static char bookMove[MSG_SIZ]; // a bit generous?
8064
8065                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8066                 strcat(bookMove, bookHit);
8067                 message = bookMove;
8068                 cps = cps->other;
8069                 programStats.nodes = programStats.depth = programStats.time =
8070                 programStats.score = programStats.got_only_move = 0;
8071                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8072
8073                 if(cps->lastPing != cps->lastPong) {
8074                     savedMessage = message; // args for deferred call
8075                     savedState = cps;
8076                     ScheduleDelayedEvent(DeferredBookMove, 10);
8077                     return;
8078                 }
8079                 goto FakeBookMove;
8080         }
8081
8082         return;
8083     }
8084
8085     /* Set special modes for chess engines.  Later something general
8086      *  could be added here; for now there is just one kludge feature,
8087      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8088      *  when "xboard" is given as an interactive command.
8089      */
8090     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8091         cps->useSigint = FALSE;
8092         cps->useSigterm = FALSE;
8093     }
8094     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8095       ParseFeatures(message+8, cps);
8096       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8097     }
8098
8099     if ((!appData.testLegality || gameInfo.variant == VariantFairy) && 
8100                                         !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8101       int dummy, s=6; char buf[MSG_SIZ];
8102       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
8103       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8104       if(startedFromSetupPosition) return;
8105       ParseFEN(boards[0], &dummy, message+s);
8106       DrawPosition(TRUE, boards[0]);
8107       startedFromSetupPosition = TRUE;
8108       return;
8109     }
8110     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8111      * want this, I was asked to put it in, and obliged.
8112      */
8113     if (!strncmp(message, "setboard ", 9)) {
8114         Board initial_position;
8115
8116         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8117
8118         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8119             DisplayError(_("Bad FEN received from engine"), 0);
8120             return ;
8121         } else {
8122            Reset(TRUE, FALSE);
8123            CopyBoard(boards[0], initial_position);
8124            initialRulePlies = FENrulePlies;
8125            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8126            else gameMode = MachinePlaysBlack;
8127            DrawPosition(FALSE, boards[currentMove]);
8128         }
8129         return;
8130     }
8131
8132     /*
8133      * Look for communication commands
8134      */
8135     if (!strncmp(message, "telluser ", 9)) {
8136         if(message[9] == '\\' && message[10] == '\\')
8137             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8138         PlayTellSound();
8139         DisplayNote(message + 9);
8140         return;
8141     }
8142     if (!strncmp(message, "tellusererror ", 14)) {
8143         cps->userError = 1;
8144         if(message[14] == '\\' && message[15] == '\\')
8145             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8146         PlayTellSound();
8147         DisplayError(message + 14, 0);
8148         return;
8149     }
8150     if (!strncmp(message, "tellopponent ", 13)) {
8151       if (appData.icsActive) {
8152         if (loggedOn) {
8153           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8154           SendToICS(buf1);
8155         }
8156       } else {
8157         DisplayNote(message + 13);
8158       }
8159       return;
8160     }
8161     if (!strncmp(message, "tellothers ", 11)) {
8162       if (appData.icsActive) {
8163         if (loggedOn) {
8164           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8165           SendToICS(buf1);
8166         }
8167       }
8168       return;
8169     }
8170     if (!strncmp(message, "tellall ", 8)) {
8171       if (appData.icsActive) {
8172         if (loggedOn) {
8173           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8174           SendToICS(buf1);
8175         }
8176       } else {
8177         DisplayNote(message + 8);
8178       }
8179       return;
8180     }
8181     if (strncmp(message, "warning", 7) == 0) {
8182         /* Undocumented feature, use tellusererror in new code */
8183         DisplayError(message, 0);
8184         return;
8185     }
8186     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8187         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8188         strcat(realname, " query");
8189         AskQuestion(realname, buf2, buf1, cps->pr);
8190         return;
8191     }
8192     /* Commands from the engine directly to ICS.  We don't allow these to be
8193      *  sent until we are logged on. Crafty kibitzes have been known to
8194      *  interfere with the login process.
8195      */
8196     if (loggedOn) {
8197         if (!strncmp(message, "tellics ", 8)) {
8198             SendToICS(message + 8);
8199             SendToICS("\n");
8200             return;
8201         }
8202         if (!strncmp(message, "tellicsnoalias ", 15)) {
8203             SendToICS(ics_prefix);
8204             SendToICS(message + 15);
8205             SendToICS("\n");
8206             return;
8207         }
8208         /* The following are for backward compatibility only */
8209         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8210             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8211             SendToICS(ics_prefix);
8212             SendToICS(message);
8213             SendToICS("\n");
8214             return;
8215         }
8216     }
8217     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8218         return;
8219     }
8220     /*
8221      * If the move is illegal, cancel it and redraw the board.
8222      * Also deal with other error cases.  Matching is rather loose
8223      * here to accommodate engines written before the spec.
8224      */
8225     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8226         strncmp(message, "Error", 5) == 0) {
8227         if (StrStr(message, "name") ||
8228             StrStr(message, "rating") || StrStr(message, "?") ||
8229             StrStr(message, "result") || StrStr(message, "board") ||
8230             StrStr(message, "bk") || StrStr(message, "computer") ||
8231             StrStr(message, "variant") || StrStr(message, "hint") ||
8232             StrStr(message, "random") || StrStr(message, "depth") ||
8233             StrStr(message, "accepted")) {
8234             return;
8235         }
8236         if (StrStr(message, "protover")) {
8237           /* Program is responding to input, so it's apparently done
8238              initializing, and this error message indicates it is
8239              protocol version 1.  So we don't need to wait any longer
8240              for it to initialize and send feature commands. */
8241           FeatureDone(cps, 1);
8242           cps->protocolVersion = 1;
8243           return;
8244         }
8245         cps->maybeThinking = FALSE;
8246
8247         if (StrStr(message, "draw")) {
8248             /* Program doesn't have "draw" command */
8249             cps->sendDrawOffers = 0;
8250             return;
8251         }
8252         if (cps->sendTime != 1 &&
8253             (StrStr(message, "time") || StrStr(message, "otim"))) {
8254           /* Program apparently doesn't have "time" or "otim" command */
8255           cps->sendTime = 0;
8256           return;
8257         }
8258         if (StrStr(message, "analyze")) {
8259             cps->analysisSupport = FALSE;
8260             cps->analyzing = FALSE;
8261 //          Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
8262             EditGameEvent(); // [HGM] try to preserve loaded game
8263             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8264             DisplayError(buf2, 0);
8265             return;
8266         }
8267         if (StrStr(message, "(no matching move)st")) {
8268           /* Special kludge for GNU Chess 4 only */
8269           cps->stKludge = TRUE;
8270           SendTimeControl(cps, movesPerSession, timeControl,
8271                           timeIncrement, appData.searchDepth,
8272                           searchTime);
8273           return;
8274         }
8275         if (StrStr(message, "(no matching move)sd")) {
8276           /* Special kludge for GNU Chess 4 only */
8277           cps->sdKludge = TRUE;
8278           SendTimeControl(cps, movesPerSession, timeControl,
8279                           timeIncrement, appData.searchDepth,
8280                           searchTime);
8281           return;
8282         }
8283         if (!StrStr(message, "llegal")) {
8284             return;
8285         }
8286         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8287             gameMode == IcsIdle) return;
8288         if (forwardMostMove <= backwardMostMove) return;
8289         if (pausing) PauseEvent();
8290       if(appData.forceIllegal) {
8291             // [HGM] illegal: machine refused move; force position after move into it
8292           SendToProgram("force\n", cps);
8293           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8294                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8295                 // when black is to move, while there might be nothing on a2 or black
8296                 // might already have the move. So send the board as if white has the move.
8297                 // But first we must change the stm of the engine, as it refused the last move
8298                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8299                 if(WhiteOnMove(forwardMostMove)) {
8300                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8301                     SendBoard(cps, forwardMostMove); // kludgeless board
8302                 } else {
8303                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8304                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8305                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8306                 }
8307           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8308             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8309                  gameMode == TwoMachinesPlay)
8310               SendToProgram("go\n", cps);
8311             return;
8312       } else
8313         if (gameMode == PlayFromGameFile) {
8314             /* Stop reading this game file */
8315             gameMode = EditGame;
8316             ModeHighlight();
8317         }
8318         /* [HGM] illegal-move claim should forfeit game when Xboard */
8319         /* only passes fully legal moves                            */
8320         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8321             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8322                                 "False illegal-move claim", GE_XBOARD );
8323             return; // do not take back move we tested as valid
8324         }
8325         currentMove = forwardMostMove-1;
8326         DisplayMove(currentMove-1); /* before DisplayMoveError */
8327         SwitchClocks(forwardMostMove-1); // [HGM] race
8328         DisplayBothClocks();
8329         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8330                 parseList[currentMove], _(cps->which));
8331         DisplayMoveError(buf1);
8332         DrawPosition(FALSE, boards[currentMove]);
8333
8334         SetUserThinkingEnables();
8335         return;
8336     }
8337     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8338         /* Program has a broken "time" command that
8339            outputs a string not ending in newline.
8340            Don't use it. */
8341         cps->sendTime = 0;
8342     }
8343
8344     /*
8345      * If chess program startup fails, exit with an error message.
8346      * Attempts to recover here are futile.
8347      */
8348     if ((StrStr(message, "unknown host") != NULL)
8349         || (StrStr(message, "No remote directory") != NULL)
8350         || (StrStr(message, "not found") != NULL)
8351         || (StrStr(message, "No such file") != NULL)
8352         || (StrStr(message, "can't alloc") != NULL)
8353         || (StrStr(message, "Permission denied") != NULL)) {
8354
8355         cps->maybeThinking = FALSE;
8356         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8357                 _(cps->which), cps->program, cps->host, message);
8358         RemoveInputSource(cps->isr);
8359         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8360             if(cps == &first) appData.noChessProgram = TRUE;
8361             DisplayError(buf1, 0);
8362         }
8363         return;
8364     }
8365
8366     /*
8367      * Look for hint output
8368      */
8369     if (sscanf(message, "Hint: %s", buf1) == 1) {
8370         if (cps == &first && hintRequested) {
8371             hintRequested = FALSE;
8372             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8373                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8374                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8375                                     PosFlags(forwardMostMove),
8376                                     fromY, fromX, toY, toX, promoChar, buf1);
8377                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8378                 DisplayInformation(buf2);
8379             } else {
8380                 /* Hint move could not be parsed!? */
8381               snprintf(buf2, sizeof(buf2),
8382                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8383                         buf1, _(cps->which));
8384                 DisplayError(buf2, 0);
8385             }
8386         } else {
8387           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8388         }
8389         return;
8390     }
8391
8392     /*
8393      * Ignore other messages if game is not in progress
8394      */
8395     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8396         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8397
8398     /*
8399      * look for win, lose, draw, or draw offer
8400      */
8401     if (strncmp(message, "1-0", 3) == 0) {
8402         char *p, *q, *r = "";
8403         p = strchr(message, '{');
8404         if (p) {
8405             q = strchr(p, '}');
8406             if (q) {
8407                 *q = NULLCHAR;
8408                 r = p + 1;
8409             }
8410         }
8411         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8412         return;
8413     } else if (strncmp(message, "0-1", 3) == 0) {
8414         char *p, *q, *r = "";
8415         p = strchr(message, '{');
8416         if (p) {
8417             q = strchr(p, '}');
8418             if (q) {
8419                 *q = NULLCHAR;
8420                 r = p + 1;
8421             }
8422         }
8423         /* Kludge for Arasan 4.1 bug */
8424         if (strcmp(r, "Black resigns") == 0) {
8425             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8426             return;
8427         }
8428         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8429         return;
8430     } else if (strncmp(message, "1/2", 3) == 0) {
8431         char *p, *q, *r = "";
8432         p = strchr(message, '{');
8433         if (p) {
8434             q = strchr(p, '}');
8435             if (q) {
8436                 *q = NULLCHAR;
8437                 r = p + 1;
8438             }
8439         }
8440
8441         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8442         return;
8443
8444     } else if (strncmp(message, "White resign", 12) == 0) {
8445         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8446         return;
8447     } else if (strncmp(message, "Black resign", 12) == 0) {
8448         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8449         return;
8450     } else if (strncmp(message, "White matches", 13) == 0 ||
8451                strncmp(message, "Black matches", 13) == 0   ) {
8452         /* [HGM] ignore GNUShogi noises */
8453         return;
8454     } else if (strncmp(message, "White", 5) == 0 &&
8455                message[5] != '(' &&
8456                StrStr(message, "Black") == NULL) {
8457         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8458         return;
8459     } else if (strncmp(message, "Black", 5) == 0 &&
8460                message[5] != '(') {
8461         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8462         return;
8463     } else if (strcmp(message, "resign") == 0 ||
8464                strcmp(message, "computer resigns") == 0) {
8465         switch (gameMode) {
8466           case MachinePlaysBlack:
8467           case IcsPlayingBlack:
8468             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8469             break;
8470           case MachinePlaysWhite:
8471           case IcsPlayingWhite:
8472             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8473             break;
8474           case TwoMachinesPlay:
8475             if (cps->twoMachinesColor[0] == 'w')
8476               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8477             else
8478               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8479             break;
8480           default:
8481             /* can't happen */
8482             break;
8483         }
8484         return;
8485     } else if (strncmp(message, "opponent mates", 14) == 0) {
8486         switch (gameMode) {
8487           case MachinePlaysBlack:
8488           case IcsPlayingBlack:
8489             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8490             break;
8491           case MachinePlaysWhite:
8492           case IcsPlayingWhite:
8493             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8494             break;
8495           case TwoMachinesPlay:
8496             if (cps->twoMachinesColor[0] == 'w')
8497               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8498             else
8499               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8500             break;
8501           default:
8502             /* can't happen */
8503             break;
8504         }
8505         return;
8506     } else if (strncmp(message, "computer mates", 14) == 0) {
8507         switch (gameMode) {
8508           case MachinePlaysBlack:
8509           case IcsPlayingBlack:
8510             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8511             break;
8512           case MachinePlaysWhite:
8513           case IcsPlayingWhite:
8514             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8515             break;
8516           case TwoMachinesPlay:
8517             if (cps->twoMachinesColor[0] == 'w')
8518               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8519             else
8520               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8521             break;
8522           default:
8523             /* can't happen */
8524             break;
8525         }
8526         return;
8527     } else if (strncmp(message, "checkmate", 9) == 0) {
8528         if (WhiteOnMove(forwardMostMove)) {
8529             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8530         } else {
8531             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8532         }
8533         return;
8534     } else if (strstr(message, "Draw") != NULL ||
8535                strstr(message, "game is a draw") != NULL) {
8536         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8537         return;
8538     } else if (strstr(message, "offer") != NULL &&
8539                strstr(message, "draw") != NULL) {
8540 #if ZIPPY
8541         if (appData.zippyPlay && first.initDone) {
8542             /* Relay offer to ICS */
8543             SendToICS(ics_prefix);
8544             SendToICS("draw\n");
8545         }
8546 #endif
8547         cps->offeredDraw = 2; /* valid until this engine moves twice */
8548         if (gameMode == TwoMachinesPlay) {
8549             if (cps->other->offeredDraw) {
8550                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8551             /* [HGM] in two-machine mode we delay relaying draw offer      */
8552             /* until after we also have move, to see if it is really claim */
8553             }
8554         } else if (gameMode == MachinePlaysWhite ||
8555                    gameMode == MachinePlaysBlack) {
8556           if (userOfferedDraw) {
8557             DisplayInformation(_("Machine accepts your draw offer"));
8558             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8559           } else {
8560             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8561           }
8562         }
8563     }
8564
8565
8566     /*
8567      * Look for thinking output
8568      */
8569     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8570           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8571                                 ) {
8572         int plylev, mvleft, mvtot, curscore, time;
8573         char mvname[MOVE_LEN];
8574         u64 nodes; // [DM]
8575         char plyext;
8576         int ignore = FALSE;
8577         int prefixHint = FALSE;
8578         mvname[0] = NULLCHAR;
8579
8580         switch (gameMode) {
8581           case MachinePlaysBlack:
8582           case IcsPlayingBlack:
8583             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8584             break;
8585           case MachinePlaysWhite:
8586           case IcsPlayingWhite:
8587             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8588             break;
8589           case AnalyzeMode:
8590           case AnalyzeFile:
8591             break;
8592           case IcsObserving: /* [DM] icsEngineAnalyze */
8593             if (!appData.icsEngineAnalyze) ignore = TRUE;
8594             break;
8595           case TwoMachinesPlay:
8596             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8597                 ignore = TRUE;
8598             }
8599             break;
8600           default:
8601             ignore = TRUE;
8602             break;
8603         }
8604
8605         if (!ignore) {
8606             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8607             buf1[0] = NULLCHAR;
8608             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8609                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8610
8611                 if (plyext != ' ' && plyext != '\t') {
8612                     time *= 100;
8613                 }
8614
8615                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8616                 if( cps->scoreIsAbsolute &&
8617                     ( gameMode == MachinePlaysBlack ||
8618                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8619                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8620                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8621                      !WhiteOnMove(currentMove)
8622                     ) )
8623                 {
8624                     curscore = -curscore;
8625                 }
8626
8627                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8628
8629                 if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
8630                         char buf[MSG_SIZ];
8631                         FILE *f;
8632                         snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName);
8633                         buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' :
8634                                              gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0];
8635                         if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf);
8636                         if(f = fopen(buf, "w")) { // export PV to applicable PV file
8637                                 fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
8638                                 fclose(f);
8639                         } else DisplayError(_("failed writing PV"), 0);
8640                 }
8641
8642                 tempStats.depth = plylev;
8643                 tempStats.nodes = nodes;
8644                 tempStats.time = time;
8645                 tempStats.score = curscore;
8646                 tempStats.got_only_move = 0;
8647
8648                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8649                         int ticklen;
8650
8651                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8652                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8653                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8654                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8655                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8656                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8657                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8658                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8659                 }
8660
8661                 /* Buffer overflow protection */
8662                 if (pv[0] != NULLCHAR) {
8663                     if (strlen(pv) >= sizeof(tempStats.movelist)
8664                         && appData.debugMode) {
8665                         fprintf(debugFP,
8666                                 "PV is too long; using the first %u bytes.\n",
8667                                 (unsigned) sizeof(tempStats.movelist) - 1);
8668                     }
8669
8670                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8671                 } else {
8672                     sprintf(tempStats.movelist, " no PV\n");
8673                 }
8674
8675                 if (tempStats.seen_stat) {
8676                     tempStats.ok_to_send = 1;
8677                 }
8678
8679                 if (strchr(tempStats.movelist, '(') != NULL) {
8680                     tempStats.line_is_book = 1;
8681                     tempStats.nr_moves = 0;
8682                     tempStats.moves_left = 0;
8683                 } else {
8684                     tempStats.line_is_book = 0;
8685                 }
8686
8687                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8688                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8689
8690                 SendProgramStatsToFrontend( cps, &tempStats );
8691
8692                 /*
8693                     [AS] Protect the thinkOutput buffer from overflow... this
8694                     is only useful if buf1 hasn't overflowed first!
8695                 */
8696                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8697                          plylev,
8698                          (gameMode == TwoMachinesPlay ?
8699                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8700                          ((double) curscore) / 100.0,
8701                          prefixHint ? lastHint : "",
8702                          prefixHint ? " " : "" );
8703
8704                 if( buf1[0] != NULLCHAR ) {
8705                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8706
8707                     if( strlen(pv) > max_len ) {
8708                         if( appData.debugMode) {
8709                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8710                         }
8711                         pv[max_len+1] = '\0';
8712                     }
8713
8714                     strcat( thinkOutput, pv);
8715                 }
8716
8717                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8718                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8719                     DisplayMove(currentMove - 1);
8720                 }
8721                 return;
8722
8723             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8724                 /* crafty (9.25+) says "(only move) <move>"
8725                  * if there is only 1 legal move
8726                  */
8727                 sscanf(p, "(only move) %s", buf1);
8728                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8729                 sprintf(programStats.movelist, "%s (only move)", buf1);
8730                 programStats.depth = 1;
8731                 programStats.nr_moves = 1;
8732                 programStats.moves_left = 1;
8733                 programStats.nodes = 1;
8734                 programStats.time = 1;
8735                 programStats.got_only_move = 1;
8736
8737                 /* Not really, but we also use this member to
8738                    mean "line isn't going to change" (Crafty
8739                    isn't searching, so stats won't change) */
8740                 programStats.line_is_book = 1;
8741
8742                 SendProgramStatsToFrontend( cps, &programStats );
8743
8744                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8745                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8746                     DisplayMove(currentMove - 1);
8747                 }
8748                 return;
8749             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8750                               &time, &nodes, &plylev, &mvleft,
8751                               &mvtot, mvname) >= 5) {
8752                 /* The stat01: line is from Crafty (9.29+) in response
8753                    to the "." command */
8754                 programStats.seen_stat = 1;
8755                 cps->maybeThinking = TRUE;
8756
8757                 if (programStats.got_only_move || !appData.periodicUpdates)
8758                   return;
8759
8760                 programStats.depth = plylev;
8761                 programStats.time = time;
8762                 programStats.nodes = nodes;
8763                 programStats.moves_left = mvleft;
8764                 programStats.nr_moves = mvtot;
8765                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8766                 programStats.ok_to_send = 1;
8767                 programStats.movelist[0] = '\0';
8768
8769                 SendProgramStatsToFrontend( cps, &programStats );
8770
8771                 return;
8772
8773             } else if (strncmp(message,"++",2) == 0) {
8774                 /* Crafty 9.29+ outputs this */
8775                 programStats.got_fail = 2;
8776                 return;
8777
8778             } else if (strncmp(message,"--",2) == 0) {
8779                 /* Crafty 9.29+ outputs this */
8780                 programStats.got_fail = 1;
8781                 return;
8782
8783             } else if (thinkOutput[0] != NULLCHAR &&
8784                        strncmp(message, "    ", 4) == 0) {
8785                 unsigned message_len;
8786
8787                 p = message;
8788                 while (*p && *p == ' ') p++;
8789
8790                 message_len = strlen( p );
8791
8792                 /* [AS] Avoid buffer overflow */
8793                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8794                     strcat(thinkOutput, " ");
8795                     strcat(thinkOutput, p);
8796                 }
8797
8798                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8799                     strcat(programStats.movelist, " ");
8800                     strcat(programStats.movelist, p);
8801                 }
8802
8803                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8804                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8805                     DisplayMove(currentMove - 1);
8806                 }
8807                 return;
8808             }
8809         }
8810         else {
8811             buf1[0] = NULLCHAR;
8812
8813             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8814                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8815             {
8816                 ChessProgramStats cpstats;
8817
8818                 if (plyext != ' ' && plyext != '\t') {
8819                     time *= 100;
8820                 }
8821
8822                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8823                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8824                     curscore = -curscore;
8825                 }
8826
8827                 cpstats.depth = plylev;
8828                 cpstats.nodes = nodes;
8829                 cpstats.time = time;
8830                 cpstats.score = curscore;
8831                 cpstats.got_only_move = 0;
8832                 cpstats.movelist[0] = '\0';
8833
8834                 if (buf1[0] != NULLCHAR) {
8835                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8836                 }
8837
8838                 cpstats.ok_to_send = 0;
8839                 cpstats.line_is_book = 0;
8840                 cpstats.nr_moves = 0;
8841                 cpstats.moves_left = 0;
8842
8843                 SendProgramStatsToFrontend( cps, &cpstats );
8844             }
8845         }
8846     }
8847 }
8848
8849
8850 /* Parse a game score from the character string "game", and
8851    record it as the history of the current game.  The game
8852    score is NOT assumed to start from the standard position.
8853    The display is not updated in any way.
8854    */
8855 void
8856 ParseGameHistory (char *game)
8857 {
8858     ChessMove moveType;
8859     int fromX, fromY, toX, toY, boardIndex;
8860     char promoChar;
8861     char *p, *q;
8862     char buf[MSG_SIZ];
8863
8864     if (appData.debugMode)
8865       fprintf(debugFP, "Parsing game history: %s\n", game);
8866
8867     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8868     gameInfo.site = StrSave(appData.icsHost);
8869     gameInfo.date = PGNDate();
8870     gameInfo.round = StrSave("-");
8871
8872     /* Parse out names of players */
8873     while (*game == ' ') game++;
8874     p = buf;
8875     while (*game != ' ') *p++ = *game++;
8876     *p = NULLCHAR;
8877     gameInfo.white = StrSave(buf);
8878     while (*game == ' ') game++;
8879     p = buf;
8880     while (*game != ' ' && *game != '\n') *p++ = *game++;
8881     *p = NULLCHAR;
8882     gameInfo.black = StrSave(buf);
8883
8884     /* Parse moves */
8885     boardIndex = blackPlaysFirst ? 1 : 0;
8886     yynewstr(game);
8887     for (;;) {
8888         yyboardindex = boardIndex;
8889         moveType = (ChessMove) Myylex();
8890         switch (moveType) {
8891           case IllegalMove:             /* maybe suicide chess, etc. */
8892   if (appData.debugMode) {
8893     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8894     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8895     setbuf(debugFP, NULL);
8896   }
8897           case WhitePromotion:
8898           case BlackPromotion:
8899           case WhiteNonPromotion:
8900           case BlackNonPromotion:
8901           case NormalMove:
8902           case WhiteCapturesEnPassant:
8903           case BlackCapturesEnPassant:
8904           case WhiteKingSideCastle:
8905           case WhiteQueenSideCastle:
8906           case BlackKingSideCastle:
8907           case BlackQueenSideCastle:
8908           case WhiteKingSideCastleWild:
8909           case WhiteQueenSideCastleWild:
8910           case BlackKingSideCastleWild:
8911           case BlackQueenSideCastleWild:
8912           /* PUSH Fabien */
8913           case WhiteHSideCastleFR:
8914           case WhiteASideCastleFR:
8915           case BlackHSideCastleFR:
8916           case BlackASideCastleFR:
8917           /* POP Fabien */
8918             fromX = currentMoveString[0] - AAA;
8919             fromY = currentMoveString[1] - ONE;
8920             toX = currentMoveString[2] - AAA;
8921             toY = currentMoveString[3] - ONE;
8922             promoChar = currentMoveString[4];
8923             break;
8924           case WhiteDrop:
8925           case BlackDrop:
8926             if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
8927             fromX = moveType == WhiteDrop ?
8928               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8929             (int) CharToPiece(ToLower(currentMoveString[0]));
8930             fromY = DROP_RANK;
8931             toX = currentMoveString[2] - AAA;
8932             toY = currentMoveString[3] - ONE;
8933             promoChar = NULLCHAR;
8934             break;
8935           case AmbiguousMove:
8936             /* bug? */
8937             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8938   if (appData.debugMode) {
8939     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8940     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8941     setbuf(debugFP, NULL);
8942   }
8943             DisplayError(buf, 0);
8944             return;
8945           case ImpossibleMove:
8946             /* bug? */
8947             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8948   if (appData.debugMode) {
8949     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8950     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8951     setbuf(debugFP, NULL);
8952   }
8953             DisplayError(buf, 0);
8954             return;
8955           case EndOfFile:
8956             if (boardIndex < backwardMostMove) {
8957                 /* Oops, gap.  How did that happen? */
8958                 DisplayError(_("Gap in move list"), 0);
8959                 return;
8960             }
8961             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8962             if (boardIndex > forwardMostMove) {
8963                 forwardMostMove = boardIndex;
8964             }
8965             return;
8966           case ElapsedTime:
8967             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8968                 strcat(parseList[boardIndex-1], " ");
8969                 strcat(parseList[boardIndex-1], yy_text);
8970             }
8971             continue;
8972           case Comment:
8973           case PGNTag:
8974           case NAG:
8975           default:
8976             /* ignore */
8977             continue;
8978           case WhiteWins:
8979           case BlackWins:
8980           case GameIsDrawn:
8981           case GameUnfinished:
8982             if (gameMode == IcsExamining) {
8983                 if (boardIndex < backwardMostMove) {
8984                     /* Oops, gap.  How did that happen? */
8985                     return;
8986                 }
8987                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8988                 return;
8989             }
8990             gameInfo.result = moveType;
8991             p = strchr(yy_text, '{');
8992             if (p == NULL) p = strchr(yy_text, '(');
8993             if (p == NULL) {
8994                 p = yy_text;
8995                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8996             } else {
8997                 q = strchr(p, *p == '{' ? '}' : ')');
8998                 if (q != NULL) *q = NULLCHAR;
8999                 p++;
9000             }
9001             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9002             gameInfo.resultDetails = StrSave(p);
9003             continue;
9004         }
9005         if (boardIndex >= forwardMostMove &&
9006             !(gameMode == IcsObserving && ics_gamenum == -1)) {
9007             backwardMostMove = blackPlaysFirst ? 1 : 0;
9008             return;
9009         }
9010         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
9011                                  fromY, fromX, toY, toX, promoChar,
9012                                  parseList[boardIndex]);
9013         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
9014         /* currentMoveString is set as a side-effect of yylex */
9015         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
9016         strcat(moveList[boardIndex], "\n");
9017         boardIndex++;
9018         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
9019         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
9020           case MT_NONE:
9021           case MT_STALEMATE:
9022           default:
9023             break;
9024           case MT_CHECK:
9025             if(gameInfo.variant != VariantShogi)
9026                 strcat(parseList[boardIndex - 1], "+");
9027             break;
9028           case MT_CHECKMATE:
9029           case MT_STAINMATE:
9030             strcat(parseList[boardIndex - 1], "#");
9031             break;
9032         }
9033     }
9034 }
9035
9036
9037 /* Apply a move to the given board  */
9038 void
9039 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
9040 {
9041   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9042   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9043
9044     /* [HGM] compute & store e.p. status and castling rights for new position */
9045     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9046
9047       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9048       oldEP = (signed char)board[EP_STATUS];
9049       board[EP_STATUS] = EP_NONE;
9050
9051   if (fromY == DROP_RANK) {
9052         /* must be first */
9053         if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change.
9054             board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible
9055             return;
9056         }
9057         piece = board[toY][toX] = (ChessSquare) fromX;
9058   } else {
9059       int i;
9060
9061       if( board[toY][toX] != EmptySquare )
9062            board[EP_STATUS] = EP_CAPTURE;
9063
9064       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9065            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9066                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9067       } else
9068       if( board[fromY][fromX] == WhitePawn ) {
9069            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9070                board[EP_STATUS] = EP_PAWN_MOVE;
9071            if( toY-fromY==2) {
9072                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9073                         gameInfo.variant != VariantBerolina || toX < fromX)
9074                       board[EP_STATUS] = toX | berolina;
9075                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9076                         gameInfo.variant != VariantBerolina || toX > fromX)
9077                       board[EP_STATUS] = toX;
9078            }
9079       } else
9080       if( board[fromY][fromX] == BlackPawn ) {
9081            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9082                board[EP_STATUS] = EP_PAWN_MOVE;
9083            if( toY-fromY== -2) {
9084                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9085                         gameInfo.variant != VariantBerolina || toX < fromX)
9086                       board[EP_STATUS] = toX | berolina;
9087                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9088                         gameInfo.variant != VariantBerolina || toX > fromX)
9089                       board[EP_STATUS] = toX;
9090            }
9091        }
9092
9093        for(i=0; i<nrCastlingRights; i++) {
9094            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9095               board[CASTLING][i] == toX   && castlingRank[i] == toY
9096              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9097        }
9098
9099      if (fromX == toX && fromY == toY) return;
9100
9101      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9102      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9103      if(gameInfo.variant == VariantKnightmate)
9104          king += (int) WhiteUnicorn - (int) WhiteKing;
9105
9106     /* Code added by Tord: */
9107     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9108     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9109         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9110       board[fromY][fromX] = EmptySquare;
9111       board[toY][toX] = EmptySquare;
9112       if((toX > fromX) != (piece == WhiteRook)) {
9113         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9114       } else {
9115         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9116       }
9117     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9118                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9119       board[fromY][fromX] = EmptySquare;
9120       board[toY][toX] = EmptySquare;
9121       if((toX > fromX) != (piece == BlackRook)) {
9122         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9123       } else {
9124         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9125       }
9126     /* End of code added by Tord */
9127
9128     } else if (board[fromY][fromX] == king
9129         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9130         && toY == fromY && toX > fromX+1) {
9131         board[fromY][fromX] = EmptySquare;
9132         board[toY][toX] = king;
9133         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9134         board[fromY][BOARD_RGHT-1] = EmptySquare;
9135     } else if (board[fromY][fromX] == king
9136         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9137                && toY == fromY && toX < fromX-1) {
9138         board[fromY][fromX] = EmptySquare;
9139         board[toY][toX] = king;
9140         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9141         board[fromY][BOARD_LEFT] = EmptySquare;
9142     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9143                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9144                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9145                ) {
9146         /* white pawn promotion */
9147         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9148         if(gameInfo.variant==VariantBughouse ||
9149            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9150             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9151         board[fromY][fromX] = EmptySquare;
9152     } else if ((fromY >= BOARD_HEIGHT>>1)
9153                && (toX != fromX)
9154                && gameInfo.variant != VariantXiangqi
9155                && gameInfo.variant != VariantBerolina
9156                && (board[fromY][fromX] == WhitePawn)
9157                && (board[toY][toX] == EmptySquare)) {
9158         board[fromY][fromX] = EmptySquare;
9159         board[toY][toX] = WhitePawn;
9160         captured = board[toY - 1][toX];
9161         board[toY - 1][toX] = EmptySquare;
9162     } else if ((fromY == BOARD_HEIGHT-4)
9163                && (toX == fromX)
9164                && gameInfo.variant == VariantBerolina
9165                && (board[fromY][fromX] == WhitePawn)
9166                && (board[toY][toX] == EmptySquare)) {
9167         board[fromY][fromX] = EmptySquare;
9168         board[toY][toX] = WhitePawn;
9169         if(oldEP & EP_BEROLIN_A) {
9170                 captured = board[fromY][fromX-1];
9171                 board[fromY][fromX-1] = EmptySquare;
9172         }else{  captured = board[fromY][fromX+1];
9173                 board[fromY][fromX+1] = EmptySquare;
9174         }
9175     } else if (board[fromY][fromX] == king
9176         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9177                && toY == fromY && toX > fromX+1) {
9178         board[fromY][fromX] = EmptySquare;
9179         board[toY][toX] = king;
9180         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9181         board[fromY][BOARD_RGHT-1] = EmptySquare;
9182     } else if (board[fromY][fromX] == king
9183         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9184                && toY == fromY && toX < fromX-1) {
9185         board[fromY][fromX] = EmptySquare;
9186         board[toY][toX] = king;
9187         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9188         board[fromY][BOARD_LEFT] = EmptySquare;
9189     } else if (fromY == 7 && fromX == 3
9190                && board[fromY][fromX] == BlackKing
9191                && toY == 7 && toX == 5) {
9192         board[fromY][fromX] = EmptySquare;
9193         board[toY][toX] = BlackKing;
9194         board[fromY][7] = EmptySquare;
9195         board[toY][4] = BlackRook;
9196     } else if (fromY == 7 && fromX == 3
9197                && board[fromY][fromX] == BlackKing
9198                && toY == 7 && toX == 1) {
9199         board[fromY][fromX] = EmptySquare;
9200         board[toY][toX] = BlackKing;
9201         board[fromY][0] = EmptySquare;
9202         board[toY][2] = BlackRook;
9203     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9204                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9205                && toY < promoRank && promoChar
9206                ) {
9207         /* black pawn promotion */
9208         board[toY][toX] = CharToPiece(ToLower(promoChar));
9209         if(gameInfo.variant==VariantBughouse ||
9210            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9211             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9212         board[fromY][fromX] = EmptySquare;
9213     } else if ((fromY < BOARD_HEIGHT>>1)
9214                && (toX != fromX)
9215                && gameInfo.variant != VariantXiangqi
9216                && gameInfo.variant != VariantBerolina
9217                && (board[fromY][fromX] == BlackPawn)
9218                && (board[toY][toX] == EmptySquare)) {
9219         board[fromY][fromX] = EmptySquare;
9220         board[toY][toX] = BlackPawn;
9221         captured = board[toY + 1][toX];
9222         board[toY + 1][toX] = EmptySquare;
9223     } else if ((fromY == 3)
9224                && (toX == fromX)
9225                && gameInfo.variant == VariantBerolina
9226                && (board[fromY][fromX] == BlackPawn)
9227                && (board[toY][toX] == EmptySquare)) {
9228         board[fromY][fromX] = EmptySquare;
9229         board[toY][toX] = BlackPawn;
9230         if(oldEP & EP_BEROLIN_A) {
9231                 captured = board[fromY][fromX-1];
9232                 board[fromY][fromX-1] = EmptySquare;
9233         }else{  captured = board[fromY][fromX+1];
9234                 board[fromY][fromX+1] = EmptySquare;
9235         }
9236     } else {
9237         board[toY][toX] = board[fromY][fromX];
9238         board[fromY][fromX] = EmptySquare;
9239     }
9240   }
9241
9242     if (gameInfo.holdingsWidth != 0) {
9243
9244       /* !!A lot more code needs to be written to support holdings  */
9245       /* [HGM] OK, so I have written it. Holdings are stored in the */
9246       /* penultimate board files, so they are automaticlly stored   */
9247       /* in the game history.                                       */
9248       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9249                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9250         /* Delete from holdings, by decreasing count */
9251         /* and erasing image if necessary            */
9252         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9253         if(p < (int) BlackPawn) { /* white drop */
9254              p -= (int)WhitePawn;
9255                  p = PieceToNumber((ChessSquare)p);
9256              if(p >= gameInfo.holdingsSize) p = 0;
9257              if(--board[p][BOARD_WIDTH-2] <= 0)
9258                   board[p][BOARD_WIDTH-1] = EmptySquare;
9259              if((int)board[p][BOARD_WIDTH-2] < 0)
9260                         board[p][BOARD_WIDTH-2] = 0;
9261         } else {                  /* black drop */
9262              p -= (int)BlackPawn;
9263                  p = PieceToNumber((ChessSquare)p);
9264              if(p >= gameInfo.holdingsSize) p = 0;
9265              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9266                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9267              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9268                         board[BOARD_HEIGHT-1-p][1] = 0;
9269         }
9270       }
9271       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9272           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9273         /* [HGM] holdings: Add to holdings, if holdings exist */
9274         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9275                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9276                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9277         }
9278         p = (int) captured;
9279         if (p >= (int) BlackPawn) {
9280           p -= (int)BlackPawn;
9281           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9282                   /* in Shogi restore piece to its original  first */
9283                   captured = (ChessSquare) (DEMOTED captured);
9284                   p = DEMOTED p;
9285           }
9286           p = PieceToNumber((ChessSquare)p);
9287           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9288           board[p][BOARD_WIDTH-2]++;
9289           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9290         } else {
9291           p -= (int)WhitePawn;
9292           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9293                   captured = (ChessSquare) (DEMOTED captured);
9294                   p = DEMOTED p;
9295           }
9296           p = PieceToNumber((ChessSquare)p);
9297           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9298           board[BOARD_HEIGHT-1-p][1]++;
9299           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9300         }
9301       }
9302     } else if (gameInfo.variant == VariantAtomic) {
9303       if (captured != EmptySquare) {
9304         int y, x;
9305         for (y = toY-1; y <= toY+1; y++) {
9306           for (x = toX-1; x <= toX+1; x++) {
9307             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9308                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9309               board[y][x] = EmptySquare;
9310             }
9311           }
9312         }
9313         board[toY][toX] = EmptySquare;
9314       }
9315     }
9316     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9317         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9318     } else
9319     if(promoChar == '+') {
9320         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9321         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9322     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9323         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9324     }
9325     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9326                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9327         // [HGM] superchess: take promotion piece out of holdings
9328         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9329         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9330             if(!--board[k][BOARD_WIDTH-2])
9331                 board[k][BOARD_WIDTH-1] = EmptySquare;
9332         } else {
9333             if(!--board[BOARD_HEIGHT-1-k][1])
9334                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9335         }
9336     }
9337
9338 }
9339
9340 /* Updates forwardMostMove */
9341 void
9342 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
9343 {
9344 //    forwardMostMove++; // [HGM] bare: moved downstream
9345
9346     (void) CoordsToAlgebraic(boards[forwardMostMove],
9347                              PosFlags(forwardMostMove),
9348                              fromY, fromX, toY, toX, promoChar,
9349                              parseList[forwardMostMove]);
9350
9351     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9352         int timeLeft; static int lastLoadFlag=0; int king, piece;
9353         piece = boards[forwardMostMove][fromY][fromX];
9354         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9355         if(gameInfo.variant == VariantKnightmate)
9356             king += (int) WhiteUnicorn - (int) WhiteKing;
9357         if(forwardMostMove == 0) {
9358             if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame)
9359                 fprintf(serverMoves, "%s;", UserName());
9360             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b')
9361                 fprintf(serverMoves, "%s;", second.tidy);
9362             fprintf(serverMoves, "%s;", first.tidy);
9363             if(gameMode == MachinePlaysWhite)
9364                 fprintf(serverMoves, "%s;", UserName());
9365             else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
9366                 fprintf(serverMoves, "%s;", second.tidy);
9367         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9368         lastLoadFlag = loadFlag;
9369         // print base move
9370         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9371         // print castling suffix
9372         if( toY == fromY && piece == king ) {
9373             if(toX-fromX > 1)
9374                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9375             if(fromX-toX >1)
9376                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9377         }
9378         // e.p. suffix
9379         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9380              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9381              boards[forwardMostMove][toY][toX] == EmptySquare
9382              && fromX != toX && fromY != toY)
9383                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9384         // promotion suffix
9385         if(promoChar != NULLCHAR)
9386                 fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
9387         if(!loadFlag) {
9388                 char buf[MOVE_LEN*2], *p; int len;
9389             fprintf(serverMoves, "/%d/%d",
9390                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9391             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9392             else                      timeLeft = blackTimeRemaining/1000;
9393             fprintf(serverMoves, "/%d", timeLeft);
9394                 strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
9395                 if(p = strchr(buf, '=')) *p = NULLCHAR;
9396                 len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
9397             fprintf(serverMoves, "/%s", buf);
9398         }
9399         fflush(serverMoves);
9400     }
9401
9402     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations..
9403         GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD);
9404       return;
9405     }
9406     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9407     if (commentList[forwardMostMove+1] != NULL) {
9408         free(commentList[forwardMostMove+1]);
9409         commentList[forwardMostMove+1] = NULL;
9410     }
9411     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9412     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9413     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9414     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9415     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9416     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9417     adjustedClock = FALSE;
9418     gameInfo.result = GameUnfinished;
9419     if (gameInfo.resultDetails != NULL) {
9420         free(gameInfo.resultDetails);
9421         gameInfo.resultDetails = NULL;
9422     }
9423     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9424                               moveList[forwardMostMove - 1]);
9425     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9426       case MT_NONE:
9427       case MT_STALEMATE:
9428       default:
9429         break;
9430       case MT_CHECK:
9431         if(gameInfo.variant != VariantShogi)
9432             strcat(parseList[forwardMostMove - 1], "+");
9433         break;
9434       case MT_CHECKMATE:
9435       case MT_STAINMATE:
9436         strcat(parseList[forwardMostMove - 1], "#");
9437         break;
9438     }
9439     if (appData.debugMode) {
9440         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9441     }
9442
9443 }
9444
9445 /* Updates currentMove if not pausing */
9446 void
9447 ShowMove (int fromX, int fromY, int toX, int toY)
9448 {
9449     int instant = (gameMode == PlayFromGameFile) ?
9450         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9451     if(appData.noGUI) return;
9452     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9453         if (!instant) {
9454             if (forwardMostMove == currentMove + 1) {
9455                 AnimateMove(boards[forwardMostMove - 1],
9456                             fromX, fromY, toX, toY);
9457             }
9458             if (appData.highlightLastMove) {
9459                 SetHighlights(fromX, fromY, toX, toY);
9460             }
9461         }
9462         currentMove = forwardMostMove;
9463     }
9464
9465     if (instant) return;
9466
9467     DisplayMove(currentMove - 1);
9468     DrawPosition(FALSE, boards[currentMove]);
9469     DisplayBothClocks();
9470     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9471 }
9472
9473 void
9474 SendEgtPath (ChessProgramState *cps)
9475 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9476         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9477
9478         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9479
9480         while(*p) {
9481             char c, *q = name+1, *r, *s;
9482
9483             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9484             while(*p && *p != ',') *q++ = *p++;
9485             *q++ = ':'; *q = 0;
9486             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9487                 strcmp(name, ",nalimov:") == 0 ) {
9488                 // take nalimov path from the menu-changeable option first, if it is defined
9489               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9490                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9491             } else
9492             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9493                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9494                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9495                 s = r = StrStr(s, ":") + 1; // beginning of path info
9496                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9497                 c = *r; *r = 0;             // temporarily null-terminate path info
9498                     *--q = 0;               // strip of trailig ':' from name
9499                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9500                 *r = c;
9501                 SendToProgram(buf,cps);     // send egtbpath command for this format
9502             }
9503             if(*p == ',') p++; // read away comma to position for next format name
9504         }
9505 }
9506
9507 void
9508 InitChessProgram (ChessProgramState *cps, int setup)
9509 /* setup needed to setup FRC opening position */
9510 {
9511     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9512     if (appData.noChessProgram) return;
9513     hintRequested = FALSE;
9514     bookRequested = FALSE;
9515
9516     ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features
9517     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9518     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9519     if(cps->memSize) { /* [HGM] memory */
9520       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9521         SendToProgram(buf, cps);
9522     }
9523     SendEgtPath(cps); /* [HGM] EGT */
9524     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9525       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9526         SendToProgram(buf, cps);
9527     }
9528
9529     SendToProgram(cps->initString, cps);
9530     if (gameInfo.variant != VariantNormal &&
9531         gameInfo.variant != VariantLoadable
9532         /* [HGM] also send variant if board size non-standard */
9533         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9534                                             ) {
9535       char *v = VariantName(gameInfo.variant);
9536       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9537         /* [HGM] in protocol 1 we have to assume all variants valid */
9538         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9539         DisplayFatalError(buf, 0, 1);
9540         return;
9541       }
9542
9543       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9544       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9545       if( gameInfo.variant == VariantXiangqi )
9546            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9547       if( gameInfo.variant == VariantShogi )
9548            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9549       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9550            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9551       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9552           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9553            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9554       if( gameInfo.variant == VariantCourier )
9555            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9556       if( gameInfo.variant == VariantSuper )
9557            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9558       if( gameInfo.variant == VariantGreat )
9559            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9560       if( gameInfo.variant == VariantSChess )
9561            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9562       if( gameInfo.variant == VariantGrand )
9563            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9564
9565       if(overruled) {
9566         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9567                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9568            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9569            if(StrStr(cps->variants, b) == NULL) {
9570                // specific sized variant not known, check if general sizing allowed
9571                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9572                    if(StrStr(cps->variants, "boardsize") == NULL) {
9573                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9574                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9575                        DisplayFatalError(buf, 0, 1);
9576                        return;
9577                    }
9578                    /* [HGM] here we really should compare with the maximum supported board size */
9579                }
9580            }
9581       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9582       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9583       SendToProgram(buf, cps);
9584     }
9585     currentlyInitializedVariant = gameInfo.variant;
9586
9587     /* [HGM] send opening position in FRC to first engine */
9588     if(setup) {
9589           SendToProgram("force\n", cps);
9590           SendBoard(cps, 0);
9591           /* engine is now in force mode! Set flag to wake it up after first move. */
9592           setboardSpoiledMachineBlack = 1;
9593     }
9594
9595     if (cps->sendICS) {
9596       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9597       SendToProgram(buf, cps);
9598     }
9599     cps->maybeThinking = FALSE;
9600     cps->offeredDraw = 0;
9601     if (!appData.icsActive) {
9602         SendTimeControl(cps, movesPerSession, timeControl,
9603                         timeIncrement, appData.searchDepth,
9604                         searchTime);
9605     }
9606     if (appData.showThinking
9607         // [HGM] thinking: four options require thinking output to be sent
9608         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9609                                 ) {
9610         SendToProgram("post\n", cps);
9611     }
9612     SendToProgram("hard\n", cps);
9613     if (!appData.ponderNextMove) {
9614         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9615            it without being sure what state we are in first.  "hard"
9616            is not a toggle, so that one is OK.
9617          */
9618         SendToProgram("easy\n", cps);
9619     }
9620     if (cps->usePing) {
9621       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9622       SendToProgram(buf, cps);
9623     }
9624     cps->initDone = TRUE;
9625     ClearEngineOutputPane(cps == &second);
9626 }
9627
9628
9629 void
9630 StartChessProgram (ChessProgramState *cps)
9631 {
9632     char buf[MSG_SIZ];
9633     int err;
9634
9635     if (appData.noChessProgram) return;
9636     cps->initDone = FALSE;
9637
9638     if (strcmp(cps->host, "localhost") == 0) {
9639         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9640     } else if (*appData.remoteShell == NULLCHAR) {
9641         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9642     } else {
9643         if (*appData.remoteUser == NULLCHAR) {
9644           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9645                     cps->program);
9646         } else {
9647           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9648                     cps->host, appData.remoteUser, cps->program);
9649         }
9650         err = StartChildProcess(buf, "", &cps->pr);
9651     }
9652
9653     if (err != 0) {
9654       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9655         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9656         if(cps != &first) return;
9657         appData.noChessProgram = TRUE;
9658         ThawUI();
9659         SetNCPMode();
9660 //      DisplayFatalError(buf, err, 1);
9661 //      cps->pr = NoProc;
9662 //      cps->isr = NULL;
9663         return;
9664     }
9665
9666     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9667     if (cps->protocolVersion > 1) {
9668       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9669       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9670       cps->comboCnt = 0;  //                and values of combo boxes
9671       SendToProgram(buf, cps);
9672     } else {
9673       SendToProgram("xboard\n", cps);
9674     }
9675 }
9676
9677 void
9678 TwoMachinesEventIfReady P((void))
9679 {
9680   static int curMess = 0;
9681   if (first.lastPing != first.lastPong) {
9682     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9683     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9684     return;
9685   }
9686   if (second.lastPing != second.lastPong) {
9687     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9688     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9689     return;
9690   }
9691   DisplayMessage("", ""); curMess = 0;
9692   ThawUI();
9693   TwoMachinesEvent();
9694 }
9695
9696 char *
9697 MakeName (char *template)
9698 {
9699     time_t clock;
9700     struct tm *tm;
9701     static char buf[MSG_SIZ];
9702     char *p = buf;
9703     int i;
9704
9705     clock = time((time_t *)NULL);
9706     tm = localtime(&clock);
9707
9708     while(*p++ = *template++) if(p[-1] == '%') {
9709         switch(*template++) {
9710           case 0:   *p = 0; return buf;
9711           case 'Y': i = tm->tm_year+1900; break;
9712           case 'y': i = tm->tm_year-100; break;
9713           case 'M': i = tm->tm_mon+1; break;
9714           case 'd': i = tm->tm_mday; break;
9715           case 'h': i = tm->tm_hour; break;
9716           case 'm': i = tm->tm_min; break;
9717           case 's': i = tm->tm_sec; break;
9718           default:  i = 0;
9719         }
9720         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9721     }
9722     return buf;
9723 }
9724
9725 int
9726 CountPlayers (char *p)
9727 {
9728     int n = 0;
9729     while(p = strchr(p, '\n')) p++, n++; // count participants
9730     return n;
9731 }
9732
9733 FILE *
9734 WriteTourneyFile (char *results, FILE *f)
9735 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9736     if(f == NULL) f = fopen(appData.tourneyFile, "w");
9737     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9738         // create a file with tournament description
9739         fprintf(f, "-participants {%s}\n", appData.participants);
9740         fprintf(f, "-seedBase %d\n", appData.seedBase);
9741         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9742         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9743         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9744         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9745         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9746         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9747         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9748         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9749         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9750         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9751         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9752         fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false");
9753         if(searchTime > 0)
9754                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9755         else {
9756                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9757                 fprintf(f, "-tc %s\n", appData.timeControl);
9758                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9759         }
9760         fprintf(f, "-results \"%s\"\n", results);
9761     }
9762     return f;
9763 }
9764
9765 #define MAXENGINES 1000
9766 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9767
9768 void
9769 Substitute (char *participants, int expunge)
9770 {
9771     int i, changed, changes=0, nPlayers=0;
9772     char *p, *q, *r, buf[MSG_SIZ];
9773     if(participants == NULL) return;
9774     if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
9775     r = p = participants; q = appData.participants;
9776     while(*p && *p == *q) {
9777         if(*p == '\n') r = p+1, nPlayers++;
9778         p++; q++;
9779     }
9780     if(*p) { // difference
9781         while(*p && *p++ != '\n');
9782         while(*q && *q++ != '\n');
9783       changed = nPlayers;
9784         changes = 1 + (strcmp(p, q) != 0);
9785     }
9786     if(changes == 1) { // a single engine mnemonic was changed
9787         q = r; while(*q) nPlayers += (*q++ == '\n');
9788         p = buf; while(*r && (*p = *r++) != '\n') p++;
9789         *p = NULLCHAR;
9790         NamesToList(firstChessProgramNames, command, mnemonic);
9791         for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
9792         if(mnemonic[i]) { // The substitute is valid
9793             FILE *f;
9794             if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
9795                 flock(fileno(f), LOCK_EX);
9796                 ParseArgsFromFile(f);
9797                 fseek(f, 0, SEEK_SET);
9798                 FREE(appData.participants); appData.participants = participants;
9799                 if(expunge) { // erase results of replaced engine
9800                     int len = strlen(appData.results), w, b, dummy;
9801                     for(i=0; i<len; i++) {
9802                         Pairing(i, nPlayers, &w, &b, &dummy);
9803                         if((w == changed || b == changed) && appData.results[i] == '*') {
9804                             DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
9805                             fclose(f);
9806                             return;
9807                         }
9808                     }
9809                     for(i=0; i<len; i++) {
9810                         Pairing(i, nPlayers, &w, &b, &dummy);
9811                         if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
9812                     }
9813                 }
9814                 WriteTourneyFile(appData.results, f);
9815                 fclose(f); // release lock
9816                 return;
9817             }
9818         } else DisplayError(_("No engine with the name you gave is installed"), 0);
9819     }
9820     if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
9821     if(changes > 1)  DisplayError(_("You can only change one engine at the time"), 0);
9822     free(participants);
9823     return;
9824 }
9825
9826 int
9827 CreateTourney (char *name)
9828 {
9829         FILE *f;
9830         if(matchMode && strcmp(name, appData.tourneyFile)) {
9831              ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
9832         }
9833         if(name[0] == NULLCHAR) {
9834             if(appData.participants[0])
9835                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9836             return 0;
9837         }
9838         f = fopen(name, "r");
9839         if(f) { // file exists
9840             ASSIGN(appData.tourneyFile, name);
9841             ParseArgsFromFile(f); // parse it
9842         } else {
9843             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9844             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9845                 DisplayError(_("Not enough participants"), 0);
9846                 return 0;
9847             }
9848             ASSIGN(appData.tourneyFile, name);
9849             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9850             if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
9851         }
9852         fclose(f);
9853         appData.noChessProgram = FALSE;
9854         appData.clockMode = TRUE;
9855         SetGNUMode();
9856         return 1;
9857 }
9858
9859 void
9860 NamesToList (char *names, char **engineList, char **engineMnemonic)
9861 {
9862     char buf[MSG_SIZ], *p, *q;
9863     int i=1;
9864     while(*names) {
9865         p = names; q = buf;
9866         while(*p && *p != '\n') *q++ = *p++;
9867         *q = 0;
9868         if(engineList[i]) free(engineList[i]);
9869         engineList[i] = strdup(buf);
9870         if(*p == '\n') p++;
9871         TidyProgramName(engineList[i], "localhost", buf);
9872         if(engineMnemonic[i]) free(engineMnemonic[i]);
9873         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9874             strcat(buf, " (");
9875             sscanf(q + 8, "%s", buf + strlen(buf));
9876             strcat(buf, ")");
9877         }
9878         engineMnemonic[i] = strdup(buf);
9879         names = p; i++;
9880       if(i > MAXENGINES - 2) break;
9881     }
9882     engineList[i] = engineMnemonic[i] = NULL;
9883 }
9884
9885 // following implemented as macro to avoid type limitations
9886 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9887
9888 void
9889 SwapEngines (int n)
9890 {   // swap settings for first engine and other engine (so far only some selected options)
9891     int h;
9892     char *p;
9893     if(n == 0) return;
9894     SWAP(directory, p)
9895     SWAP(chessProgram, p)
9896     SWAP(isUCI, h)
9897     SWAP(hasOwnBookUCI, h)
9898     SWAP(protocolVersion, h)
9899     SWAP(reuse, h)
9900     SWAP(scoreIsAbsolute, h)
9901     SWAP(timeOdds, h)
9902     SWAP(logo, p)
9903     SWAP(pgnName, p)
9904     SWAP(pvSAN, h)
9905     SWAP(engOptions, p)
9906 }
9907
9908 void
9909 SetPlayer (int player)
9910 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9911     int i;
9912     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9913     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9914     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9915     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9916     if(mnemonic[i]) {
9917         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9918         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
9919         appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER;
9920         ParseArgsFromString(buf);
9921     }
9922     free(engineName);
9923 }
9924
9925 int
9926 Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9927 {   // determine players from game number
9928     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9929
9930     if(appData.tourneyType == 0) {
9931         roundsPerCycle = (nPlayers - 1) | 1;
9932         pairingsPerRound = nPlayers / 2;
9933     } else if(appData.tourneyType > 0) {
9934         roundsPerCycle = nPlayers - appData.tourneyType;
9935         pairingsPerRound = appData.tourneyType;
9936     }
9937     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9938     gamesPerCycle = gamesPerRound * roundsPerCycle;
9939     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9940     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9941     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9942     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9943     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9944     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9945
9946     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9947     if(appData.roundSync) *syncInterval = gamesPerRound;
9948
9949     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9950
9951     if(appData.tourneyType == 0) {
9952         if(curPairing == (nPlayers-1)/2 ) {
9953             *whitePlayer = curRound;
9954             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9955         } else {
9956             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9957             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9958             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9959             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9960         }
9961     } else if(appData.tourneyType > 1) {
9962         *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round
9963         *whitePlayer = curRound + appData.tourneyType;
9964     } else if(appData.tourneyType > 0) {
9965         *whitePlayer = curPairing;
9966         *blackPlayer = curRound + appData.tourneyType;
9967     }
9968
9969     // take care of white/black alternation per round. 
9970     // For cycles and games this is already taken care of by default, derived from matchGame!
9971     return curRound & 1;
9972 }
9973
9974 int
9975 NextTourneyGame (int nr, int *swapColors)
9976 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9977     char *p, *q;
9978     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9979     FILE *tf;
9980     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9981     tf = fopen(appData.tourneyFile, "r");
9982     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9983     ParseArgsFromFile(tf); fclose(tf);
9984     InitTimeControls(); // TC might be altered from tourney file
9985
9986     nPlayers = CountPlayers(appData.participants); // count participants
9987     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9988     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9989
9990     if(syncInterval) {
9991         p = q = appData.results;
9992         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9993         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9994             DisplayMessage(_("Waiting for other game(s)"),"");
9995             waitingForGame = TRUE;
9996             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9997             return 0;
9998         }
9999         waitingForGame = FALSE;
10000     }
10001
10002     if(appData.tourneyType < 0) {
10003         if(nr>=0 && !pairingReceived) {
10004             char buf[1<<16];
10005             if(pairing.pr == NoProc) {
10006                 if(!appData.pairingEngine[0]) {
10007                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
10008                     return 0;
10009                 }
10010                 StartChessProgram(&pairing); // starts the pairing engine
10011             }
10012             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
10013             SendToProgram(buf, &pairing);
10014             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
10015             SendToProgram(buf, &pairing);
10016             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
10017         }
10018         pairingReceived = 0;                              // ... so we continue here 
10019         *swapColors = 0;
10020         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
10021         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
10022         matchGame = 1; roundNr = nr / syncInterval + 1;
10023     }
10024
10025     if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded
10026
10027     // redefine engines, engine dir, etc.
10028     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
10029     if(first.pr == NoProc || nr < 0) {
10030       SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
10031       InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
10032     }
10033     if(second.pr == NoProc) {
10034       SwapEngines(1);
10035       SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
10036       SwapEngines(1);         // and make that valid for second engine by swapping
10037       InitEngine(&second, 1);
10038     }
10039     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
10040     UpdateLogos(FALSE);     // leave display to ModeHiglight()
10041     return 1;
10042 }
10043
10044 void
10045 NextMatchGame ()
10046 {   // performs game initialization that does not invoke engines, and then tries to start the game
10047     int res, firstWhite, swapColors = 0;
10048     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
10049     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
10050     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
10051     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
10052     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
10053     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
10054     if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
10055     Reset(FALSE, first.pr != NoProc);
10056     res = LoadGameOrPosition(matchGame); // setup game
10057     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
10058     if(!res) return; // abort when bad game/pos file
10059     TwoMachinesEvent();
10060 }
10061
10062 void
10063 UserAdjudicationEvent (int result)
10064 {
10065     ChessMove gameResult = GameIsDrawn;
10066
10067     if( result > 0 ) {
10068         gameResult = WhiteWins;
10069     }
10070     else if( result < 0 ) {
10071         gameResult = BlackWins;
10072     }
10073
10074     if( gameMode == TwoMachinesPlay ) {
10075         GameEnds( gameResult, "User adjudication", GE_XBOARD );
10076     }
10077 }
10078
10079
10080 // [HGM] save: calculate checksum of game to make games easily identifiable
10081 int
10082 StringCheckSum (char *s)
10083 {
10084         int i = 0;
10085         if(s==NULL) return 0;
10086         while(*s) i = i*259 + *s++;
10087         return i;
10088 }
10089
10090 int
10091 GameCheckSum ()
10092 {
10093         int i, sum=0;
10094         for(i=backwardMostMove; i<forwardMostMove; i++) {
10095                 sum += pvInfoList[i].depth;
10096                 sum += StringCheckSum(parseList[i]);
10097                 sum += StringCheckSum(commentList[i]);
10098                 sum *= 261;
10099         }
10100         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
10101         return sum + StringCheckSum(commentList[i]);
10102 } // end of save patch
10103
10104 void
10105 GameEnds (ChessMove result, char *resultDetails, int whosays)
10106 {
10107     GameMode nextGameMode;
10108     int isIcsGame;
10109     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
10110
10111     if(endingGame) return; /* [HGM] crash: forbid recursion */
10112     endingGame = 1;
10113     if(twoBoards) { // [HGM] dual: switch back to one board
10114         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
10115         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
10116     }
10117     if (appData.debugMode) {
10118       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
10119               result, resultDetails ? resultDetails : "(null)", whosays);
10120     }
10121
10122     fromX = fromY = -1; // [HGM] abort any move the user is entering.
10123
10124     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
10125         /* If we are playing on ICS, the server decides when the
10126            game is over, but the engine can offer to draw, claim
10127            a draw, or resign.
10128          */
10129 #if ZIPPY
10130         if (appData.zippyPlay && first.initDone) {
10131             if (result == GameIsDrawn) {
10132                 /* In case draw still needs to be claimed */
10133                 SendToICS(ics_prefix);
10134                 SendToICS("draw\n");
10135             } else if (StrCaseStr(resultDetails, "resign")) {
10136                 SendToICS(ics_prefix);
10137                 SendToICS("resign\n");
10138             }
10139         }
10140 #endif
10141         endingGame = 0; /* [HGM] crash */
10142         return;
10143     }
10144
10145     /* If we're loading the game from a file, stop */
10146     if (whosays == GE_FILE) {
10147       (void) StopLoadGameTimer();
10148       gameFileFP = NULL;
10149     }
10150
10151     /* Cancel draw offers */
10152     first.offeredDraw = second.offeredDraw = 0;
10153
10154     /* If this is an ICS game, only ICS can really say it's done;
10155        if not, anyone can. */
10156     isIcsGame = (gameMode == IcsPlayingWhite ||
10157                  gameMode == IcsPlayingBlack ||
10158                  gameMode == IcsObserving    ||
10159                  gameMode == IcsExamining);
10160
10161     if (!isIcsGame || whosays == GE_ICS) {
10162         /* OK -- not an ICS game, or ICS said it was done */
10163         StopClocks();
10164         if (!isIcsGame && !appData.noChessProgram)
10165           SetUserThinkingEnables();
10166
10167         /* [HGM] if a machine claims the game end we verify this claim */
10168         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10169             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10170                 char claimer;
10171                 ChessMove trueResult = (ChessMove) -1;
10172
10173                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10174                                             first.twoMachinesColor[0] :
10175                                             second.twoMachinesColor[0] ;
10176
10177                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10178                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10179                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10180                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10181                 } else
10182                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10183                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10184                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10185                 } else
10186                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10187                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10188                 }
10189
10190                 // now verify win claims, but not in drop games, as we don't understand those yet
10191                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10192                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10193                     (result == WhiteWins && claimer == 'w' ||
10194                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10195                       if (appData.debugMode) {
10196                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10197                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10198                       }
10199                       if(result != trueResult) {
10200                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10201                               result = claimer == 'w' ? BlackWins : WhiteWins;
10202                               resultDetails = buf;
10203                       }
10204                 } else
10205                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10206                     && (forwardMostMove <= backwardMostMove ||
10207                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10208                         (claimer=='b')==(forwardMostMove&1))
10209                                                                                   ) {
10210                       /* [HGM] verify: draws that were not flagged are false claims */
10211                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10212                       result = claimer == 'w' ? BlackWins : WhiteWins;
10213                       resultDetails = buf;
10214                 }
10215                 /* (Claiming a loss is accepted no questions asked!) */
10216             }
10217             /* [HGM] bare: don't allow bare King to win */
10218             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10219                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10220                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10221                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10222                && result != GameIsDrawn)
10223             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10224                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10225                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10226                         if(p >= 0 && p <= (int)WhiteKing) k++;
10227                 }
10228                 if (appData.debugMode) {
10229                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10230                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10231                 }
10232                 if(k <= 1) {
10233                         result = GameIsDrawn;
10234                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10235                         resultDetails = buf;
10236                 }
10237             }
10238         }
10239
10240
10241         if(serverMoves != NULL && !loadFlag) { char c = '=';
10242             if(result==WhiteWins) c = '+';
10243             if(result==BlackWins) c = '-';
10244             if(resultDetails != NULL)
10245                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves);
10246         }
10247         if (resultDetails != NULL) {
10248             gameInfo.result = result;
10249             gameInfo.resultDetails = StrSave(resultDetails);
10250
10251             /* display last move only if game was not loaded from file */
10252             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10253                 DisplayMove(currentMove - 1);
10254
10255             if (forwardMostMove != 0) {
10256                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10257                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10258                                                                 ) {
10259                     if (*appData.saveGameFile != NULLCHAR) {
10260                         SaveGameToFile(appData.saveGameFile, TRUE);
10261                     } else if (appData.autoSaveGames) {
10262                         AutoSaveGame();
10263                     }
10264                     if (*appData.savePositionFile != NULLCHAR) {
10265                         SavePositionToFile(appData.savePositionFile);
10266                     }
10267                 }
10268             }
10269
10270             /* Tell program how game ended in case it is learning */
10271             /* [HGM] Moved this to after saving the PGN, just in case */
10272             /* engine died and we got here through time loss. In that */
10273             /* case we will get a fatal error writing the pipe, which */
10274             /* would otherwise lose us the PGN.                       */
10275             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10276             /* output during GameEnds should never be fatal anymore   */
10277             if (gameMode == MachinePlaysWhite ||
10278                 gameMode == MachinePlaysBlack ||
10279                 gameMode == TwoMachinesPlay ||
10280                 gameMode == IcsPlayingWhite ||
10281                 gameMode == IcsPlayingBlack ||
10282                 gameMode == BeginningOfGame) {
10283                 char buf[MSG_SIZ];
10284                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10285                         resultDetails);
10286                 if (first.pr != NoProc) {
10287                     SendToProgram(buf, &first);
10288                 }
10289                 if (second.pr != NoProc &&
10290                     gameMode == TwoMachinesPlay) {
10291                     SendToProgram(buf, &second);
10292                 }
10293             }
10294         }
10295
10296         if (appData.icsActive) {
10297             if (appData.quietPlay &&
10298                 (gameMode == IcsPlayingWhite ||
10299                  gameMode == IcsPlayingBlack)) {
10300                 SendToICS(ics_prefix);
10301                 SendToICS("set shout 1\n");
10302             }
10303             nextGameMode = IcsIdle;
10304             ics_user_moved = FALSE;
10305             /* clean up premove.  It's ugly when the game has ended and the
10306              * premove highlights are still on the board.
10307              */
10308             if (gotPremove) {
10309               gotPremove = FALSE;
10310               ClearPremoveHighlights();
10311               DrawPosition(FALSE, boards[currentMove]);
10312             }
10313             if (whosays == GE_ICS) {
10314                 switch (result) {
10315                 case WhiteWins:
10316                     if (gameMode == IcsPlayingWhite)
10317                         PlayIcsWinSound();
10318                     else if(gameMode == IcsPlayingBlack)
10319                         PlayIcsLossSound();
10320                     break;
10321                 case BlackWins:
10322                     if (gameMode == IcsPlayingBlack)
10323                         PlayIcsWinSound();
10324                     else if(gameMode == IcsPlayingWhite)
10325                         PlayIcsLossSound();
10326                     break;
10327                 case GameIsDrawn:
10328                     PlayIcsDrawSound();
10329                     break;
10330                 default:
10331                     PlayIcsUnfinishedSound();
10332                 }
10333             }
10334         } else if (gameMode == EditGame ||
10335                    gameMode == PlayFromGameFile ||
10336                    gameMode == AnalyzeMode ||
10337                    gameMode == AnalyzeFile) {
10338             nextGameMode = gameMode;
10339         } else {
10340             nextGameMode = EndOfGame;
10341         }
10342         pausing = FALSE;
10343         ModeHighlight();
10344     } else {
10345         nextGameMode = gameMode;
10346     }
10347
10348     if (appData.noChessProgram) {
10349         gameMode = nextGameMode;
10350         ModeHighlight();
10351         endingGame = 0; /* [HGM] crash */
10352         return;
10353     }
10354
10355     if (first.reuse) {
10356         /* Put first chess program into idle state */
10357         if (first.pr != NoProc &&
10358             (gameMode == MachinePlaysWhite ||
10359              gameMode == MachinePlaysBlack ||
10360              gameMode == TwoMachinesPlay ||
10361              gameMode == IcsPlayingWhite ||
10362              gameMode == IcsPlayingBlack ||
10363              gameMode == BeginningOfGame)) {
10364             SendToProgram("force\n", &first);
10365             if (first.usePing) {
10366               char buf[MSG_SIZ];
10367               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10368               SendToProgram(buf, &first);
10369             }
10370         }
10371     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10372         /* Kill off first chess program */
10373         if (first.isr != NULL)
10374           RemoveInputSource(first.isr);
10375         first.isr = NULL;
10376
10377         if (first.pr != NoProc) {
10378             ExitAnalyzeMode();
10379             DoSleep( appData.delayBeforeQuit );
10380             SendToProgram("quit\n", &first);
10381             DoSleep( appData.delayAfterQuit );
10382             DestroyChildProcess(first.pr, first.useSigterm);
10383         }
10384         first.pr = NoProc;
10385     }
10386     if (second.reuse) {
10387         /* Put second chess program into idle state */
10388         if (second.pr != NoProc &&
10389             gameMode == TwoMachinesPlay) {
10390             SendToProgram("force\n", &second);
10391             if (second.usePing) {
10392               char buf[MSG_SIZ];
10393               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10394               SendToProgram(buf, &second);
10395             }
10396         }
10397     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10398         /* Kill off second chess program */
10399         if (second.isr != NULL)
10400           RemoveInputSource(second.isr);
10401         second.isr = NULL;
10402
10403         if (second.pr != NoProc) {
10404             DoSleep( appData.delayBeforeQuit );
10405             SendToProgram("quit\n", &second);
10406             DoSleep( appData.delayAfterQuit );
10407             DestroyChildProcess(second.pr, second.useSigterm);
10408         }
10409         second.pr = NoProc;
10410     }
10411
10412     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10413         char resChar = '=';
10414         switch (result) {
10415         case WhiteWins:
10416           resChar = '+';
10417           if (first.twoMachinesColor[0] == 'w') {
10418             first.matchWins++;
10419           } else {
10420             second.matchWins++;
10421           }
10422           break;
10423         case BlackWins:
10424           resChar = '-';
10425           if (first.twoMachinesColor[0] == 'b') {
10426             first.matchWins++;
10427           } else {
10428             second.matchWins++;
10429           }
10430           break;
10431         case GameUnfinished:
10432           resChar = ' ';
10433         default:
10434           break;
10435         }
10436
10437         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10438         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10439             if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame);
10440             ReserveGame(nextGame, resChar); // sets nextGame
10441             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10442             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10443         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10444
10445         if (nextGame <= appData.matchGames && !abortMatch) {
10446             gameMode = nextGameMode;
10447             matchGame = nextGame; // this will be overruled in tourney mode!
10448             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10449             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10450             endingGame = 0; /* [HGM] crash */
10451             return;
10452         } else {
10453             gameMode = nextGameMode;
10454             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10455                      first.tidy, second.tidy,
10456                      first.matchWins, second.matchWins,
10457                      appData.matchGames - (first.matchWins + second.matchWins));
10458             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10459             if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney);
10460             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10461             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10462                 first.twoMachinesColor = "black\n";
10463                 second.twoMachinesColor = "white\n";
10464             } else {
10465                 first.twoMachinesColor = "white\n";
10466                 second.twoMachinesColor = "black\n";
10467             }
10468         }
10469     }
10470     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10471         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10472       ExitAnalyzeMode();
10473     gameMode = nextGameMode;
10474     ModeHighlight();
10475     endingGame = 0;  /* [HGM] crash */
10476     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10477         if(matchMode == TRUE) { // match through command line: exit with or without popup
10478             if(ranking) {
10479                 ToNrEvent(forwardMostMove);
10480                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10481                 else ExitEvent(0);
10482             } else DisplayFatalError(buf, 0, 0);
10483         } else { // match through menu; just stop, with or without popup
10484             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10485             ModeHighlight();
10486             if(ranking){
10487                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10488             } else DisplayNote(buf);
10489       }
10490       if(ranking) free(ranking);
10491     }
10492 }
10493
10494 /* Assumes program was just initialized (initString sent).
10495    Leaves program in force mode. */
10496 void
10497 FeedMovesToProgram (ChessProgramState *cps, int upto)
10498 {
10499     int i;
10500
10501     if (appData.debugMode)
10502       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10503               startedFromSetupPosition ? "position and " : "",
10504               backwardMostMove, upto, cps->which);
10505     if(currentlyInitializedVariant != gameInfo.variant) {
10506       char buf[MSG_SIZ];
10507         // [HGM] variantswitch: make engine aware of new variant
10508         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10509                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10510         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10511         SendToProgram(buf, cps);
10512         currentlyInitializedVariant = gameInfo.variant;
10513     }
10514     SendToProgram("force\n", cps);
10515     if (startedFromSetupPosition) {
10516         SendBoard(cps, backwardMostMove);
10517     if (appData.debugMode) {
10518         fprintf(debugFP, "feedMoves\n");
10519     }
10520     }
10521     for (i = backwardMostMove; i < upto; i++) {
10522         SendMoveToProgram(i, cps);
10523     }
10524 }
10525
10526
10527 int
10528 ResurrectChessProgram ()
10529 {
10530      /* The chess program may have exited.
10531         If so, restart it and feed it all the moves made so far. */
10532     static int doInit = 0;
10533
10534     if (appData.noChessProgram) return 1;
10535
10536     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10537         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10538         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10539         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10540     } else {
10541         if (first.pr != NoProc) return 1;
10542         StartChessProgram(&first);
10543     }
10544     InitChessProgram(&first, FALSE);
10545     FeedMovesToProgram(&first, currentMove);
10546
10547     if (!first.sendTime) {
10548         /* can't tell gnuchess what its clock should read,
10549            so we bow to its notion. */
10550         ResetClocks();
10551         timeRemaining[0][currentMove] = whiteTimeRemaining;
10552         timeRemaining[1][currentMove] = blackTimeRemaining;
10553     }
10554
10555     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10556                 appData.icsEngineAnalyze) && first.analysisSupport) {
10557       SendToProgram("analyze\n", &first);
10558       first.analyzing = TRUE;
10559     }
10560     return 1;
10561 }
10562
10563 /*
10564  * Button procedures
10565  */
10566 void
10567 Reset (int redraw, int init)
10568 {
10569     int i;
10570
10571     if (appData.debugMode) {
10572         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10573                 redraw, init, gameMode);
10574     }
10575     CleanupTail(); // [HGM] vari: delete any stored variations
10576     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
10577     pausing = pauseExamInvalid = FALSE;
10578     startedFromSetupPosition = blackPlaysFirst = FALSE;
10579     firstMove = TRUE;
10580     whiteFlag = blackFlag = FALSE;
10581     userOfferedDraw = FALSE;
10582     hintRequested = bookRequested = FALSE;
10583     first.maybeThinking = FALSE;
10584     second.maybeThinking = FALSE;
10585     first.bookSuspend = FALSE; // [HGM] book
10586     second.bookSuspend = FALSE;
10587     thinkOutput[0] = NULLCHAR;
10588     lastHint[0] = NULLCHAR;
10589     ClearGameInfo(&gameInfo);
10590     gameInfo.variant = StringToVariant(appData.variant);
10591     ics_user_moved = ics_clock_paused = FALSE;
10592     ics_getting_history = H_FALSE;
10593     ics_gamenum = -1;
10594     white_holding[0] = black_holding[0] = NULLCHAR;
10595     ClearProgramStats();
10596     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10597
10598     ResetFrontEnd();
10599     ClearHighlights();
10600     flipView = appData.flipView;
10601     ClearPremoveHighlights();
10602     gotPremove = FALSE;
10603     alarmSounded = FALSE;
10604
10605     GameEnds(EndOfFile, NULL, GE_PLAYER);
10606     if(appData.serverMovesName != NULL) {
10607         /* [HGM] prepare to make moves file for broadcasting */
10608         clock_t t = clock();
10609         if(serverMoves != NULL) fclose(serverMoves);
10610         serverMoves = fopen(appData.serverMovesName, "r");
10611         if(serverMoves != NULL) {
10612             fclose(serverMoves);
10613             /* delay 15 sec before overwriting, so all clients can see end */
10614             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10615         }
10616         serverMoves = fopen(appData.serverMovesName, "w");
10617     }
10618
10619     ExitAnalyzeMode();
10620     gameMode = BeginningOfGame;
10621     ModeHighlight();
10622     if(appData.icsActive) gameInfo.variant = VariantNormal;
10623     currentMove = forwardMostMove = backwardMostMove = 0;
10624     MarkTargetSquares(1);
10625     InitPosition(redraw);
10626     for (i = 0; i < MAX_MOVES; i++) {
10627         if (commentList[i] != NULL) {
10628             free(commentList[i]);
10629             commentList[i] = NULL;
10630         }
10631     }
10632     ResetClocks();
10633     timeRemaining[0][0] = whiteTimeRemaining;
10634     timeRemaining[1][0] = blackTimeRemaining;
10635
10636     if (first.pr == NoProc) {
10637         StartChessProgram(&first);
10638     }
10639     if (init) {
10640             InitChessProgram(&first, startedFromSetupPosition);
10641     }
10642     DisplayTitle("");
10643     DisplayMessage("", "");
10644     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10645     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10646 }
10647
10648 void
10649 AutoPlayGameLoop ()
10650 {
10651     for (;;) {
10652         if (!AutoPlayOneMove())
10653           return;
10654         if (matchMode || appData.timeDelay == 0)
10655           continue;
10656         if (appData.timeDelay < 0)
10657           return;
10658         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10659         break;
10660     }
10661 }
10662
10663
10664 int
10665 AutoPlayOneMove ()
10666 {
10667     int fromX, fromY, toX, toY;
10668
10669     if (appData.debugMode) {
10670       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10671     }
10672
10673     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10674       return FALSE;
10675
10676     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10677       pvInfoList[currentMove].depth = programStats.depth;
10678       pvInfoList[currentMove].score = programStats.score;
10679       pvInfoList[currentMove].time  = 0;
10680       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10681     }
10682
10683     if (currentMove >= forwardMostMove) {
10684       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10685 //      gameMode = EndOfGame;
10686 //      ModeHighlight();
10687
10688       /* [AS] Clear current move marker at the end of a game */
10689       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10690
10691       return FALSE;
10692     }
10693
10694     toX = moveList[currentMove][2] - AAA;
10695     toY = moveList[currentMove][3] - ONE;
10696
10697     if (moveList[currentMove][1] == '@') {
10698         if (appData.highlightLastMove) {
10699             SetHighlights(-1, -1, toX, toY);
10700         }
10701     } else {
10702         fromX = moveList[currentMove][0] - AAA;
10703         fromY = moveList[currentMove][1] - ONE;
10704
10705         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10706
10707         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10708
10709         if (appData.highlightLastMove) {
10710             SetHighlights(fromX, fromY, toX, toY);
10711         }
10712     }
10713     DisplayMove(currentMove);
10714     SendMoveToProgram(currentMove++, &first);
10715     DisplayBothClocks();
10716     DrawPosition(FALSE, boards[currentMove]);
10717     // [HGM] PV info: always display, routine tests if empty
10718     DisplayComment(currentMove - 1, commentList[currentMove]);
10719     return TRUE;
10720 }
10721
10722
10723 int
10724 LoadGameOneMove (ChessMove readAhead)
10725 {
10726     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10727     char promoChar = NULLCHAR;
10728     ChessMove moveType;
10729     char move[MSG_SIZ];
10730     char *p, *q;
10731
10732     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10733         gameMode != AnalyzeMode && gameMode != Training) {
10734         gameFileFP = NULL;
10735         return FALSE;
10736     }
10737
10738     yyboardindex = forwardMostMove;
10739     if (readAhead != EndOfFile) {
10740       moveType = readAhead;
10741     } else {
10742       if (gameFileFP == NULL)
10743           return FALSE;
10744       moveType = (ChessMove) Myylex();
10745     }
10746
10747     done = FALSE;
10748     switch (moveType) {
10749       case Comment:
10750         if (appData.debugMode)
10751           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10752         p = yy_text;
10753
10754         /* append the comment but don't display it */
10755         AppendComment(currentMove, p, FALSE);
10756         return TRUE;
10757
10758       case WhiteCapturesEnPassant:
10759       case BlackCapturesEnPassant:
10760       case WhitePromotion:
10761       case BlackPromotion:
10762       case WhiteNonPromotion:
10763       case BlackNonPromotion:
10764       case NormalMove:
10765       case WhiteKingSideCastle:
10766       case WhiteQueenSideCastle:
10767       case BlackKingSideCastle:
10768       case BlackQueenSideCastle:
10769       case WhiteKingSideCastleWild:
10770       case WhiteQueenSideCastleWild:
10771       case BlackKingSideCastleWild:
10772       case BlackQueenSideCastleWild:
10773       /* PUSH Fabien */
10774       case WhiteHSideCastleFR:
10775       case WhiteASideCastleFR:
10776       case BlackHSideCastleFR:
10777       case BlackASideCastleFR:
10778       /* POP Fabien */
10779         if (appData.debugMode)
10780           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10781         fromX = currentMoveString[0] - AAA;
10782         fromY = currentMoveString[1] - ONE;
10783         toX = currentMoveString[2] - AAA;
10784         toY = currentMoveString[3] - ONE;
10785         promoChar = currentMoveString[4];
10786         break;
10787
10788       case WhiteDrop:
10789       case BlackDrop:
10790         if (appData.debugMode)
10791           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10792         fromX = moveType == WhiteDrop ?
10793           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10794         (int) CharToPiece(ToLower(currentMoveString[0]));
10795         fromY = DROP_RANK;
10796         toX = currentMoveString[2] - AAA;
10797         toY = currentMoveString[3] - ONE;
10798         break;
10799
10800       case WhiteWins:
10801       case BlackWins:
10802       case GameIsDrawn:
10803       case GameUnfinished:
10804         if (appData.debugMode)
10805           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10806         p = strchr(yy_text, '{');
10807         if (p == NULL) p = strchr(yy_text, '(');
10808         if (p == NULL) {
10809             p = yy_text;
10810             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10811         } else {
10812             q = strchr(p, *p == '{' ? '}' : ')');
10813             if (q != NULL) *q = NULLCHAR;
10814             p++;
10815         }
10816         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10817         GameEnds(moveType, p, GE_FILE);
10818         done = TRUE;
10819         if (cmailMsgLoaded) {
10820             ClearHighlights();
10821             flipView = WhiteOnMove(currentMove);
10822             if (moveType == GameUnfinished) flipView = !flipView;
10823             if (appData.debugMode)
10824               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10825         }
10826         break;
10827
10828       case EndOfFile:
10829         if (appData.debugMode)
10830           fprintf(debugFP, "Parser hit end of file\n");
10831         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10832           case MT_NONE:
10833           case MT_CHECK:
10834             break;
10835           case MT_CHECKMATE:
10836           case MT_STAINMATE:
10837             if (WhiteOnMove(currentMove)) {
10838                 GameEnds(BlackWins, "Black mates", GE_FILE);
10839             } else {
10840                 GameEnds(WhiteWins, "White mates", GE_FILE);
10841             }
10842             break;
10843           case MT_STALEMATE:
10844             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10845             break;
10846         }
10847         done = TRUE;
10848         break;
10849
10850       case MoveNumberOne:
10851         if (lastLoadGameStart == GNUChessGame) {
10852             /* GNUChessGames have numbers, but they aren't move numbers */
10853             if (appData.debugMode)
10854               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10855                       yy_text, (int) moveType);
10856             return LoadGameOneMove(EndOfFile); /* tail recursion */
10857         }
10858         /* else fall thru */
10859
10860       case XBoardGame:
10861       case GNUChessGame:
10862       case PGNTag:
10863         /* Reached start of next game in file */
10864         if (appData.debugMode)
10865           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10866         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10867           case MT_NONE:
10868           case MT_CHECK:
10869             break;
10870           case MT_CHECKMATE:
10871           case MT_STAINMATE:
10872             if (WhiteOnMove(currentMove)) {
10873                 GameEnds(BlackWins, "Black mates", GE_FILE);
10874             } else {
10875                 GameEnds(WhiteWins, "White mates", GE_FILE);
10876             }
10877             break;
10878           case MT_STALEMATE:
10879             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10880             break;
10881         }
10882         done = TRUE;
10883         break;
10884
10885       case PositionDiagram:     /* should not happen; ignore */
10886       case ElapsedTime:         /* ignore */
10887       case NAG:                 /* ignore */
10888         if (appData.debugMode)
10889           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10890                   yy_text, (int) moveType);
10891         return LoadGameOneMove(EndOfFile); /* tail recursion */
10892
10893       case IllegalMove:
10894         if (appData.testLegality) {
10895             if (appData.debugMode)
10896               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10897             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10898                     (forwardMostMove / 2) + 1,
10899                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10900             DisplayError(move, 0);
10901             done = TRUE;
10902         } else {
10903             if (appData.debugMode)
10904               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10905                       yy_text, currentMoveString);
10906             fromX = currentMoveString[0] - AAA;
10907             fromY = currentMoveString[1] - ONE;
10908             toX = currentMoveString[2] - AAA;
10909             toY = currentMoveString[3] - ONE;
10910             promoChar = currentMoveString[4];
10911         }
10912         break;
10913
10914       case AmbiguousMove:
10915         if (appData.debugMode)
10916           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10917         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10918                 (forwardMostMove / 2) + 1,
10919                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10920         DisplayError(move, 0);
10921         done = TRUE;
10922         break;
10923
10924       default:
10925       case ImpossibleMove:
10926         if (appData.debugMode)
10927           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10928         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10929                 (forwardMostMove / 2) + 1,
10930                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10931         DisplayError(move, 0);
10932         done = TRUE;
10933         break;
10934     }
10935
10936     if (done) {
10937         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10938             DrawPosition(FALSE, boards[currentMove]);
10939             DisplayBothClocks();
10940             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10941               DisplayComment(currentMove - 1, commentList[currentMove]);
10942         }
10943         (void) StopLoadGameTimer();
10944         gameFileFP = NULL;
10945         cmailOldMove = forwardMostMove;
10946         return FALSE;
10947     } else {
10948         /* currentMoveString is set as a side-effect of yylex */
10949
10950         thinkOutput[0] = NULLCHAR;
10951         MakeMove(fromX, fromY, toX, toY, promoChar);
10952         currentMove = forwardMostMove;
10953         return TRUE;
10954     }
10955 }
10956
10957 /* Load the nth game from the given file */
10958 int
10959 LoadGameFromFile (char *filename, int n, char *title, int useList)
10960 {
10961     FILE *f;
10962     char buf[MSG_SIZ];
10963
10964     if (strcmp(filename, "-") == 0) {
10965         f = stdin;
10966         title = "stdin";
10967     } else {
10968         f = fopen(filename, "rb");
10969         if (f == NULL) {
10970           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10971             DisplayError(buf, errno);
10972             return FALSE;
10973         }
10974     }
10975     if (fseek(f, 0, 0) == -1) {
10976         /* f is not seekable; probably a pipe */
10977         useList = FALSE;
10978     }
10979     if (useList && n == 0) {
10980         int error = GameListBuild(f);
10981         if (error) {
10982             DisplayError(_("Cannot build game list"), error);
10983         } else if (!ListEmpty(&gameList) &&
10984                    ((ListGame *) gameList.tailPred)->number > 1) {
10985             GameListPopUp(f, title);
10986             return TRUE;
10987         }
10988         GameListDestroy();
10989         n = 1;
10990     }
10991     if (n == 0) n = 1;
10992     return LoadGame(f, n, title, FALSE);
10993 }
10994
10995
10996 void
10997 MakeRegisteredMove ()
10998 {
10999     int fromX, fromY, toX, toY;
11000     char promoChar;
11001     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11002         switch (cmailMoveType[lastLoadGameNumber - 1]) {
11003           case CMAIL_MOVE:
11004           case CMAIL_DRAW:
11005             if (appData.debugMode)
11006               fprintf(debugFP, "Restoring %s for game %d\n",
11007                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11008
11009             thinkOutput[0] = NULLCHAR;
11010             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
11011             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
11012             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
11013             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
11014             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
11015             promoChar = cmailMove[lastLoadGameNumber - 1][4];
11016             MakeMove(fromX, fromY, toX, toY, promoChar);
11017             ShowMove(fromX, fromY, toX, toY);
11018
11019             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
11020               case MT_NONE:
11021               case MT_CHECK:
11022                 break;
11023
11024               case MT_CHECKMATE:
11025               case MT_STAINMATE:
11026                 if (WhiteOnMove(currentMove)) {
11027                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
11028                 } else {
11029                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
11030                 }
11031                 break;
11032
11033               case MT_STALEMATE:
11034                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
11035                 break;
11036             }
11037
11038             break;
11039
11040           case CMAIL_RESIGN:
11041             if (WhiteOnMove(currentMove)) {
11042                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11043             } else {
11044                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11045             }
11046             break;
11047
11048           case CMAIL_ACCEPT:
11049             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11050             break;
11051
11052           default:
11053             break;
11054         }
11055     }
11056
11057     return;
11058 }
11059
11060 /* Wrapper around LoadGame for use when a Cmail message is loaded */
11061 int
11062 CmailLoadGame (FILE *f, int gameNumber, char *title, int useList)
11063 {
11064     int retVal;
11065
11066     if (gameNumber > nCmailGames) {
11067         DisplayError(_("No more games in this message"), 0);
11068         return FALSE;
11069     }
11070     if (f == lastLoadGameFP) {
11071         int offset = gameNumber - lastLoadGameNumber;
11072         if (offset == 0) {
11073             cmailMsg[0] = NULLCHAR;
11074             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11075                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11076                 nCmailMovesRegistered--;
11077             }
11078             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11079             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
11080                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
11081             }
11082         } else {
11083             if (! RegisterMove()) return FALSE;
11084         }
11085     }
11086
11087     retVal = LoadGame(f, gameNumber, title, useList);
11088
11089     /* Make move registered during previous look at this game, if any */
11090     MakeRegisteredMove();
11091
11092     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
11093         commentList[currentMove]
11094           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
11095         DisplayComment(currentMove - 1, commentList[currentMove]);
11096     }
11097
11098     return retVal;
11099 }
11100
11101 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
11102 int
11103 ReloadGame (int offset)
11104 {
11105     int gameNumber = lastLoadGameNumber + offset;
11106     if (lastLoadGameFP == NULL) {
11107         DisplayError(_("No game has been loaded yet"), 0);
11108         return FALSE;
11109     }
11110     if (gameNumber <= 0) {
11111         DisplayError(_("Can't back up any further"), 0);
11112         return FALSE;
11113     }
11114     if (cmailMsgLoaded) {
11115         return CmailLoadGame(lastLoadGameFP, gameNumber,
11116                              lastLoadGameTitle, lastLoadGameUseList);
11117     } else {
11118         return LoadGame(lastLoadGameFP, gameNumber,
11119                         lastLoadGameTitle, lastLoadGameUseList);
11120     }
11121 }
11122
11123 int keys[EmptySquare+1];
11124
11125 int
11126 PositionMatches (Board b1, Board b2)
11127 {
11128     int r, f, sum=0;
11129     switch(appData.searchMode) {
11130         case 1: return CompareWithRights(b1, b2);
11131         case 2:
11132             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11133                 if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
11134             }
11135             return TRUE;
11136         case 3:
11137             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11138               if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
11139                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11140             }
11141             return sum==0;
11142         case 4:
11143             for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11144                 sum += keys[b1[r][f]] - keys[b2[r][f]];
11145             }
11146             return sum==0;
11147     }
11148     return TRUE;
11149 }
11150
11151 #define Q_PROMO  4
11152 #define Q_EP     3
11153 #define Q_BCASTL 2
11154 #define Q_WCASTL 1
11155
11156 int pieceList[256], quickBoard[256];
11157 ChessSquare pieceType[256] = { EmptySquare };
11158 Board soughtBoard, reverseBoard, flipBoard, rotateBoard;
11159 int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
11160 int soughtTotal, turn;
11161 Boolean epOK, flipSearch;
11162
11163 typedef struct {
11164     unsigned char piece, to;
11165 } Move;
11166
11167 #define DSIZE (250000)
11168
11169 Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves
11170 Move *moveDatabase = initialSpace;
11171 unsigned int movePtr, dataSize = DSIZE;
11172
11173 int
11174 MakePieceList (Board board, int *counts)
11175 {
11176     int r, f, n=Q_PROMO, total=0;
11177     for(r=0;r<EmptySquare;r++) counts[r] = 0; // piece-type counts
11178     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11179         int sq = f + (r<<4);
11180         if(board[r][f] == EmptySquare) quickBoard[sq] = 0; else {
11181             quickBoard[sq] = ++n;
11182             pieceList[n] = sq;
11183             pieceType[n] = board[r][f];
11184             counts[board[r][f]]++;
11185             if(board[r][f] == WhiteKing) pieceList[1] = n; else
11186             if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
11187             total++;
11188         }
11189     }
11190     epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
11191     return total;
11192 }
11193
11194 void
11195 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
11196 {
11197     int sq = fromX + (fromY<<4);
11198     int piece = quickBoard[sq];
11199     quickBoard[sq] = 0;
11200     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
11201     if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11202         int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
11203         moveDatabase[movePtr++].piece = Q_WCASTL;
11204         quickBoard[sq] = piece;
11205         piece = quickBoard[from]; quickBoard[from] = 0;
11206         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11207     } else
11208     if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
11209         int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
11210         moveDatabase[movePtr++].piece = Q_BCASTL;
11211         quickBoard[sq] = piece;
11212         piece = quickBoard[from]; quickBoard[from] = 0;
11213         moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
11214     } else
11215     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
11216         quickBoard[(fromY<<4)+toX] = 0;
11217         moveDatabase[movePtr].piece = Q_EP;
11218         moveDatabase[movePtr++].to = (fromY<<4)+toX;
11219         moveDatabase[movePtr].to = sq;
11220     } else
11221     if(promoPiece != pieceType[piece]) {
11222         moveDatabase[movePtr++].piece = Q_PROMO;
11223         moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
11224     }
11225     moveDatabase[movePtr].piece = piece;
11226     quickBoard[sq] = piece;
11227     movePtr++;
11228 }
11229
11230 int
11231 PackGame (Board board)
11232 {
11233     Move *newSpace = NULL;
11234     moveDatabase[movePtr].piece = 0; // terminate previous game
11235     if(movePtr > dataSize) {
11236         if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128);
11237         dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB)
11238         if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move));
11239         if(newSpace) {
11240             int i;
11241             Move *p = moveDatabase, *q = newSpace;
11242             for(i=0; i<movePtr; i++) *q++ = *p++;    // copy to newly allocated space
11243             if(dataSize > 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated)
11244             moveDatabase = newSpace;
11245         } else { // calloc failed, we must be out of memory. Too bad...
11246             dataSize = 0; // prevent calloc events for all subsequent games
11247             return 0;     // and signal this one isn't cached
11248         }
11249     }
11250     movePtr++;
11251     MakePieceList(board, counts);
11252     return movePtr;
11253 }
11254
11255 int
11256 QuickCompare (Board board, int *minCounts, int *maxCounts)
11257 {   // compare according to search mode
11258     int r, f;
11259     switch(appData.searchMode)
11260     {
11261       case 1: // exact position match
11262         if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move
11263         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11264             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11265         }
11266         break;
11267       case 2: // can have extra material on empty squares
11268         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11269             if(board[r][f] == EmptySquare) continue;
11270             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11271         }
11272         break;
11273       case 3: // material with exact Pawn structure
11274         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11275             if(board[r][f] != WhitePawn && board[r][f] != BlackPawn) continue;
11276             if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
11277         } // fall through to material comparison
11278       case 4: // exact material
11279         for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
11280         break;
11281       case 6: // material range with given imbalance
11282         for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
11283         // fall through to range comparison
11284       case 5: // material range
11285         for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
11286     }
11287     return TRUE;
11288 }
11289
11290 int
11291 QuickScan (Board board, Move *move)
11292 {   // reconstruct game,and compare all positions in it
11293     int cnt=0, stretch=0, total = MakePieceList(board, counts);
11294     do {
11295         int piece = move->piece;
11296         int to = move->to, from = pieceList[piece];
11297         if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
11298           if(!piece) return -1;
11299           if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
11300             piece = (++move)->piece;
11301             from = pieceList[piece];
11302             counts[pieceType[piece]]--;
11303             pieceType[piece] = (ChessSquare) move->to;
11304             counts[move->to]++;
11305           } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to)
11306             counts[pieceType[quickBoard[to]]]--;
11307             quickBoard[to] = 0; total--;
11308             move++;
11309             continue;
11310           } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
11311             piece = pieceList[piece]; // first two elements of pieceList contain King numbers
11312             from  = pieceList[piece]; // so this must be King
11313             quickBoard[from] = 0;
11314             quickBoard[to] = piece;
11315             pieceList[piece] = to;
11316             move++;
11317             continue;
11318           }
11319         }
11320         if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
11321         if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
11322         quickBoard[from] = 0;
11323         quickBoard[to] = piece;
11324         pieceList[piece] = to;
11325         cnt++; turn ^= 3;
11326         if(QuickCompare(soughtBoard, minSought, maxSought) ||
11327            appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
11328            flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
11329                                 appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
11330           ) {
11331             static int lastCounts[EmptySquare+1];
11332             int i;
11333             if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
11334             if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
11335         } else stretch = 0;
11336         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
11337         move++;
11338     } while(1);
11339 }
11340
11341 void
11342 InitSearch ()
11343 {
11344     int r, f;
11345     flipSearch = FALSE;
11346     CopyBoard(soughtBoard, boards[currentMove]);
11347     soughtTotal = MakePieceList(soughtBoard, maxSought);
11348     soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1;
11349     if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!)
11350     CopyBoard(reverseBoard, boards[currentMove]);
11351     for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11352         int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
11353         if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
11354         reverseBoard[r][f] = piece;
11355     }
11356     reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; 
11357     for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
11358     if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights
11359                  || (boards[currentMove][CASTLING][2] == NoRights || 
11360                      boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights )
11361                  && (boards[currentMove][CASTLING][5] == NoRights || 
11362                      boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) )
11363       ) {
11364         flipSearch = TRUE;
11365         CopyBoard(flipBoard, soughtBoard);
11366         CopyBoard(rotateBoard, reverseBoard);
11367         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
11368             flipBoard[r][f]    = soughtBoard[r][BOARD_WIDTH-1-f];
11369             rotateBoard[r][f] = reverseBoard[r][BOARD_WIDTH-1-f];
11370         }
11371     }
11372     for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
11373     if(appData.searchMode >= 5) {
11374         for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
11375         MakePieceList(soughtBoard, minSought);
11376         for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
11377     }
11378     if(gameInfo.variant == VariantCrazyhouse || gameInfo.variant == VariantShogi || gameInfo.variant == VariantBughouse)
11379         soughtTotal = 0; // in drop games nr of pieces does not fall monotonously
11380 }
11381
11382 GameInfo dummyInfo;
11383
11384 int
11385 GameContainsPosition (FILE *f, ListGame *lg)
11386 {
11387     int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
11388     int fromX, fromY, toX, toY;
11389     char promoChar;
11390     static int initDone=FALSE;
11391
11392     // weed out games based on numerical tag comparison
11393     if(lg->gameInfo.variant != gameInfo.variant) return -1; // wrong variant
11394     if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1;
11395     if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1;
11396     if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1;
11397     if(!initDone) {
11398         for(next = WhitePawn; next<EmptySquare; next++) keys[next] = random()>>8 ^ random()<<6 ^random()<<20;
11399         initDone = TRUE;
11400     }
11401     if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
11402     else CopyBoard(boards[scratch], initialPosition); // default start position
11403     if(lg->moves) {
11404         turn = btm + 1;
11405         if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
11406         if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
11407     }
11408     if(btm) plyNr++;
11409     if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11410     fseek(f, lg->offset, 0);
11411     yynewfile(f);
11412     while(1) {
11413         yyboardindex = scratch;
11414         quickFlag = plyNr+1;
11415         next = Myylex();
11416         quickFlag = 0;
11417         switch(next) {
11418             case PGNTag:
11419                 if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
11420             default:
11421                 continue;
11422
11423             case XBoardGame:
11424             case GNUChessGame:
11425                 if(plyNr) return -1; // after we have seen moves, this is for new game
11426               continue;
11427
11428             case AmbiguousMove: // we cannot reconstruct the game beyond these two
11429             case ImpossibleMove:
11430             case WhiteWins: // game ends here with these four
11431             case BlackWins:
11432             case GameIsDrawn:
11433             case GameUnfinished:
11434                 return -1;
11435
11436             case IllegalMove:
11437                 if(appData.testLegality) return -1;
11438             case WhiteCapturesEnPassant:
11439             case BlackCapturesEnPassant:
11440             case WhitePromotion:
11441             case BlackPromotion:
11442             case WhiteNonPromotion:
11443             case BlackNonPromotion:
11444             case NormalMove:
11445             case WhiteKingSideCastle:
11446             case WhiteQueenSideCastle:
11447             case BlackKingSideCastle:
11448             case BlackQueenSideCastle:
11449             case WhiteKingSideCastleWild:
11450             case WhiteQueenSideCastleWild:
11451             case BlackKingSideCastleWild:
11452             case BlackQueenSideCastleWild:
11453             case WhiteHSideCastleFR:
11454             case WhiteASideCastleFR:
11455             case BlackHSideCastleFR:
11456             case BlackASideCastleFR:
11457                 fromX = currentMoveString[0] - AAA;
11458                 fromY = currentMoveString[1] - ONE;
11459                 toX = currentMoveString[2] - AAA;
11460                 toY = currentMoveString[3] - ONE;
11461                 promoChar = currentMoveString[4];
11462                 break;
11463             case WhiteDrop:
11464             case BlackDrop:
11465                 fromX = next == WhiteDrop ?
11466                   (int) CharToPiece(ToUpper(currentMoveString[0])) :
11467                   (int) CharToPiece(ToLower(currentMoveString[0]));
11468                 fromY = DROP_RANK;
11469                 toX = currentMoveString[2] - AAA;
11470                 toY = currentMoveString[3] - ONE;
11471                 promoChar = 0;
11472                 break;
11473         }
11474         // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
11475         plyNr++;
11476         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
11477         if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
11478         if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
11479         if(appData.findMirror) {
11480             if(PositionMatches(boards[scratch], flipBoard)) return plyNr;
11481             if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr;
11482         }
11483     }
11484 }
11485
11486 /* Load the nth game from open file f */
11487 int
11488 LoadGame (FILE *f, int gameNumber, char *title, int useList)
11489 {
11490     ChessMove cm;
11491     char buf[MSG_SIZ];
11492     int gn = gameNumber;
11493     ListGame *lg = NULL;
11494     int numPGNTags = 0;
11495     int err, pos = -1;
11496     GameMode oldGameMode;
11497     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11498
11499     if (appData.debugMode)
11500         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11501
11502     if (gameMode == Training )
11503         SetTrainingModeOff();
11504
11505     oldGameMode = gameMode;
11506     if (gameMode != BeginningOfGame) {
11507       Reset(FALSE, TRUE);
11508     }
11509
11510     gameFileFP = f;
11511     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11512         fclose(lastLoadGameFP);
11513     }
11514
11515     if (useList) {
11516         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11517
11518         if (lg) {
11519             fseek(f, lg->offset, 0);
11520             GameListHighlight(gameNumber);
11521             pos = lg->position;
11522             gn = 1;
11523         }
11524         else {
11525             DisplayError(_("Game number out of range"), 0);
11526             return FALSE;
11527         }
11528     } else {
11529         GameListDestroy();
11530         if (fseek(f, 0, 0) == -1) {
11531             if (f == lastLoadGameFP ?
11532                 gameNumber == lastLoadGameNumber + 1 :
11533                 gameNumber == 1) {
11534                 gn = 1;
11535             } else {
11536                 DisplayError(_("Can't seek on game file"), 0);
11537                 return FALSE;
11538             }
11539         }
11540     }
11541     lastLoadGameFP = f;
11542     lastLoadGameNumber = gameNumber;
11543     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11544     lastLoadGameUseList = useList;
11545
11546     yynewfile(f);
11547
11548     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11549       snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."),
11550                 lg->gameInfo.black);
11551             DisplayTitle(buf);
11552     } else if (*title != NULLCHAR) {
11553         if (gameNumber > 1) {
11554           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11555             DisplayTitle(buf);
11556         } else {
11557             DisplayTitle(title);
11558         }
11559     }
11560
11561     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11562         gameMode = PlayFromGameFile;
11563         ModeHighlight();
11564     }
11565
11566     currentMove = forwardMostMove = backwardMostMove = 0;
11567     CopyBoard(boards[0], initialPosition);
11568     StopClocks();
11569
11570     /*
11571      * Skip the first gn-1 games in the file.
11572      * Also skip over anything that precedes an identifiable
11573      * start of game marker, to avoid being confused by
11574      * garbage at the start of the file.  Currently
11575      * recognized start of game markers are the move number "1",
11576      * the pattern "gnuchess .* game", the pattern
11577      * "^[#;%] [^ ]* game file", and a PGN tag block.
11578      * A game that starts with one of the latter two patterns
11579      * will also have a move number 1, possibly
11580      * following a position diagram.
11581      * 5-4-02: Let's try being more lenient and allowing a game to
11582      * start with an unnumbered move.  Does that break anything?
11583      */
11584     cm = lastLoadGameStart = EndOfFile;
11585     while (gn > 0) {
11586         yyboardindex = forwardMostMove;
11587         cm = (ChessMove) Myylex();
11588         switch (cm) {
11589           case EndOfFile:
11590             if (cmailMsgLoaded) {
11591                 nCmailGames = CMAIL_MAX_GAMES - gn;
11592             } else {
11593                 Reset(TRUE, TRUE);
11594                 DisplayError(_("Game not found in file"), 0);
11595             }
11596             return FALSE;
11597
11598           case GNUChessGame:
11599           case XBoardGame:
11600             gn--;
11601             lastLoadGameStart = cm;
11602             break;
11603
11604           case MoveNumberOne:
11605             switch (lastLoadGameStart) {
11606               case GNUChessGame:
11607               case XBoardGame:
11608               case PGNTag:
11609                 break;
11610               case MoveNumberOne:
11611               case EndOfFile:
11612                 gn--;           /* count this game */
11613                 lastLoadGameStart = cm;
11614                 break;
11615               default:
11616                 /* impossible */
11617                 break;
11618             }
11619             break;
11620
11621           case PGNTag:
11622             switch (lastLoadGameStart) {
11623               case GNUChessGame:
11624               case PGNTag:
11625               case MoveNumberOne:
11626               case EndOfFile:
11627                 gn--;           /* count this game */
11628                 lastLoadGameStart = cm;
11629                 break;
11630               case XBoardGame:
11631                 lastLoadGameStart = cm; /* game counted already */
11632                 break;
11633               default:
11634                 /* impossible */
11635                 break;
11636             }
11637             if (gn > 0) {
11638                 do {
11639                     yyboardindex = forwardMostMove;
11640                     cm = (ChessMove) Myylex();
11641                 } while (cm == PGNTag || cm == Comment);
11642             }
11643             break;
11644
11645           case WhiteWins:
11646           case BlackWins:
11647           case GameIsDrawn:
11648             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11649                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11650                     != CMAIL_OLD_RESULT) {
11651                     nCmailResults ++ ;
11652                     cmailResult[  CMAIL_MAX_GAMES
11653                                 - gn - 1] = CMAIL_OLD_RESULT;
11654                 }
11655             }
11656             break;
11657
11658           case NormalMove:
11659             /* Only a NormalMove can be at the start of a game
11660              * without a position diagram. */
11661             if (lastLoadGameStart == EndOfFile ) {
11662               gn--;
11663               lastLoadGameStart = MoveNumberOne;
11664             }
11665             break;
11666
11667           default:
11668             break;
11669         }
11670     }
11671
11672     if (appData.debugMode)
11673       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11674
11675     if (cm == XBoardGame) {
11676         /* Skip any header junk before position diagram and/or move 1 */
11677         for (;;) {
11678             yyboardindex = forwardMostMove;
11679             cm = (ChessMove) Myylex();
11680
11681             if (cm == EndOfFile ||
11682                 cm == GNUChessGame || cm == XBoardGame) {
11683                 /* Empty game; pretend end-of-file and handle later */
11684                 cm = EndOfFile;
11685                 break;
11686             }
11687
11688             if (cm == MoveNumberOne || cm == PositionDiagram ||
11689                 cm == PGNTag || cm == Comment)
11690               break;
11691         }
11692     } else if (cm == GNUChessGame) {
11693         if (gameInfo.event != NULL) {
11694             free(gameInfo.event);
11695         }
11696         gameInfo.event = StrSave(yy_text);
11697     }
11698
11699     startedFromSetupPosition = FALSE;
11700     while (cm == PGNTag) {
11701         if (appData.debugMode)
11702           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11703         err = ParsePGNTag(yy_text, &gameInfo);
11704         if (!err) numPGNTags++;
11705
11706         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11707         if(gameInfo.variant != oldVariant) {
11708             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11709             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11710             InitPosition(TRUE);
11711             oldVariant = gameInfo.variant;
11712             if (appData.debugMode)
11713               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11714         }
11715
11716
11717         if (gameInfo.fen != NULL) {
11718           Board initial_position;
11719           startedFromSetupPosition = TRUE;
11720           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11721             Reset(TRUE, TRUE);
11722             DisplayError(_("Bad FEN position in file"), 0);
11723             return FALSE;
11724           }
11725           CopyBoard(boards[0], initial_position);
11726           if (blackPlaysFirst) {
11727             currentMove = forwardMostMove = backwardMostMove = 1;
11728             CopyBoard(boards[1], initial_position);
11729             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11730             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11731             timeRemaining[0][1] = whiteTimeRemaining;
11732             timeRemaining[1][1] = blackTimeRemaining;
11733             if (commentList[0] != NULL) {
11734               commentList[1] = commentList[0];
11735               commentList[0] = NULL;
11736             }
11737           } else {
11738             currentMove = forwardMostMove = backwardMostMove = 0;
11739           }
11740           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11741           {   int i;
11742               initialRulePlies = FENrulePlies;
11743               for( i=0; i< nrCastlingRights; i++ )
11744                   initialRights[i] = initial_position[CASTLING][i];
11745           }
11746           yyboardindex = forwardMostMove;
11747           free(gameInfo.fen);
11748           gameInfo.fen = NULL;
11749         }
11750
11751         yyboardindex = forwardMostMove;
11752         cm = (ChessMove) Myylex();
11753
11754         /* Handle comments interspersed among the tags */
11755         while (cm == Comment) {
11756             char *p;
11757             if (appData.debugMode)
11758               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11759             p = yy_text;
11760             AppendComment(currentMove, p, FALSE);
11761             yyboardindex = forwardMostMove;
11762             cm = (ChessMove) Myylex();
11763         }
11764     }
11765
11766     /* don't rely on existence of Event tag since if game was
11767      * pasted from clipboard the Event tag may not exist
11768      */
11769     if (numPGNTags > 0){
11770         char *tags;
11771         if (gameInfo.variant == VariantNormal) {
11772           VariantClass v = StringToVariant(gameInfo.event);
11773           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11774           if(v < VariantShogi) gameInfo.variant = v;
11775         }
11776         if (!matchMode) {
11777           if( appData.autoDisplayTags ) {
11778             tags = PGNTags(&gameInfo);
11779             TagsPopUp(tags, CmailMsg());
11780             free(tags);
11781           }
11782         }
11783     } else {
11784         /* Make something up, but don't display it now */
11785         SetGameInfo();
11786         TagsPopDown();
11787     }
11788
11789     if (cm == PositionDiagram) {
11790         int i, j;
11791         char *p;
11792         Board initial_position;
11793
11794         if (appData.debugMode)
11795           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11796
11797         if (!startedFromSetupPosition) {
11798             p = yy_text;
11799             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11800               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11801                 switch (*p) {
11802                   case '{':
11803                   case '[':
11804                   case '-':
11805                   case ' ':
11806                   case '\t':
11807                   case '\n':
11808                   case '\r':
11809                     break;
11810                   default:
11811                     initial_position[i][j++] = CharToPiece(*p);
11812                     break;
11813                 }
11814             while (*p == ' ' || *p == '\t' ||
11815                    *p == '\n' || *p == '\r') p++;
11816
11817             if (strncmp(p, "black", strlen("black"))==0)
11818               blackPlaysFirst = TRUE;
11819             else
11820               blackPlaysFirst = FALSE;
11821             startedFromSetupPosition = TRUE;
11822
11823             CopyBoard(boards[0], initial_position);
11824             if (blackPlaysFirst) {
11825                 currentMove = forwardMostMove = backwardMostMove = 1;
11826                 CopyBoard(boards[1], initial_position);
11827                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11828                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11829                 timeRemaining[0][1] = whiteTimeRemaining;
11830                 timeRemaining[1][1] = blackTimeRemaining;
11831                 if (commentList[0] != NULL) {
11832                     commentList[1] = commentList[0];
11833                     commentList[0] = NULL;
11834                 }
11835             } else {
11836                 currentMove = forwardMostMove = backwardMostMove = 0;
11837             }
11838         }
11839         yyboardindex = forwardMostMove;
11840         cm = (ChessMove) Myylex();
11841     }
11842
11843     if (first.pr == NoProc) {
11844         StartChessProgram(&first);
11845     }
11846     InitChessProgram(&first, FALSE);
11847     SendToProgram("force\n", &first);
11848     if (startedFromSetupPosition) {
11849         SendBoard(&first, forwardMostMove);
11850     if (appData.debugMode) {
11851         fprintf(debugFP, "Load Game\n");
11852     }
11853         DisplayBothClocks();
11854     }
11855
11856     /* [HGM] server: flag to write setup moves in broadcast file as one */
11857     loadFlag = appData.suppressLoadMoves;
11858
11859     while (cm == Comment) {
11860         char *p;
11861         if (appData.debugMode)
11862           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11863         p = yy_text;
11864         AppendComment(currentMove, p, FALSE);
11865         yyboardindex = forwardMostMove;
11866         cm = (ChessMove) Myylex();
11867     }
11868
11869     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11870         cm == WhiteWins || cm == BlackWins ||
11871         cm == GameIsDrawn || cm == GameUnfinished) {
11872         DisplayMessage("", _("No moves in game"));
11873         if (cmailMsgLoaded) {
11874             if (appData.debugMode)
11875               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11876             ClearHighlights();
11877             flipView = FALSE;
11878         }
11879         DrawPosition(FALSE, boards[currentMove]);
11880         DisplayBothClocks();
11881         gameMode = EditGame;
11882         ModeHighlight();
11883         gameFileFP = NULL;
11884         cmailOldMove = 0;
11885         return TRUE;
11886     }
11887
11888     // [HGM] PV info: routine tests if comment empty
11889     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11890         DisplayComment(currentMove - 1, commentList[currentMove]);
11891     }
11892     if (!matchMode && appData.timeDelay != 0)
11893       DrawPosition(FALSE, boards[currentMove]);
11894
11895     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11896       programStats.ok_to_send = 1;
11897     }
11898
11899     /* if the first token after the PGN tags is a move
11900      * and not move number 1, retrieve it from the parser
11901      */
11902     if (cm != MoveNumberOne)
11903         LoadGameOneMove(cm);
11904
11905     /* load the remaining moves from the file */
11906     while (LoadGameOneMove(EndOfFile)) {
11907       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11908       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11909     }
11910
11911     /* rewind to the start of the game */
11912     currentMove = backwardMostMove;
11913
11914     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11915
11916     if (oldGameMode == AnalyzeFile ||
11917         oldGameMode == AnalyzeMode) {
11918       AnalyzeFileEvent();
11919     }
11920
11921     if (!matchMode && pos >= 0) {
11922         ToNrEvent(pos); // [HGM] no autoplay if selected on position
11923     } else
11924     if (matchMode || appData.timeDelay == 0) {
11925       ToEndEvent();
11926     } else if (appData.timeDelay > 0) {
11927       AutoPlayGameLoop();
11928     }
11929
11930     if (appData.debugMode)
11931         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11932
11933     loadFlag = 0; /* [HGM] true game starts */
11934     return TRUE;
11935 }
11936
11937 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11938 int
11939 ReloadPosition (int offset)
11940 {
11941     int positionNumber = lastLoadPositionNumber + offset;
11942     if (lastLoadPositionFP == NULL) {
11943         DisplayError(_("No position has been loaded yet"), 0);
11944         return FALSE;
11945     }
11946     if (positionNumber <= 0) {
11947         DisplayError(_("Can't back up any further"), 0);
11948         return FALSE;
11949     }
11950     return LoadPosition(lastLoadPositionFP, positionNumber,
11951                         lastLoadPositionTitle);
11952 }
11953
11954 /* Load the nth position from the given file */
11955 int
11956 LoadPositionFromFile (char *filename, int n, char *title)
11957 {
11958     FILE *f;
11959     char buf[MSG_SIZ];
11960
11961     if (strcmp(filename, "-") == 0) {
11962         return LoadPosition(stdin, n, "stdin");
11963     } else {
11964         f = fopen(filename, "rb");
11965         if (f == NULL) {
11966             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11967             DisplayError(buf, errno);
11968             return FALSE;
11969         } else {
11970             return LoadPosition(f, n, title);
11971         }
11972     }
11973 }
11974
11975 /* Load the nth position from the given open file, and close it */
11976 int
11977 LoadPosition (FILE *f, int positionNumber, char *title)
11978 {
11979     char *p, line[MSG_SIZ];
11980     Board initial_position;
11981     int i, j, fenMode, pn;
11982
11983     if (gameMode == Training )
11984         SetTrainingModeOff();
11985
11986     if (gameMode != BeginningOfGame) {
11987         Reset(FALSE, TRUE);
11988     }
11989     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11990         fclose(lastLoadPositionFP);
11991     }
11992     if (positionNumber == 0) positionNumber = 1;
11993     lastLoadPositionFP = f;
11994     lastLoadPositionNumber = positionNumber;
11995     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11996     if (first.pr == NoProc && !appData.noChessProgram) {
11997       StartChessProgram(&first);
11998       InitChessProgram(&first, FALSE);
11999     }
12000     pn = positionNumber;
12001     if (positionNumber < 0) {
12002         /* Negative position number means to seek to that byte offset */
12003         if (fseek(f, -positionNumber, 0) == -1) {
12004             DisplayError(_("Can't seek on position file"), 0);
12005             return FALSE;
12006         };
12007         pn = 1;
12008     } else {
12009         if (fseek(f, 0, 0) == -1) {
12010             if (f == lastLoadPositionFP ?
12011                 positionNumber == lastLoadPositionNumber + 1 :
12012                 positionNumber == 1) {
12013                 pn = 1;
12014             } else {
12015                 DisplayError(_("Can't seek on position file"), 0);
12016                 return FALSE;
12017             }
12018         }
12019     }
12020     /* See if this file is FEN or old-style xboard */
12021     if (fgets(line, MSG_SIZ, f) == NULL) {
12022         DisplayError(_("Position not found in file"), 0);
12023         return FALSE;
12024     }
12025     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
12026     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
12027
12028     if (pn >= 2) {
12029         if (fenMode || line[0] == '#') pn--;
12030         while (pn > 0) {
12031             /* skip positions before number pn */
12032             if (fgets(line, MSG_SIZ, f) == NULL) {
12033                 Reset(TRUE, TRUE);
12034                 DisplayError(_("Position not found in file"), 0);
12035                 return FALSE;
12036             }
12037             if (fenMode || line[0] == '#') pn--;
12038         }
12039     }
12040
12041     if (fenMode) {
12042         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
12043             DisplayError(_("Bad FEN position in file"), 0);
12044             return FALSE;
12045         }
12046     } else {
12047         (void) fgets(line, MSG_SIZ, f);
12048         (void) fgets(line, MSG_SIZ, f);
12049
12050         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12051             (void) fgets(line, MSG_SIZ, f);
12052             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
12053                 if (*p == ' ')
12054                   continue;
12055                 initial_position[i][j++] = CharToPiece(*p);
12056             }
12057         }
12058
12059         blackPlaysFirst = FALSE;
12060         if (!feof(f)) {
12061             (void) fgets(line, MSG_SIZ, f);
12062             if (strncmp(line, "black", strlen("black"))==0)
12063               blackPlaysFirst = TRUE;
12064         }
12065     }
12066     startedFromSetupPosition = TRUE;
12067
12068     CopyBoard(boards[0], initial_position);
12069     if (blackPlaysFirst) {
12070         currentMove = forwardMostMove = backwardMostMove = 1;
12071         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12072         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12073         CopyBoard(boards[1], initial_position);
12074         DisplayMessage("", _("Black to play"));
12075     } else {
12076         currentMove = forwardMostMove = backwardMostMove = 0;
12077         DisplayMessage("", _("White to play"));
12078     }
12079     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
12080     if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed
12081         SendToProgram("force\n", &first);
12082         SendBoard(&first, forwardMostMove);
12083     }
12084     if (appData.debugMode) {
12085 int i, j;
12086   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
12087   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
12088         fprintf(debugFP, "Load Position\n");
12089     }
12090
12091     if (positionNumber > 1) {
12092       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
12093         DisplayTitle(line);
12094     } else {
12095         DisplayTitle(title);
12096     }
12097     gameMode = EditGame;
12098     ModeHighlight();
12099     ResetClocks();
12100     timeRemaining[0][1] = whiteTimeRemaining;
12101     timeRemaining[1][1] = blackTimeRemaining;
12102     DrawPosition(FALSE, boards[currentMove]);
12103
12104     return TRUE;
12105 }
12106
12107
12108 void
12109 CopyPlayerNameIntoFileName (char **dest, char *src)
12110 {
12111     while (*src != NULLCHAR && *src != ',') {
12112         if (*src == ' ') {
12113             *(*dest)++ = '_';
12114             src++;
12115         } else {
12116             *(*dest)++ = *src++;
12117         }
12118     }
12119 }
12120
12121 char *
12122 DefaultFileName (char *ext)
12123 {
12124     static char def[MSG_SIZ];
12125     char *p;
12126
12127     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
12128         p = def;
12129         CopyPlayerNameIntoFileName(&p, gameInfo.white);
12130         *p++ = '-';
12131         CopyPlayerNameIntoFileName(&p, gameInfo.black);
12132         *p++ = '.';
12133         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
12134     } else {
12135         def[0] = NULLCHAR;
12136     }
12137     return def;
12138 }
12139
12140 /* Save the current game to the given file */
12141 int
12142 SaveGameToFile (char *filename, int append)
12143 {
12144     FILE *f;
12145     char buf[MSG_SIZ];
12146     int result, i, t,tot=0;
12147
12148     if (strcmp(filename, "-") == 0) {
12149         return SaveGame(stdout, 0, NULL);
12150     } else {
12151         for(i=0; i<10; i++) { // upto 10 tries
12152              f = fopen(filename, append ? "a" : "w");
12153              if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot);
12154              if(f || errno != 13) break;
12155              DoSleep(t = 5 + random()%11); // wait 5-15 msec
12156              tot += t;
12157         }
12158         if (f == NULL) {
12159             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12160             DisplayError(buf, errno);
12161             return FALSE;
12162         } else {
12163             safeStrCpy(buf, lastMsg, MSG_SIZ);
12164             DisplayMessage(_("Waiting for access to save file"), "");
12165             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
12166             DisplayMessage(_("Saving game"), "");
12167             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno);     // better safe than sorry...
12168             result = SaveGame(f, 0, NULL);
12169             DisplayMessage(buf, "");
12170             return result;
12171         }
12172     }
12173 }
12174
12175 char *
12176 SavePart (char *str)
12177 {
12178     static char buf[MSG_SIZ];
12179     char *p;
12180
12181     p = strchr(str, ' ');
12182     if (p == NULL) return str;
12183     strncpy(buf, str, p - str);
12184     buf[p - str] = NULLCHAR;
12185     return buf;
12186 }
12187
12188 #define PGN_MAX_LINE 75
12189
12190 #define PGN_SIDE_WHITE  0
12191 #define PGN_SIDE_BLACK  1
12192
12193 static int
12194 FindFirstMoveOutOfBook (int side)
12195 {
12196     int result = -1;
12197
12198     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
12199         int index = backwardMostMove;
12200         int has_book_hit = 0;
12201
12202         if( (index % 2) != side ) {
12203             index++;
12204         }
12205
12206         while( index < forwardMostMove ) {
12207             /* Check to see if engine is in book */
12208             int depth = pvInfoList[index].depth;
12209             int score = pvInfoList[index].score;
12210             int in_book = 0;
12211
12212             if( depth <= 2 ) {
12213                 in_book = 1;
12214             }
12215             else if( score == 0 && depth == 63 ) {
12216                 in_book = 1; /* Zappa */
12217             }
12218             else if( score == 2 && depth == 99 ) {
12219                 in_book = 1; /* Abrok */
12220             }
12221
12222             has_book_hit += in_book;
12223
12224             if( ! in_book ) {
12225                 result = index;
12226
12227                 break;
12228             }
12229
12230             index += 2;
12231         }
12232     }
12233
12234     return result;
12235 }
12236
12237 void
12238 GetOutOfBookInfo (char * buf)
12239 {
12240     int oob[2];
12241     int i;
12242     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12243
12244     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
12245     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
12246
12247     *buf = '\0';
12248
12249     if( oob[0] >= 0 || oob[1] >= 0 ) {
12250         for( i=0; i<2; i++ ) {
12251             int idx = oob[i];
12252
12253             if( idx >= 0 ) {
12254                 if( i > 0 && oob[0] >= 0 ) {
12255                     strcat( buf, "   " );
12256                 }
12257
12258                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
12259                 sprintf( buf+strlen(buf), "%s%.2f",
12260                     pvInfoList[idx].score >= 0 ? "+" : "",
12261                     pvInfoList[idx].score / 100.0 );
12262             }
12263         }
12264     }
12265 }
12266
12267 /* Save game in PGN style and close the file */
12268 int
12269 SaveGamePGN (FILE *f)
12270 {
12271     int i, offset, linelen, newblock;
12272     time_t tm;
12273 //    char *movetext;
12274     char numtext[32];
12275     int movelen, numlen, blank;
12276     char move_buffer[100]; /* [AS] Buffer for move+PV info */
12277
12278     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12279
12280     tm = time((time_t *) NULL);
12281
12282     PrintPGNTags(f, &gameInfo);
12283
12284     if (backwardMostMove > 0 || startedFromSetupPosition) {
12285         char *fen = PositionToFEN(backwardMostMove, NULL);
12286         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
12287         fprintf(f, "\n{--------------\n");
12288         PrintPosition(f, backwardMostMove);
12289         fprintf(f, "--------------}\n");
12290         free(fen);
12291     }
12292     else {
12293         /* [AS] Out of book annotation */
12294         if( appData.saveOutOfBookInfo ) {
12295             char buf[64];
12296
12297             GetOutOfBookInfo( buf );
12298
12299             if( buf[0] != '\0' ) {
12300                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
12301             }
12302         }
12303
12304         fprintf(f, "\n");
12305     }
12306
12307     i = backwardMostMove;
12308     linelen = 0;
12309     newblock = TRUE;
12310
12311     while (i < forwardMostMove) {
12312         /* Print comments preceding this move */
12313         if (commentList[i] != NULL) {
12314             if (linelen > 0) fprintf(f, "\n");
12315             fprintf(f, "%s", commentList[i]);
12316             linelen = 0;
12317             newblock = TRUE;
12318         }
12319
12320         /* Format move number */
12321         if ((i % 2) == 0)
12322           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
12323         else
12324           if (newblock)
12325             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
12326           else
12327             numtext[0] = NULLCHAR;
12328
12329         numlen = strlen(numtext);
12330         newblock = FALSE;
12331
12332         /* Print move number */
12333         blank = linelen > 0 && numlen > 0;
12334         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
12335             fprintf(f, "\n");
12336             linelen = 0;
12337             blank = 0;
12338         }
12339         if (blank) {
12340             fprintf(f, " ");
12341             linelen++;
12342         }
12343         fprintf(f, "%s", numtext);
12344         linelen += numlen;
12345
12346         /* Get move */
12347         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
12348         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
12349
12350         /* Print move */
12351         blank = linelen > 0 && movelen > 0;
12352         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12353             fprintf(f, "\n");
12354             linelen = 0;
12355             blank = 0;
12356         }
12357         if (blank) {
12358             fprintf(f, " ");
12359             linelen++;
12360         }
12361         fprintf(f, "%s", move_buffer);
12362         linelen += movelen;
12363
12364         /* [AS] Add PV info if present */
12365         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
12366             /* [HGM] add time */
12367             char buf[MSG_SIZ]; int seconds;
12368
12369             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
12370
12371             if( seconds <= 0)
12372               buf[0] = 0;
12373             else
12374               if( seconds < 30 )
12375                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
12376               else
12377                 {
12378                   seconds = (seconds + 4)/10; // round to full seconds
12379                   if( seconds < 60 )
12380                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
12381                   else
12382                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
12383                 }
12384
12385             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
12386                       pvInfoList[i].score >= 0 ? "+" : "",
12387                       pvInfoList[i].score / 100.0,
12388                       pvInfoList[i].depth,
12389                       buf );
12390
12391             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
12392
12393             /* Print score/depth */
12394             blank = linelen > 0 && movelen > 0;
12395             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
12396                 fprintf(f, "\n");
12397                 linelen = 0;
12398                 blank = 0;
12399             }
12400             if (blank) {
12401                 fprintf(f, " ");
12402                 linelen++;
12403             }
12404             fprintf(f, "%s", move_buffer);
12405             linelen += movelen;
12406         }
12407
12408         i++;
12409     }
12410
12411     /* Start a new line */
12412     if (linelen > 0) fprintf(f, "\n");
12413
12414     /* Print comments after last move */
12415     if (commentList[i] != NULL) {
12416         fprintf(f, "%s\n", commentList[i]);
12417     }
12418
12419     /* Print result */
12420     if (gameInfo.resultDetails != NULL &&
12421         gameInfo.resultDetails[0] != NULLCHAR) {
12422         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
12423                 PGNResult(gameInfo.result));
12424     } else {
12425         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12426     }
12427
12428     fclose(f);
12429     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12430     return TRUE;
12431 }
12432
12433 /* Save game in old style and close the file */
12434 int
12435 SaveGameOldStyle (FILE *f)
12436 {
12437     int i, offset;
12438     time_t tm;
12439
12440     tm = time((time_t *) NULL);
12441
12442     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
12443     PrintOpponents(f);
12444
12445     if (backwardMostMove > 0 || startedFromSetupPosition) {
12446         fprintf(f, "\n[--------------\n");
12447         PrintPosition(f, backwardMostMove);
12448         fprintf(f, "--------------]\n");
12449     } else {
12450         fprintf(f, "\n");
12451     }
12452
12453     i = backwardMostMove;
12454     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
12455
12456     while (i < forwardMostMove) {
12457         if (commentList[i] != NULL) {
12458             fprintf(f, "[%s]\n", commentList[i]);
12459         }
12460
12461         if ((i % 2) == 1) {
12462             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
12463             i++;
12464         } else {
12465             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
12466             i++;
12467             if (commentList[i] != NULL) {
12468                 fprintf(f, "\n");
12469                 continue;
12470             }
12471             if (i >= forwardMostMove) {
12472                 fprintf(f, "\n");
12473                 break;
12474             }
12475             fprintf(f, "%s\n", parseList[i]);
12476             i++;
12477         }
12478     }
12479
12480     if (commentList[i] != NULL) {
12481         fprintf(f, "[%s]\n", commentList[i]);
12482     }
12483
12484     /* This isn't really the old style, but it's close enough */
12485     if (gameInfo.resultDetails != NULL &&
12486         gameInfo.resultDetails[0] != NULLCHAR) {
12487         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12488                 gameInfo.resultDetails);
12489     } else {
12490         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12491     }
12492
12493     fclose(f);
12494     return TRUE;
12495 }
12496
12497 /* Save the current game to open file f and close the file */
12498 int
12499 SaveGame (FILE *f, int dummy, char *dummy2)
12500 {
12501     if (gameMode == EditPosition) EditPositionDone(TRUE);
12502     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12503     if (appData.oldSaveStyle)
12504       return SaveGameOldStyle(f);
12505     else
12506       return SaveGamePGN(f);
12507 }
12508
12509 /* Save the current position to the given file */
12510 int
12511 SavePositionToFile (char *filename)
12512 {
12513     FILE *f;
12514     char buf[MSG_SIZ];
12515
12516     if (strcmp(filename, "-") == 0) {
12517         return SavePosition(stdout, 0, NULL);
12518     } else {
12519         f = fopen(filename, "a");
12520         if (f == NULL) {
12521             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12522             DisplayError(buf, errno);
12523             return FALSE;
12524         } else {
12525             safeStrCpy(buf, lastMsg, MSG_SIZ);
12526             DisplayMessage(_("Waiting for access to save file"), "");
12527             flock(fileno(f), LOCK_EX); // [HGM] lock
12528             DisplayMessage(_("Saving position"), "");
12529             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12530             SavePosition(f, 0, NULL);
12531             DisplayMessage(buf, "");
12532             return TRUE;
12533         }
12534     }
12535 }
12536
12537 /* Save the current position to the given open file and close the file */
12538 int
12539 SavePosition (FILE *f, int dummy, char *dummy2)
12540 {
12541     time_t tm;
12542     char *fen;
12543
12544     if (gameMode == EditPosition) EditPositionDone(TRUE);
12545     if (appData.oldSaveStyle) {
12546         tm = time((time_t *) NULL);
12547
12548         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12549         PrintOpponents(f);
12550         fprintf(f, "[--------------\n");
12551         PrintPosition(f, currentMove);
12552         fprintf(f, "--------------]\n");
12553     } else {
12554         fen = PositionToFEN(currentMove, NULL);
12555         fprintf(f, "%s\n", fen);
12556         free(fen);
12557     }
12558     fclose(f);
12559     return TRUE;
12560 }
12561
12562 void
12563 ReloadCmailMsgEvent (int unregister)
12564 {
12565 #if !WIN32
12566     static char *inFilename = NULL;
12567     static char *outFilename;
12568     int i;
12569     struct stat inbuf, outbuf;
12570     int status;
12571
12572     /* Any registered moves are unregistered if unregister is set, */
12573     /* i.e. invoked by the signal handler */
12574     if (unregister) {
12575         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12576             cmailMoveRegistered[i] = FALSE;
12577             if (cmailCommentList[i] != NULL) {
12578                 free(cmailCommentList[i]);
12579                 cmailCommentList[i] = NULL;
12580             }
12581         }
12582         nCmailMovesRegistered = 0;
12583     }
12584
12585     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12586         cmailResult[i] = CMAIL_NOT_RESULT;
12587     }
12588     nCmailResults = 0;
12589
12590     if (inFilename == NULL) {
12591         /* Because the filenames are static they only get malloced once  */
12592         /* and they never get freed                                      */
12593         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12594         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12595
12596         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12597         sprintf(outFilename, "%s.out", appData.cmailGameName);
12598     }
12599
12600     status = stat(outFilename, &outbuf);
12601     if (status < 0) {
12602         cmailMailedMove = FALSE;
12603     } else {
12604         status = stat(inFilename, &inbuf);
12605         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12606     }
12607
12608     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12609        counts the games, notes how each one terminated, etc.
12610
12611        It would be nice to remove this kludge and instead gather all
12612        the information while building the game list.  (And to keep it
12613        in the game list nodes instead of having a bunch of fixed-size
12614        parallel arrays.)  Note this will require getting each game's
12615        termination from the PGN tags, as the game list builder does
12616        not process the game moves.  --mann
12617        */
12618     cmailMsgLoaded = TRUE;
12619     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12620
12621     /* Load first game in the file or popup game menu */
12622     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12623
12624 #endif /* !WIN32 */
12625     return;
12626 }
12627
12628 int
12629 RegisterMove ()
12630 {
12631     FILE *f;
12632     char string[MSG_SIZ];
12633
12634     if (   cmailMailedMove
12635         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12636         return TRUE;            /* Allow free viewing  */
12637     }
12638
12639     /* Unregister move to ensure that we don't leave RegisterMove        */
12640     /* with the move registered when the conditions for registering no   */
12641     /* longer hold                                                       */
12642     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12643         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12644         nCmailMovesRegistered --;
12645
12646         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12647           {
12648               free(cmailCommentList[lastLoadGameNumber - 1]);
12649               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12650           }
12651     }
12652
12653     if (cmailOldMove == -1) {
12654         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12655         return FALSE;
12656     }
12657
12658     if (currentMove > cmailOldMove + 1) {
12659         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12660         return FALSE;
12661     }
12662
12663     if (currentMove < cmailOldMove) {
12664         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12665         return FALSE;
12666     }
12667
12668     if (forwardMostMove > currentMove) {
12669         /* Silently truncate extra moves */
12670         TruncateGame();
12671     }
12672
12673     if (   (currentMove == cmailOldMove + 1)
12674         || (   (currentMove == cmailOldMove)
12675             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12676                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12677         if (gameInfo.result != GameUnfinished) {
12678             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12679         }
12680
12681         if (commentList[currentMove] != NULL) {
12682             cmailCommentList[lastLoadGameNumber - 1]
12683               = StrSave(commentList[currentMove]);
12684         }
12685         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12686
12687         if (appData.debugMode)
12688           fprintf(debugFP, "Saving %s for game %d\n",
12689                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12690
12691         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12692
12693         f = fopen(string, "w");
12694         if (appData.oldSaveStyle) {
12695             SaveGameOldStyle(f); /* also closes the file */
12696
12697             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12698             f = fopen(string, "w");
12699             SavePosition(f, 0, NULL); /* also closes the file */
12700         } else {
12701             fprintf(f, "{--------------\n");
12702             PrintPosition(f, currentMove);
12703             fprintf(f, "--------------}\n\n");
12704
12705             SaveGame(f, 0, NULL); /* also closes the file*/
12706         }
12707
12708         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12709         nCmailMovesRegistered ++;
12710     } else if (nCmailGames == 1) {
12711         DisplayError(_("You have not made a move yet"), 0);
12712         return FALSE;
12713     }
12714
12715     return TRUE;
12716 }
12717
12718 void
12719 MailMoveEvent ()
12720 {
12721 #if !WIN32
12722     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12723     FILE *commandOutput;
12724     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12725     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12726     int nBuffers;
12727     int i;
12728     int archived;
12729     char *arcDir;
12730
12731     if (! cmailMsgLoaded) {
12732         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12733         return;
12734     }
12735
12736     if (nCmailGames == nCmailResults) {
12737         DisplayError(_("No unfinished games"), 0);
12738         return;
12739     }
12740
12741 #if CMAIL_PROHIBIT_REMAIL
12742     if (cmailMailedMove) {
12743       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);
12744         DisplayError(msg, 0);
12745         return;
12746     }
12747 #endif
12748
12749     if (! (cmailMailedMove || RegisterMove())) return;
12750
12751     if (   cmailMailedMove
12752         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12753       snprintf(string, MSG_SIZ, partCommandString,
12754                appData.debugMode ? " -v" : "", appData.cmailGameName);
12755         commandOutput = popen(string, "r");
12756
12757         if (commandOutput == NULL) {
12758             DisplayError(_("Failed to invoke cmail"), 0);
12759         } else {
12760             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12761                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12762             }
12763             if (nBuffers > 1) {
12764                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12765                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12766                 nBytes = MSG_SIZ - 1;
12767             } else {
12768                 (void) memcpy(msg, buffer, nBytes);
12769             }
12770             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12771
12772             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12773                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12774
12775                 archived = TRUE;
12776                 for (i = 0; i < nCmailGames; i ++) {
12777                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12778                         archived = FALSE;
12779                     }
12780                 }
12781                 if (   archived
12782                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12783                         != NULL)) {
12784                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12785                            arcDir,
12786                            appData.cmailGameName,
12787                            gameInfo.date);
12788                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12789                     cmailMsgLoaded = FALSE;
12790                 }
12791             }
12792
12793             DisplayInformation(msg);
12794             pclose(commandOutput);
12795         }
12796     } else {
12797         if ((*cmailMsg) != '\0') {
12798             DisplayInformation(cmailMsg);
12799         }
12800     }
12801
12802     return;
12803 #endif /* !WIN32 */
12804 }
12805
12806 char *
12807 CmailMsg ()
12808 {
12809 #if WIN32
12810     return NULL;
12811 #else
12812     int  prependComma = 0;
12813     char number[5];
12814     char string[MSG_SIZ];       /* Space for game-list */
12815     int  i;
12816
12817     if (!cmailMsgLoaded) return "";
12818
12819     if (cmailMailedMove) {
12820       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12821     } else {
12822         /* Create a list of games left */
12823       snprintf(string, MSG_SIZ, "[");
12824         for (i = 0; i < nCmailGames; i ++) {
12825             if (! (   cmailMoveRegistered[i]
12826                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12827                 if (prependComma) {
12828                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12829                 } else {
12830                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12831                     prependComma = 1;
12832                 }
12833
12834                 strcat(string, number);
12835             }
12836         }
12837         strcat(string, "]");
12838
12839         if (nCmailMovesRegistered + nCmailResults == 0) {
12840             switch (nCmailGames) {
12841               case 1:
12842                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12843                 break;
12844
12845               case 2:
12846                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12847                 break;
12848
12849               default:
12850                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12851                          nCmailGames);
12852                 break;
12853             }
12854         } else {
12855             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12856               case 1:
12857                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12858                          string);
12859                 break;
12860
12861               case 0:
12862                 if (nCmailResults == nCmailGames) {
12863                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12864                 } else {
12865                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12866                 }
12867                 break;
12868
12869               default:
12870                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12871                          string);
12872             }
12873         }
12874     }
12875     return cmailMsg;
12876 #endif /* WIN32 */
12877 }
12878
12879 void
12880 ResetGameEvent ()
12881 {
12882     if (gameMode == Training)
12883       SetTrainingModeOff();
12884
12885     Reset(TRUE, TRUE);
12886     cmailMsgLoaded = FALSE;
12887     if (appData.icsActive) {
12888       SendToICS(ics_prefix);
12889       SendToICS("refresh\n");
12890     }
12891 }
12892
12893 void
12894 ExitEvent (int status)
12895 {
12896     exiting++;
12897     if (exiting > 2) {
12898       /* Give up on clean exit */
12899       exit(status);
12900     }
12901     if (exiting > 1) {
12902       /* Keep trying for clean exit */
12903       return;
12904     }
12905
12906     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12907
12908     if (telnetISR != NULL) {
12909       RemoveInputSource(telnetISR);
12910     }
12911     if (icsPR != NoProc) {
12912       DestroyChildProcess(icsPR, TRUE);
12913     }
12914
12915     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12916     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12917
12918     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12919     /* make sure this other one finishes before killing it!                  */
12920     if(endingGame) { int count = 0;
12921         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12922         while(endingGame && count++ < 10) DoSleep(1);
12923         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12924     }
12925
12926     /* Kill off chess programs */
12927     if (first.pr != NoProc) {
12928         ExitAnalyzeMode();
12929
12930         DoSleep( appData.delayBeforeQuit );
12931         SendToProgram("quit\n", &first);
12932         DoSleep( appData.delayAfterQuit );
12933         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12934     }
12935     if (second.pr != NoProc) {
12936         DoSleep( appData.delayBeforeQuit );
12937         SendToProgram("quit\n", &second);
12938         DoSleep( appData.delayAfterQuit );
12939         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12940     }
12941     if (first.isr != NULL) {
12942         RemoveInputSource(first.isr);
12943     }
12944     if (second.isr != NULL) {
12945         RemoveInputSource(second.isr);
12946     }
12947
12948     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12949     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12950
12951     ShutDownFrontEnd();
12952     exit(status);
12953 }
12954
12955 void
12956 PauseEvent ()
12957 {
12958     if (appData.debugMode)
12959         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12960     if (pausing) {
12961         pausing = FALSE;
12962         ModeHighlight();
12963         if (gameMode == MachinePlaysWhite ||
12964             gameMode == MachinePlaysBlack) {
12965             StartClocks();
12966         } else {
12967             DisplayBothClocks();
12968         }
12969         if (gameMode == PlayFromGameFile) {
12970             if (appData.timeDelay >= 0)
12971                 AutoPlayGameLoop();
12972         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12973             Reset(FALSE, TRUE);
12974             SendToICS(ics_prefix);
12975             SendToICS("refresh\n");
12976         } else if (currentMove < forwardMostMove) {
12977             ForwardInner(forwardMostMove);
12978         }
12979         pauseExamInvalid = FALSE;
12980     } else {
12981         switch (gameMode) {
12982           default:
12983             return;
12984           case IcsExamining:
12985             pauseExamForwardMostMove = forwardMostMove;
12986             pauseExamInvalid = FALSE;
12987             /* fall through */
12988           case IcsObserving:
12989           case IcsPlayingWhite:
12990           case IcsPlayingBlack:
12991             pausing = TRUE;
12992             ModeHighlight();
12993             return;
12994           case PlayFromGameFile:
12995             (void) StopLoadGameTimer();
12996             pausing = TRUE;
12997             ModeHighlight();
12998             break;
12999           case BeginningOfGame:
13000             if (appData.icsActive) return;
13001             /* else fall through */
13002           case MachinePlaysWhite:
13003           case MachinePlaysBlack:
13004           case TwoMachinesPlay:
13005             if (forwardMostMove == 0)
13006               return;           /* don't pause if no one has moved */
13007             if ((gameMode == MachinePlaysWhite &&
13008                  !WhiteOnMove(forwardMostMove)) ||
13009                 (gameMode == MachinePlaysBlack &&
13010                  WhiteOnMove(forwardMostMove))) {
13011                 StopClocks();
13012             }
13013             pausing = TRUE;
13014             ModeHighlight();
13015             break;
13016         }
13017     }
13018 }
13019
13020 void
13021 EditCommentEvent ()
13022 {
13023     char title[MSG_SIZ];
13024
13025     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
13026       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
13027     } else {
13028       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
13029                WhiteOnMove(currentMove - 1) ? " " : ".. ",
13030                parseList[currentMove - 1]);
13031     }
13032
13033     EditCommentPopUp(currentMove, title, commentList[currentMove]);
13034 }
13035
13036
13037 void
13038 EditTagsEvent ()
13039 {
13040     char *tags = PGNTags(&gameInfo);
13041     bookUp = FALSE;
13042     EditTagsPopUp(tags, NULL);
13043     free(tags);
13044 }
13045
13046 void
13047 AnalyzeModeEvent ()
13048 {
13049     if (appData.noChessProgram || gameMode == AnalyzeMode)
13050       return;
13051
13052     if (gameMode != AnalyzeFile) {
13053         if (!appData.icsEngineAnalyze) {
13054                EditGameEvent();
13055                if (gameMode != EditGame) return;
13056         }
13057         ResurrectChessProgram();
13058         SendToProgram("analyze\n", &first);
13059         first.analyzing = TRUE;
13060         /*first.maybeThinking = TRUE;*/
13061         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13062         EngineOutputPopUp();
13063     }
13064     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
13065     pausing = FALSE;
13066     ModeHighlight();
13067     SetGameInfo();
13068
13069     StartAnalysisClock();
13070     GetTimeMark(&lastNodeCountTime);
13071     lastNodeCount = 0;
13072 }
13073
13074 void
13075 AnalyzeFileEvent ()
13076 {
13077     if (appData.noChessProgram || gameMode == AnalyzeFile)
13078       return;
13079
13080     if (gameMode != AnalyzeMode) {
13081         EditGameEvent();
13082         if (gameMode != EditGame) return;
13083         ResurrectChessProgram();
13084         SendToProgram("analyze\n", &first);
13085         first.analyzing = TRUE;
13086         /*first.maybeThinking = TRUE;*/
13087         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13088         EngineOutputPopUp();
13089     }
13090     gameMode = AnalyzeFile;
13091     pausing = FALSE;
13092     ModeHighlight();
13093     SetGameInfo();
13094
13095     StartAnalysisClock();
13096     GetTimeMark(&lastNodeCountTime);
13097     lastNodeCount = 0;
13098     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
13099 }
13100
13101 void
13102 MachineWhiteEvent ()
13103 {
13104     char buf[MSG_SIZ];
13105     char *bookHit = NULL;
13106
13107     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
13108       return;
13109
13110
13111     if (gameMode == PlayFromGameFile ||
13112         gameMode == TwoMachinesPlay  ||
13113         gameMode == Training         ||
13114         gameMode == AnalyzeMode      ||
13115         gameMode == EndOfGame)
13116         EditGameEvent();
13117
13118     if (gameMode == EditPosition)
13119         EditPositionDone(TRUE);
13120
13121     if (!WhiteOnMove(currentMove)) {
13122         DisplayError(_("It is not White's turn"), 0);
13123         return;
13124     }
13125
13126     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13127       ExitAnalyzeMode();
13128
13129     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13130         gameMode == AnalyzeFile)
13131         TruncateGame();
13132
13133     ResurrectChessProgram();    /* in case it isn't running */
13134     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
13135         gameMode = MachinePlaysWhite;
13136         ResetClocks();
13137     } else
13138     gameMode = MachinePlaysWhite;
13139     pausing = FALSE;
13140     ModeHighlight();
13141     SetGameInfo();
13142     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13143     DisplayTitle(buf);
13144     if (first.sendName) {
13145       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
13146       SendToProgram(buf, &first);
13147     }
13148     if (first.sendTime) {
13149       if (first.useColors) {
13150         SendToProgram("black\n", &first); /*gnu kludge*/
13151       }
13152       SendTimeRemaining(&first, TRUE);
13153     }
13154     if (first.useColors) {
13155       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
13156     }
13157     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13158     SetMachineThinkingEnables();
13159     first.maybeThinking = TRUE;
13160     StartClocks();
13161     firstMove = FALSE;
13162
13163     if (appData.autoFlipView && !flipView) {
13164       flipView = !flipView;
13165       DrawPosition(FALSE, NULL);
13166       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13167     }
13168
13169     if(bookHit) { // [HGM] book: simulate book reply
13170         static char bookMove[MSG_SIZ]; // a bit generous?
13171
13172         programStats.nodes = programStats.depth = programStats.time =
13173         programStats.score = programStats.got_only_move = 0;
13174         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13175
13176         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13177         strcat(bookMove, bookHit);
13178         HandleMachineMove(bookMove, &first);
13179     }
13180 }
13181
13182 void
13183 MachineBlackEvent ()
13184 {
13185   char buf[MSG_SIZ];
13186   char *bookHit = NULL;
13187
13188     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
13189         return;
13190
13191
13192     if (gameMode == PlayFromGameFile ||
13193         gameMode == TwoMachinesPlay  ||
13194         gameMode == Training         ||
13195         gameMode == AnalyzeMode      ||
13196         gameMode == EndOfGame)
13197         EditGameEvent();
13198
13199     if (gameMode == EditPosition)
13200         EditPositionDone(TRUE);
13201
13202     if (WhiteOnMove(currentMove)) {
13203         DisplayError(_("It is not Black's turn"), 0);
13204         return;
13205     }
13206
13207     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
13208       ExitAnalyzeMode();
13209
13210     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13211         gameMode == AnalyzeFile)
13212         TruncateGame();
13213
13214     ResurrectChessProgram();    /* in case it isn't running */
13215     gameMode = MachinePlaysBlack;
13216     pausing = FALSE;
13217     ModeHighlight();
13218     SetGameInfo();
13219     snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13220     DisplayTitle(buf);
13221     if (first.sendName) {
13222       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
13223       SendToProgram(buf, &first);
13224     }
13225     if (first.sendTime) {
13226       if (first.useColors) {
13227         SendToProgram("white\n", &first); /*gnu kludge*/
13228       }
13229       SendTimeRemaining(&first, FALSE);
13230     }
13231     if (first.useColors) {
13232       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
13233     }
13234     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
13235     SetMachineThinkingEnables();
13236     first.maybeThinking = TRUE;
13237     StartClocks();
13238
13239     if (appData.autoFlipView && flipView) {
13240       flipView = !flipView;
13241       DrawPosition(FALSE, NULL);
13242       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
13243     }
13244     if(bookHit) { // [HGM] book: simulate book reply
13245         static char bookMove[MSG_SIZ]; // a bit generous?
13246
13247         programStats.nodes = programStats.depth = programStats.time =
13248         programStats.score = programStats.got_only_move = 0;
13249         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13250
13251         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13252         strcat(bookMove, bookHit);
13253         HandleMachineMove(bookMove, &first);
13254     }
13255 }
13256
13257
13258 void
13259 DisplayTwoMachinesTitle ()
13260 {
13261     char buf[MSG_SIZ];
13262     if (appData.matchGames > 0) {
13263         if(appData.tourneyFile[0]) {
13264           snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)",
13265                    gameInfo.white, _("vs."), gameInfo.black,
13266                    nextGame+1, appData.matchGames+1,
13267                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
13268         } else 
13269         if (first.twoMachinesColor[0] == 'w') {
13270           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13271                    gameInfo.white, _("vs."),  gameInfo.black,
13272                    first.matchWins, second.matchWins,
13273                    matchGame - 1 - (first.matchWins + second.matchWins));
13274         } else {
13275           snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)",
13276                    gameInfo.white, _("vs."), gameInfo.black,
13277                    second.matchWins, first.matchWins,
13278                    matchGame - 1 - (first.matchWins + second.matchWins));
13279         }
13280     } else {
13281       snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black);
13282     }
13283     DisplayTitle(buf);
13284 }
13285
13286 void
13287 SettingsMenuIfReady ()
13288 {
13289   if (second.lastPing != second.lastPong) {
13290     DisplayMessage("", _("Waiting for second chess program"));
13291     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
13292     return;
13293   }
13294   ThawUI();
13295   DisplayMessage("", "");
13296   SettingsPopUp(&second);
13297 }
13298
13299 int
13300 WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
13301 {
13302     char buf[MSG_SIZ];
13303     if (cps->pr == NoProc) {
13304         StartChessProgram(cps);
13305         if (cps->protocolVersion == 1) {
13306           retry();
13307         } else {
13308           /* kludge: allow timeout for initial "feature" command */
13309           FreezeUI();
13310           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
13311           DisplayMessage("", buf);
13312           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
13313         }
13314         return 1;
13315     }
13316     return 0;
13317 }
13318
13319 void
13320 TwoMachinesEvent P((void))
13321 {
13322     int i;
13323     char buf[MSG_SIZ];
13324     ChessProgramState *onmove;
13325     char *bookHit = NULL;
13326     static int stalling = 0;
13327     TimeMark now;
13328     long wait;
13329
13330     if (appData.noChessProgram) return;
13331
13332     switch (gameMode) {
13333       case TwoMachinesPlay:
13334         return;
13335       case MachinePlaysWhite:
13336       case MachinePlaysBlack:
13337         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13338             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13339             return;
13340         }
13341         /* fall through */
13342       case BeginningOfGame:
13343       case PlayFromGameFile:
13344       case EndOfGame:
13345         EditGameEvent();
13346         if (gameMode != EditGame) return;
13347         break;
13348       case EditPosition:
13349         EditPositionDone(TRUE);
13350         break;
13351       case AnalyzeMode:
13352       case AnalyzeFile:
13353         ExitAnalyzeMode();
13354         break;
13355       case EditGame:
13356       default:
13357         break;
13358     }
13359
13360 //    forwardMostMove = currentMove;
13361     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
13362
13363     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
13364
13365     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
13366     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
13367       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13368       return;
13369     }
13370     if(!stalling) {
13371       InitChessProgram(&second, FALSE); // unbalances ping of second engine
13372       SendToProgram("force\n", &second);
13373       stalling = 1;
13374       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
13375       return;
13376     }
13377     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
13378     if(appData.matchPause>10000 || appData.matchPause<10)
13379                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
13380     wait = SubtractTimeMarks(&now, &pauseStart);
13381     if(wait < appData.matchPause) {
13382         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
13383         return;
13384     }
13385     stalling = 0;
13386     DisplayMessage("", "");
13387     if (startedFromSetupPosition) {
13388         SendBoard(&second, backwardMostMove);
13389     if (appData.debugMode) {
13390         fprintf(debugFP, "Two Machines\n");
13391     }
13392     }
13393     for (i = backwardMostMove; i < forwardMostMove; i++) {
13394         SendMoveToProgram(i, &second);
13395     }
13396
13397     gameMode = TwoMachinesPlay;
13398     pausing = FALSE;
13399     ModeHighlight(); // [HGM] logo: this triggers display update of logos
13400     SetGameInfo();
13401     DisplayTwoMachinesTitle();
13402     firstMove = TRUE;
13403     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
13404         onmove = &first;
13405     } else {
13406         onmove = &second;
13407     }
13408     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
13409     SendToProgram(first.computerString, &first);
13410     if (first.sendName) {
13411       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
13412       SendToProgram(buf, &first);
13413     }
13414     SendToProgram(second.computerString, &second);
13415     if (second.sendName) {
13416       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
13417       SendToProgram(buf, &second);
13418     }
13419
13420     ResetClocks();
13421     if (!first.sendTime || !second.sendTime) {
13422         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13423         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13424     }
13425     if (onmove->sendTime) {
13426       if (onmove->useColors) {
13427         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
13428       }
13429       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
13430     }
13431     if (onmove->useColors) {
13432       SendToProgram(onmove->twoMachinesColor, onmove);
13433     }
13434     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
13435 //    SendToProgram("go\n", onmove);
13436     onmove->maybeThinking = TRUE;
13437     SetMachineThinkingEnables();
13438
13439     StartClocks();
13440
13441     if(bookHit) { // [HGM] book: simulate book reply
13442         static char bookMove[MSG_SIZ]; // a bit generous?
13443
13444         programStats.nodes = programStats.depth = programStats.time =
13445         programStats.score = programStats.got_only_move = 0;
13446         sprintf(programStats.movelist, "%s (xbook)", bookHit);
13447
13448         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
13449         strcat(bookMove, bookHit);
13450         savedMessage = bookMove; // args for deferred call
13451         savedState = onmove;
13452         ScheduleDelayedEvent(DeferredBookMove, 1);
13453     }
13454 }
13455
13456 void
13457 TrainingEvent ()
13458 {
13459     if (gameMode == Training) {
13460       SetTrainingModeOff();
13461       gameMode = PlayFromGameFile;
13462       DisplayMessage("", _("Training mode off"));
13463     } else {
13464       gameMode = Training;
13465       animateTraining = appData.animate;
13466
13467       /* make sure we are not already at the end of the game */
13468       if (currentMove < forwardMostMove) {
13469         SetTrainingModeOn();
13470         DisplayMessage("", _("Training mode on"));
13471       } else {
13472         gameMode = PlayFromGameFile;
13473         DisplayError(_("Already at end of game"), 0);
13474       }
13475     }
13476     ModeHighlight();
13477 }
13478
13479 void
13480 IcsClientEvent ()
13481 {
13482     if (!appData.icsActive) return;
13483     switch (gameMode) {
13484       case IcsPlayingWhite:
13485       case IcsPlayingBlack:
13486       case IcsObserving:
13487       case IcsIdle:
13488       case BeginningOfGame:
13489       case IcsExamining:
13490         return;
13491
13492       case EditGame:
13493         break;
13494
13495       case EditPosition:
13496         EditPositionDone(TRUE);
13497         break;
13498
13499       case AnalyzeMode:
13500       case AnalyzeFile:
13501         ExitAnalyzeMode();
13502         break;
13503
13504       default:
13505         EditGameEvent();
13506         break;
13507     }
13508
13509     gameMode = IcsIdle;
13510     ModeHighlight();
13511     return;
13512 }
13513
13514 void
13515 EditGameEvent ()
13516 {
13517     int i;
13518
13519     switch (gameMode) {
13520       case Training:
13521         SetTrainingModeOff();
13522         break;
13523       case MachinePlaysWhite:
13524       case MachinePlaysBlack:
13525       case BeginningOfGame:
13526         SendToProgram("force\n", &first);
13527         SetUserThinkingEnables();
13528         break;
13529       case PlayFromGameFile:
13530         (void) StopLoadGameTimer();
13531         if (gameFileFP != NULL) {
13532             gameFileFP = NULL;
13533         }
13534         break;
13535       case EditPosition:
13536         EditPositionDone(TRUE);
13537         break;
13538       case AnalyzeMode:
13539       case AnalyzeFile:
13540         ExitAnalyzeMode();
13541         SendToProgram("force\n", &first);
13542         break;
13543       case TwoMachinesPlay:
13544         GameEnds(EndOfFile, NULL, GE_PLAYER);
13545         ResurrectChessProgram();
13546         SetUserThinkingEnables();
13547         break;
13548       case EndOfGame:
13549         ResurrectChessProgram();
13550         break;
13551       case IcsPlayingBlack:
13552       case IcsPlayingWhite:
13553         DisplayError(_("Warning: You are still playing a game"), 0);
13554         break;
13555       case IcsObserving:
13556         DisplayError(_("Warning: You are still observing a game"), 0);
13557         break;
13558       case IcsExamining:
13559         DisplayError(_("Warning: You are still examining a game"), 0);
13560         break;
13561       case IcsIdle:
13562         break;
13563       case EditGame:
13564       default:
13565         return;
13566     }
13567
13568     pausing = FALSE;
13569     StopClocks();
13570     first.offeredDraw = second.offeredDraw = 0;
13571
13572     if (gameMode == PlayFromGameFile) {
13573         whiteTimeRemaining = timeRemaining[0][currentMove];
13574         blackTimeRemaining = timeRemaining[1][currentMove];
13575         DisplayTitle("");
13576     }
13577
13578     if (gameMode == MachinePlaysWhite ||
13579         gameMode == MachinePlaysBlack ||
13580         gameMode == TwoMachinesPlay ||
13581         gameMode == EndOfGame) {
13582         i = forwardMostMove;
13583         while (i > currentMove) {
13584             SendToProgram("undo\n", &first);
13585             i--;
13586         }
13587         if(!adjustedClock) {
13588         whiteTimeRemaining = timeRemaining[0][currentMove];
13589         blackTimeRemaining = timeRemaining[1][currentMove];
13590         DisplayBothClocks();
13591         }
13592         if (whiteFlag || blackFlag) {
13593             whiteFlag = blackFlag = 0;
13594         }
13595         DisplayTitle("");
13596     }
13597
13598     gameMode = EditGame;
13599     ModeHighlight();
13600     SetGameInfo();
13601 }
13602
13603
13604 void
13605 EditPositionEvent ()
13606 {
13607     if (gameMode == EditPosition) {
13608         EditGameEvent();
13609         return;
13610     }
13611
13612     EditGameEvent();
13613     if (gameMode != EditGame) return;
13614
13615     gameMode = EditPosition;
13616     ModeHighlight();
13617     SetGameInfo();
13618     if (currentMove > 0)
13619       CopyBoard(boards[0], boards[currentMove]);
13620
13621     blackPlaysFirst = !WhiteOnMove(currentMove);
13622     ResetClocks();
13623     currentMove = forwardMostMove = backwardMostMove = 0;
13624     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13625     DisplayMove(-1);
13626 }
13627
13628 void
13629 ExitAnalyzeMode ()
13630 {
13631     /* [DM] icsEngineAnalyze - possible call from other functions */
13632     if (appData.icsEngineAnalyze) {
13633         appData.icsEngineAnalyze = FALSE;
13634
13635         DisplayMessage("",_("Close ICS engine analyze..."));
13636     }
13637     if (first.analysisSupport && first.analyzing) {
13638       SendToProgram("exit\n", &first);
13639       first.analyzing = FALSE;
13640     }
13641     thinkOutput[0] = NULLCHAR;
13642 }
13643
13644 void
13645 EditPositionDone (Boolean fakeRights)
13646 {
13647     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13648
13649     startedFromSetupPosition = TRUE;
13650     InitChessProgram(&first, FALSE);
13651     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13652       boards[0][EP_STATUS] = EP_NONE;
13653       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13654     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13655         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13656         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13657       } else boards[0][CASTLING][2] = NoRights;
13658     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13659         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13660         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13661       } else boards[0][CASTLING][5] = NoRights;
13662     }
13663     SendToProgram("force\n", &first);
13664     if (blackPlaysFirst) {
13665         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13666         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13667         currentMove = forwardMostMove = backwardMostMove = 1;
13668         CopyBoard(boards[1], boards[0]);
13669     } else {
13670         currentMove = forwardMostMove = backwardMostMove = 0;
13671     }
13672     SendBoard(&first, forwardMostMove);
13673     if (appData.debugMode) {
13674         fprintf(debugFP, "EditPosDone\n");
13675     }
13676     DisplayTitle("");
13677     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13678     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13679     gameMode = EditGame;
13680     ModeHighlight();
13681     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13682     ClearHighlights(); /* [AS] */
13683 }
13684
13685 /* Pause for `ms' milliseconds */
13686 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13687 void
13688 TimeDelay (long ms)
13689 {
13690     TimeMark m1, m2;
13691
13692     GetTimeMark(&m1);
13693     do {
13694         GetTimeMark(&m2);
13695     } while (SubtractTimeMarks(&m2, &m1) < ms);
13696 }
13697
13698 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13699 void
13700 SendMultiLineToICS (char *buf)
13701 {
13702     char temp[MSG_SIZ+1], *p;
13703     int len;
13704
13705     len = strlen(buf);
13706     if (len > MSG_SIZ)
13707       len = MSG_SIZ;
13708
13709     strncpy(temp, buf, len);
13710     temp[len] = 0;
13711
13712     p = temp;
13713     while (*p) {
13714         if (*p == '\n' || *p == '\r')
13715           *p = ' ';
13716         ++p;
13717     }
13718
13719     strcat(temp, "\n");
13720     SendToICS(temp);
13721     SendToPlayer(temp, strlen(temp));
13722 }
13723
13724 void
13725 SetWhiteToPlayEvent ()
13726 {
13727     if (gameMode == EditPosition) {
13728         blackPlaysFirst = FALSE;
13729         DisplayBothClocks();    /* works because currentMove is 0 */
13730     } else if (gameMode == IcsExamining) {
13731         SendToICS(ics_prefix);
13732         SendToICS("tomove white\n");
13733     }
13734 }
13735
13736 void
13737 SetBlackToPlayEvent ()
13738 {
13739     if (gameMode == EditPosition) {
13740         blackPlaysFirst = TRUE;
13741         currentMove = 1;        /* kludge */
13742         DisplayBothClocks();
13743         currentMove = 0;
13744     } else if (gameMode == IcsExamining) {
13745         SendToICS(ics_prefix);
13746         SendToICS("tomove black\n");
13747     }
13748 }
13749
13750 void
13751 EditPositionMenuEvent (ChessSquare selection, int x, int y)
13752 {
13753     char buf[MSG_SIZ];
13754     ChessSquare piece = boards[0][y][x];
13755
13756     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13757
13758     switch (selection) {
13759       case ClearBoard:
13760         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13761             SendToICS(ics_prefix);
13762             SendToICS("bsetup clear\n");
13763         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13764             SendToICS(ics_prefix);
13765             SendToICS("clearboard\n");
13766         } else {
13767             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13768                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13769                 for (y = 0; y < BOARD_HEIGHT; y++) {
13770                     if (gameMode == IcsExamining) {
13771                         if (boards[currentMove][y][x] != EmptySquare) {
13772                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13773                                     AAA + x, ONE + y);
13774                             SendToICS(buf);
13775                         }
13776                     } else {
13777                         boards[0][y][x] = p;
13778                     }
13779                 }
13780             }
13781         }
13782         if (gameMode == EditPosition) {
13783             DrawPosition(FALSE, boards[0]);
13784         }
13785         break;
13786
13787       case WhitePlay:
13788         SetWhiteToPlayEvent();
13789         break;
13790
13791       case BlackPlay:
13792         SetBlackToPlayEvent();
13793         break;
13794
13795       case EmptySquare:
13796         if (gameMode == IcsExamining) {
13797             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13798             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13799             SendToICS(buf);
13800         } else {
13801             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13802                 if(x == BOARD_LEFT-2) {
13803                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13804                     boards[0][y][1] = 0;
13805                 } else
13806                 if(x == BOARD_RGHT+1) {
13807                     if(y >= gameInfo.holdingsSize) break;
13808                     boards[0][y][BOARD_WIDTH-2] = 0;
13809                 } else break;
13810             }
13811             boards[0][y][x] = EmptySquare;
13812             DrawPosition(FALSE, boards[0]);
13813         }
13814         break;
13815
13816       case PromotePiece:
13817         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13818            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13819             selection = (ChessSquare) (PROMOTED piece);
13820         } else if(piece == EmptySquare) selection = WhiteSilver;
13821         else selection = (ChessSquare)((int)piece - 1);
13822         goto defaultlabel;
13823
13824       case DemotePiece:
13825         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13826            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13827             selection = (ChessSquare) (DEMOTED piece);
13828         } else if(piece == EmptySquare) selection = BlackSilver;
13829         else selection = (ChessSquare)((int)piece + 1);
13830         goto defaultlabel;
13831
13832       case WhiteQueen:
13833       case BlackQueen:
13834         if(gameInfo.variant == VariantShatranj ||
13835            gameInfo.variant == VariantXiangqi  ||
13836            gameInfo.variant == VariantCourier  ||
13837            gameInfo.variant == VariantMakruk     )
13838             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13839         goto defaultlabel;
13840
13841       case WhiteKing:
13842       case BlackKing:
13843         if(gameInfo.variant == VariantXiangqi)
13844             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13845         if(gameInfo.variant == VariantKnightmate)
13846             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13847       default:
13848         defaultlabel:
13849         if (gameMode == IcsExamining) {
13850             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13851             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13852                      PieceToChar(selection), AAA + x, ONE + y);
13853             SendToICS(buf);
13854         } else {
13855             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13856                 int n;
13857                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13858                     n = PieceToNumber(selection - BlackPawn);
13859                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13860                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13861                     boards[0][BOARD_HEIGHT-1-n][1]++;
13862                 } else
13863                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13864                     n = PieceToNumber(selection);
13865                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13866                     boards[0][n][BOARD_WIDTH-1] = selection;
13867                     boards[0][n][BOARD_WIDTH-2]++;
13868                 }
13869             } else
13870             boards[0][y][x] = selection;
13871             DrawPosition(TRUE, boards[0]);
13872         }
13873         break;
13874     }
13875 }
13876
13877
13878 void
13879 DropMenuEvent (ChessSquare selection, int x, int y)
13880 {
13881     ChessMove moveType;
13882
13883     switch (gameMode) {
13884       case IcsPlayingWhite:
13885       case MachinePlaysBlack:
13886         if (!WhiteOnMove(currentMove)) {
13887             DisplayMoveError(_("It is Black's turn"));
13888             return;
13889         }
13890         moveType = WhiteDrop;
13891         break;
13892       case IcsPlayingBlack:
13893       case MachinePlaysWhite:
13894         if (WhiteOnMove(currentMove)) {
13895             DisplayMoveError(_("It is White's turn"));
13896             return;
13897         }
13898         moveType = BlackDrop;
13899         break;
13900       case EditGame:
13901         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13902         break;
13903       default:
13904         return;
13905     }
13906
13907     if (moveType == BlackDrop && selection < BlackPawn) {
13908       selection = (ChessSquare) ((int) selection
13909                                  + (int) BlackPawn - (int) WhitePawn);
13910     }
13911     if (boards[currentMove][y][x] != EmptySquare) {
13912         DisplayMoveError(_("That square is occupied"));
13913         return;
13914     }
13915
13916     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13917 }
13918
13919 void
13920 AcceptEvent ()
13921 {
13922     /* Accept a pending offer of any kind from opponent */
13923
13924     if (appData.icsActive) {
13925         SendToICS(ics_prefix);
13926         SendToICS("accept\n");
13927     } else if (cmailMsgLoaded) {
13928         if (currentMove == cmailOldMove &&
13929             commentList[cmailOldMove] != NULL &&
13930             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13931                    "Black offers a draw" : "White offers a draw")) {
13932             TruncateGame();
13933             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13934             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13935         } else {
13936             DisplayError(_("There is no pending offer on this move"), 0);
13937             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13938         }
13939     } else {
13940         /* Not used for offers from chess program */
13941     }
13942 }
13943
13944 void
13945 DeclineEvent ()
13946 {
13947     /* Decline a pending offer of any kind from opponent */
13948
13949     if (appData.icsActive) {
13950         SendToICS(ics_prefix);
13951         SendToICS("decline\n");
13952     } else if (cmailMsgLoaded) {
13953         if (currentMove == cmailOldMove &&
13954             commentList[cmailOldMove] != NULL &&
13955             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13956                    "Black offers a draw" : "White offers a draw")) {
13957 #ifdef NOTDEF
13958             AppendComment(cmailOldMove, "Draw declined", TRUE);
13959             DisplayComment(cmailOldMove - 1, "Draw declined");
13960 #endif /*NOTDEF*/
13961         } else {
13962             DisplayError(_("There is no pending offer on this move"), 0);
13963         }
13964     } else {
13965         /* Not used for offers from chess program */
13966     }
13967 }
13968
13969 void
13970 RematchEvent ()
13971 {
13972     /* Issue ICS rematch command */
13973     if (appData.icsActive) {
13974         SendToICS(ics_prefix);
13975         SendToICS("rematch\n");
13976     }
13977 }
13978
13979 void
13980 CallFlagEvent ()
13981 {
13982     /* Call your opponent's flag (claim a win on time) */
13983     if (appData.icsActive) {
13984         SendToICS(ics_prefix);
13985         SendToICS("flag\n");
13986     } else {
13987         switch (gameMode) {
13988           default:
13989             return;
13990           case MachinePlaysWhite:
13991             if (whiteFlag) {
13992                 if (blackFlag)
13993                   GameEnds(GameIsDrawn, "Both players ran out of time",
13994                            GE_PLAYER);
13995                 else
13996                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13997             } else {
13998                 DisplayError(_("Your opponent is not out of time"), 0);
13999             }
14000             break;
14001           case MachinePlaysBlack:
14002             if (blackFlag) {
14003                 if (whiteFlag)
14004                   GameEnds(GameIsDrawn, "Both players ran out of time",
14005                            GE_PLAYER);
14006                 else
14007                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
14008             } else {
14009                 DisplayError(_("Your opponent is not out of time"), 0);
14010             }
14011             break;
14012         }
14013     }
14014 }
14015
14016 void
14017 ClockClick (int which)
14018 {       // [HGM] code moved to back-end from winboard.c
14019         if(which) { // black clock
14020           if (gameMode == EditPosition || gameMode == IcsExamining) {
14021             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14022             SetBlackToPlayEvent();
14023           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
14024           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
14025           } else if (shiftKey) {
14026             AdjustClock(which, -1);
14027           } else if (gameMode == IcsPlayingWhite ||
14028                      gameMode == MachinePlaysBlack) {
14029             CallFlagEvent();
14030           }
14031         } else { // white clock
14032           if (gameMode == EditPosition || gameMode == IcsExamining) {
14033             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
14034             SetWhiteToPlayEvent();
14035           } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
14036           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
14037           } else if (shiftKey) {
14038             AdjustClock(which, -1);
14039           } else if (gameMode == IcsPlayingBlack ||
14040                    gameMode == MachinePlaysWhite) {
14041             CallFlagEvent();
14042           }
14043         }
14044 }
14045
14046 void
14047 DrawEvent ()
14048 {
14049     /* Offer draw or accept pending draw offer from opponent */
14050
14051     if (appData.icsActive) {
14052         /* Note: tournament rules require draw offers to be
14053            made after you make your move but before you punch
14054            your clock.  Currently ICS doesn't let you do that;
14055            instead, you immediately punch your clock after making
14056            a move, but you can offer a draw at any time. */
14057
14058         SendToICS(ics_prefix);
14059         SendToICS("draw\n");
14060         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
14061     } else if (cmailMsgLoaded) {
14062         if (currentMove == cmailOldMove &&
14063             commentList[cmailOldMove] != NULL &&
14064             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
14065                    "Black offers a draw" : "White offers a draw")) {
14066             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
14067             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
14068         } else if (currentMove == cmailOldMove + 1) {
14069             char *offer = WhiteOnMove(cmailOldMove) ?
14070               "White offers a draw" : "Black offers a draw";
14071             AppendComment(currentMove, offer, TRUE);
14072             DisplayComment(currentMove - 1, offer);
14073             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
14074         } else {
14075             DisplayError(_("You must make your move before offering a draw"), 0);
14076             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
14077         }
14078     } else if (first.offeredDraw) {
14079         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
14080     } else {
14081         if (first.sendDrawOffers) {
14082             SendToProgram("draw\n", &first);
14083             userOfferedDraw = TRUE;
14084         }
14085     }
14086 }
14087
14088 void
14089 AdjournEvent ()
14090 {
14091     /* Offer Adjourn or accept pending Adjourn offer from opponent */
14092
14093     if (appData.icsActive) {
14094         SendToICS(ics_prefix);
14095         SendToICS("adjourn\n");
14096     } else {
14097         /* Currently GNU Chess doesn't offer or accept Adjourns */
14098     }
14099 }
14100
14101
14102 void
14103 AbortEvent ()
14104 {
14105     /* Offer Abort or accept pending Abort offer from opponent */
14106
14107     if (appData.icsActive) {
14108         SendToICS(ics_prefix);
14109         SendToICS("abort\n");
14110     } else {
14111         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
14112     }
14113 }
14114
14115 void
14116 ResignEvent ()
14117 {
14118     /* Resign.  You can do this even if it's not your turn. */
14119
14120     if (appData.icsActive) {
14121         SendToICS(ics_prefix);
14122         SendToICS("resign\n");
14123     } else {
14124         switch (gameMode) {
14125           case MachinePlaysWhite:
14126             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14127             break;
14128           case MachinePlaysBlack:
14129             GameEnds(BlackWins, "White resigns", GE_PLAYER);
14130             break;
14131           case EditGame:
14132             if (cmailMsgLoaded) {
14133                 TruncateGame();
14134                 if (WhiteOnMove(cmailOldMove)) {
14135                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
14136                 } else {
14137                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
14138                 }
14139                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
14140             }
14141             break;
14142           default:
14143             break;
14144         }
14145     }
14146 }
14147
14148
14149 void
14150 StopObservingEvent ()
14151 {
14152     /* Stop observing current games */
14153     SendToICS(ics_prefix);
14154     SendToICS("unobserve\n");
14155 }
14156
14157 void
14158 StopExaminingEvent ()
14159 {
14160     /* Stop observing current game */
14161     SendToICS(ics_prefix);
14162     SendToICS("unexamine\n");
14163 }
14164
14165 void
14166 ForwardInner (int target)
14167 {
14168     int limit;
14169
14170     if (appData.debugMode)
14171         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
14172                 target, currentMove, forwardMostMove);
14173
14174     if (gameMode == EditPosition)
14175       return;
14176
14177     seekGraphUp = FALSE;
14178     MarkTargetSquares(1);
14179
14180     if (gameMode == PlayFromGameFile && !pausing)
14181       PauseEvent();
14182
14183     if (gameMode == IcsExamining && pausing)
14184       limit = pauseExamForwardMostMove;
14185     else
14186       limit = forwardMostMove;
14187
14188     if (target > limit) target = limit;
14189
14190     if (target > 0 && moveList[target - 1][0]) {
14191         int fromX, fromY, toX, toY;
14192         toX = moveList[target - 1][2] - AAA;
14193         toY = moveList[target - 1][3] - ONE;
14194         if (moveList[target - 1][1] == '@') {
14195             if (appData.highlightLastMove) {
14196                 SetHighlights(-1, -1, toX, toY);
14197             }
14198         } else {
14199             fromX = moveList[target - 1][0] - AAA;
14200             fromY = moveList[target - 1][1] - ONE;
14201             if (target == currentMove + 1) {
14202                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
14203             }
14204             if (appData.highlightLastMove) {
14205                 SetHighlights(fromX, fromY, toX, toY);
14206             }
14207         }
14208     }
14209     if (gameMode == EditGame || gameMode == AnalyzeMode ||
14210         gameMode == Training || gameMode == PlayFromGameFile ||
14211         gameMode == AnalyzeFile) {
14212         while (currentMove < target) {
14213             SendMoveToProgram(currentMove++, &first);
14214         }
14215     } else {
14216         currentMove = target;
14217     }
14218
14219     if (gameMode == EditGame || gameMode == EndOfGame) {
14220         whiteTimeRemaining = timeRemaining[0][currentMove];
14221         blackTimeRemaining = timeRemaining[1][currentMove];
14222     }
14223     DisplayBothClocks();
14224     DisplayMove(currentMove - 1);
14225     DrawPosition(FALSE, boards[currentMove]);
14226     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14227     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
14228         DisplayComment(currentMove - 1, commentList[currentMove]);
14229     }
14230 }
14231
14232
14233 void
14234 ForwardEvent ()
14235 {
14236     if (gameMode == IcsExamining && !pausing) {
14237         SendToICS(ics_prefix);
14238         SendToICS("forward\n");
14239     } else {
14240         ForwardInner(currentMove + 1);
14241     }
14242 }
14243
14244 void
14245 ToEndEvent ()
14246 {
14247     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14248         /* to optimze, we temporarily turn off analysis mode while we feed
14249          * the remaining moves to the engine. Otherwise we get analysis output
14250          * after each move.
14251          */
14252         if (first.analysisSupport) {
14253           SendToProgram("exit\nforce\n", &first);
14254           first.analyzing = FALSE;
14255         }
14256     }
14257
14258     if (gameMode == IcsExamining && !pausing) {
14259         SendToICS(ics_prefix);
14260         SendToICS("forward 999999\n");
14261     } else {
14262         ForwardInner(forwardMostMove);
14263     }
14264
14265     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14266         /* we have fed all the moves, so reactivate analysis mode */
14267         SendToProgram("analyze\n", &first);
14268         first.analyzing = TRUE;
14269         /*first.maybeThinking = TRUE;*/
14270         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14271     }
14272 }
14273
14274 void
14275 BackwardInner (int target)
14276 {
14277     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
14278
14279     if (appData.debugMode)
14280         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
14281                 target, currentMove, forwardMostMove);
14282
14283     if (gameMode == EditPosition) return;
14284     seekGraphUp = FALSE;
14285     MarkTargetSquares(1);
14286     if (currentMove <= backwardMostMove) {
14287         ClearHighlights();
14288         DrawPosition(full_redraw, boards[currentMove]);
14289         return;
14290     }
14291     if (gameMode == PlayFromGameFile && !pausing)
14292       PauseEvent();
14293
14294     if (moveList[target][0]) {
14295         int fromX, fromY, toX, toY;
14296         toX = moveList[target][2] - AAA;
14297         toY = moveList[target][3] - ONE;
14298         if (moveList[target][1] == '@') {
14299             if (appData.highlightLastMove) {
14300                 SetHighlights(-1, -1, toX, toY);
14301             }
14302         } else {
14303             fromX = moveList[target][0] - AAA;
14304             fromY = moveList[target][1] - ONE;
14305             if (target == currentMove - 1) {
14306                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
14307             }
14308             if (appData.highlightLastMove) {
14309                 SetHighlights(fromX, fromY, toX, toY);
14310             }
14311         }
14312     }
14313     if (gameMode == EditGame || gameMode==AnalyzeMode ||
14314         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
14315         while (currentMove > target) {
14316             if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
14317                 // null move cannot be undone. Reload program with move history before it.
14318                 int i;
14319                 for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move
14320                     if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break;
14321                 }
14322                 SendBoard(&first, i); 
14323                 for(currentMove=i; currentMove<target; currentMove++) SendMoveToProgram(currentMove, &first);
14324                 break;
14325             }
14326             SendToProgram("undo\n", &first);
14327             currentMove--;
14328         }
14329     } else {
14330         currentMove = target;
14331     }
14332
14333     if (gameMode == EditGame || gameMode == EndOfGame) {
14334         whiteTimeRemaining = timeRemaining[0][currentMove];
14335         blackTimeRemaining = timeRemaining[1][currentMove];
14336     }
14337     DisplayBothClocks();
14338     DisplayMove(currentMove - 1);
14339     DrawPosition(full_redraw, boards[currentMove]);
14340     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
14341     // [HGM] PV info: routine tests if comment empty
14342     DisplayComment(currentMove - 1, commentList[currentMove]);
14343 }
14344
14345 void
14346 BackwardEvent ()
14347 {
14348     if (gameMode == IcsExamining && !pausing) {
14349         SendToICS(ics_prefix);
14350         SendToICS("backward\n");
14351     } else {
14352         BackwardInner(currentMove - 1);
14353     }
14354 }
14355
14356 void
14357 ToStartEvent ()
14358 {
14359     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14360         /* to optimize, we temporarily turn off analysis mode while we undo
14361          * all the moves. Otherwise we get analysis output after each undo.
14362          */
14363         if (first.analysisSupport) {
14364           SendToProgram("exit\nforce\n", &first);
14365           first.analyzing = FALSE;
14366         }
14367     }
14368
14369     if (gameMode == IcsExamining && !pausing) {
14370         SendToICS(ics_prefix);
14371         SendToICS("backward 999999\n");
14372     } else {
14373         BackwardInner(backwardMostMove);
14374     }
14375
14376     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14377         /* we have fed all the moves, so reactivate analysis mode */
14378         SendToProgram("analyze\n", &first);
14379         first.analyzing = TRUE;
14380         /*first.maybeThinking = TRUE;*/
14381         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
14382     }
14383 }
14384
14385 void
14386 ToNrEvent (int to)
14387 {
14388   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
14389   if (to >= forwardMostMove) to = forwardMostMove;
14390   if (to <= backwardMostMove) to = backwardMostMove;
14391   if (to < currentMove) {
14392     BackwardInner(to);
14393   } else {
14394     ForwardInner(to);
14395   }
14396 }
14397
14398 void
14399 RevertEvent (Boolean annotate)
14400 {
14401     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
14402         return;
14403     }
14404     if (gameMode != IcsExamining) {
14405         DisplayError(_("You are not examining a game"), 0);
14406         return;
14407     }
14408     if (pausing) {
14409         DisplayError(_("You can't revert while pausing"), 0);
14410         return;
14411     }
14412     SendToICS(ics_prefix);
14413     SendToICS("revert\n");
14414 }
14415
14416 void
14417 RetractMoveEvent ()
14418 {
14419     switch (gameMode) {
14420       case MachinePlaysWhite:
14421       case MachinePlaysBlack:
14422         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
14423             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
14424             return;
14425         }
14426         if (forwardMostMove < 2) return;
14427         currentMove = forwardMostMove = forwardMostMove - 2;
14428         whiteTimeRemaining = timeRemaining[0][currentMove];
14429         blackTimeRemaining = timeRemaining[1][currentMove];
14430         DisplayBothClocks();
14431         DisplayMove(currentMove - 1);
14432         ClearHighlights();/*!! could figure this out*/
14433         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
14434         SendToProgram("remove\n", &first);
14435         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
14436         break;
14437
14438       case BeginningOfGame:
14439       default:
14440         break;
14441
14442       case IcsPlayingWhite:
14443       case IcsPlayingBlack:
14444         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
14445             SendToICS(ics_prefix);
14446             SendToICS("takeback 2\n");
14447         } else {
14448             SendToICS(ics_prefix);
14449             SendToICS("takeback 1\n");
14450         }
14451         break;
14452     }
14453 }
14454
14455 void
14456 MoveNowEvent ()
14457 {
14458     ChessProgramState *cps;
14459
14460     switch (gameMode) {
14461       case MachinePlaysWhite:
14462         if (!WhiteOnMove(forwardMostMove)) {
14463             DisplayError(_("It is your turn"), 0);
14464             return;
14465         }
14466         cps = &first;
14467         break;
14468       case MachinePlaysBlack:
14469         if (WhiteOnMove(forwardMostMove)) {
14470             DisplayError(_("It is your turn"), 0);
14471             return;
14472         }
14473         cps = &first;
14474         break;
14475       case TwoMachinesPlay:
14476         if (WhiteOnMove(forwardMostMove) ==
14477             (first.twoMachinesColor[0] == 'w')) {
14478             cps = &first;
14479         } else {
14480             cps = &second;
14481         }
14482         break;
14483       case BeginningOfGame:
14484       default:
14485         return;
14486     }
14487     SendToProgram("?\n", cps);
14488 }
14489
14490 void
14491 TruncateGameEvent ()
14492 {
14493     EditGameEvent();
14494     if (gameMode != EditGame) return;
14495     TruncateGame();
14496 }
14497
14498 void
14499 TruncateGame ()
14500 {
14501     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14502     if (forwardMostMove > currentMove) {
14503         if (gameInfo.resultDetails != NULL) {
14504             free(gameInfo.resultDetails);
14505             gameInfo.resultDetails = NULL;
14506             gameInfo.result = GameUnfinished;
14507         }
14508         forwardMostMove = currentMove;
14509         HistorySet(parseList, backwardMostMove, forwardMostMove,
14510                    currentMove-1);
14511     }
14512 }
14513
14514 void
14515 HintEvent ()
14516 {
14517     if (appData.noChessProgram) return;
14518     switch (gameMode) {
14519       case MachinePlaysWhite:
14520         if (WhiteOnMove(forwardMostMove)) {
14521             DisplayError(_("Wait until your turn"), 0);
14522             return;
14523         }
14524         break;
14525       case BeginningOfGame:
14526       case MachinePlaysBlack:
14527         if (!WhiteOnMove(forwardMostMove)) {
14528             DisplayError(_("Wait until your turn"), 0);
14529             return;
14530         }
14531         break;
14532       default:
14533         DisplayError(_("No hint available"), 0);
14534         return;
14535     }
14536     SendToProgram("hint\n", &first);
14537     hintRequested = TRUE;
14538 }
14539
14540 void
14541 BookEvent ()
14542 {
14543     if (appData.noChessProgram) return;
14544     switch (gameMode) {
14545       case MachinePlaysWhite:
14546         if (WhiteOnMove(forwardMostMove)) {
14547             DisplayError(_("Wait until your turn"), 0);
14548             return;
14549         }
14550         break;
14551       case BeginningOfGame:
14552       case MachinePlaysBlack:
14553         if (!WhiteOnMove(forwardMostMove)) {
14554             DisplayError(_("Wait until your turn"), 0);
14555             return;
14556         }
14557         break;
14558       case EditPosition:
14559         EditPositionDone(TRUE);
14560         break;
14561       case TwoMachinesPlay:
14562         return;
14563       default:
14564         break;
14565     }
14566     SendToProgram("bk\n", &first);
14567     bookOutput[0] = NULLCHAR;
14568     bookRequested = TRUE;
14569 }
14570
14571 void
14572 AboutGameEvent ()
14573 {
14574     char *tags = PGNTags(&gameInfo);
14575     TagsPopUp(tags, CmailMsg());
14576     free(tags);
14577 }
14578
14579 /* end button procedures */
14580
14581 void
14582 PrintPosition (FILE *fp, int move)
14583 {
14584     int i, j;
14585
14586     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14587         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14588             char c = PieceToChar(boards[move][i][j]);
14589             fputc(c == 'x' ? '.' : c, fp);
14590             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14591         }
14592     }
14593     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14594       fprintf(fp, "white to play\n");
14595     else
14596       fprintf(fp, "black to play\n");
14597 }
14598
14599 void
14600 PrintOpponents (FILE *fp)
14601 {
14602     if (gameInfo.white != NULL) {
14603         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14604     } else {
14605         fprintf(fp, "\n");
14606     }
14607 }
14608
14609 /* Find last component of program's own name, using some heuristics */
14610 void
14611 TidyProgramName (char *prog, char *host, char buf[MSG_SIZ])
14612 {
14613     char *p, *q;
14614     int local = (strcmp(host, "localhost") == 0);
14615     while (!local && (p = strchr(prog, ';')) != NULL) {
14616         p++;
14617         while (*p == ' ') p++;
14618         prog = p;
14619     }
14620     if (*prog == '"' || *prog == '\'') {
14621         q = strchr(prog + 1, *prog);
14622     } else {
14623         q = strchr(prog, ' ');
14624     }
14625     if (q == NULL) q = prog + strlen(prog);
14626     p = q;
14627     while (p >= prog && *p != '/' && *p != '\\') p--;
14628     p++;
14629     if(p == prog && *p == '"') p++;
14630     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14631     memcpy(buf, p, q - p);
14632     buf[q - p] = NULLCHAR;
14633     if (!local) {
14634         strcat(buf, "@");
14635         strcat(buf, host);
14636     }
14637 }
14638
14639 char *
14640 TimeControlTagValue ()
14641 {
14642     char buf[MSG_SIZ];
14643     if (!appData.clockMode) {
14644       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14645     } else if (movesPerSession > 0) {
14646       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14647     } else if (timeIncrement == 0) {
14648       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14649     } else {
14650       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14651     }
14652     return StrSave(buf);
14653 }
14654
14655 void
14656 SetGameInfo ()
14657 {
14658     /* This routine is used only for certain modes */
14659     VariantClass v = gameInfo.variant;
14660     ChessMove r = GameUnfinished;
14661     char *p = NULL;
14662
14663     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14664         r = gameInfo.result;
14665         p = gameInfo.resultDetails;
14666         gameInfo.resultDetails = NULL;
14667     }
14668     ClearGameInfo(&gameInfo);
14669     gameInfo.variant = v;
14670
14671     switch (gameMode) {
14672       case MachinePlaysWhite:
14673         gameInfo.event = StrSave( appData.pgnEventHeader );
14674         gameInfo.site = StrSave(HostName());
14675         gameInfo.date = PGNDate();
14676         gameInfo.round = StrSave("-");
14677         gameInfo.white = StrSave(first.tidy);
14678         gameInfo.black = StrSave(UserName());
14679         gameInfo.timeControl = TimeControlTagValue();
14680         break;
14681
14682       case MachinePlaysBlack:
14683         gameInfo.event = StrSave( appData.pgnEventHeader );
14684         gameInfo.site = StrSave(HostName());
14685         gameInfo.date = PGNDate();
14686         gameInfo.round = StrSave("-");
14687         gameInfo.white = StrSave(UserName());
14688         gameInfo.black = StrSave(first.tidy);
14689         gameInfo.timeControl = TimeControlTagValue();
14690         break;
14691
14692       case TwoMachinesPlay:
14693         gameInfo.event = StrSave( appData.pgnEventHeader );
14694         gameInfo.site = StrSave(HostName());
14695         gameInfo.date = PGNDate();
14696         if (roundNr > 0) {
14697             char buf[MSG_SIZ];
14698             snprintf(buf, MSG_SIZ, "%d", roundNr);
14699             gameInfo.round = StrSave(buf);
14700         } else {
14701             gameInfo.round = StrSave("-");
14702         }
14703         if (first.twoMachinesColor[0] == 'w') {
14704             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14705             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14706         } else {
14707             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14708             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14709         }
14710         gameInfo.timeControl = TimeControlTagValue();
14711         break;
14712
14713       case EditGame:
14714         gameInfo.event = StrSave("Edited game");
14715         gameInfo.site = StrSave(HostName());
14716         gameInfo.date = PGNDate();
14717         gameInfo.round = StrSave("-");
14718         gameInfo.white = StrSave("-");
14719         gameInfo.black = StrSave("-");
14720         gameInfo.result = r;
14721         gameInfo.resultDetails = p;
14722         break;
14723
14724       case EditPosition:
14725         gameInfo.event = StrSave("Edited position");
14726         gameInfo.site = StrSave(HostName());
14727         gameInfo.date = PGNDate();
14728         gameInfo.round = StrSave("-");
14729         gameInfo.white = StrSave("-");
14730         gameInfo.black = StrSave("-");
14731         break;
14732
14733       case IcsPlayingWhite:
14734       case IcsPlayingBlack:
14735       case IcsObserving:
14736       case IcsExamining:
14737         break;
14738
14739       case PlayFromGameFile:
14740         gameInfo.event = StrSave("Game from non-PGN file");
14741         gameInfo.site = StrSave(HostName());
14742         gameInfo.date = PGNDate();
14743         gameInfo.round = StrSave("-");
14744         gameInfo.white = StrSave("?");
14745         gameInfo.black = StrSave("?");
14746         break;
14747
14748       default:
14749         break;
14750     }
14751 }
14752
14753 void
14754 ReplaceComment (int index, char *text)
14755 {
14756     int len;
14757     char *p;
14758     float score;
14759
14760     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14761        pvInfoList[index-1].depth == len &&
14762        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14763        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14764     while (*text == '\n') text++;
14765     len = strlen(text);
14766     while (len > 0 && text[len - 1] == '\n') len--;
14767
14768     if (commentList[index] != NULL)
14769       free(commentList[index]);
14770
14771     if (len == 0) {
14772         commentList[index] = NULL;
14773         return;
14774     }
14775   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14776       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14777       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14778     commentList[index] = (char *) malloc(len + 2);
14779     strncpy(commentList[index], text, len);
14780     commentList[index][len] = '\n';
14781     commentList[index][len + 1] = NULLCHAR;
14782   } else {
14783     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14784     char *p;
14785     commentList[index] = (char *) malloc(len + 7);
14786     safeStrCpy(commentList[index], "{\n", 3);
14787     safeStrCpy(commentList[index]+2, text, len+1);
14788     commentList[index][len+2] = NULLCHAR;
14789     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14790     strcat(commentList[index], "\n}\n");
14791   }
14792 }
14793
14794 void
14795 CrushCRs (char *text)
14796 {
14797   char *p = text;
14798   char *q = text;
14799   char ch;
14800
14801   do {
14802     ch = *p++;
14803     if (ch == '\r') continue;
14804     *q++ = ch;
14805   } while (ch != '\0');
14806 }
14807
14808 void
14809 AppendComment (int index, char *text, Boolean addBraces)
14810 /* addBraces  tells if we should add {} */
14811 {
14812     int oldlen, len;
14813     char *old;
14814
14815 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14816     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14817
14818     CrushCRs(text);
14819     while (*text == '\n') text++;
14820     len = strlen(text);
14821     while (len > 0 && text[len - 1] == '\n') len--;
14822     text[len] = NULLCHAR;
14823
14824     if (len == 0) return;
14825
14826     if (commentList[index] != NULL) {
14827       Boolean addClosingBrace = addBraces;
14828         old = commentList[index];
14829         oldlen = strlen(old);
14830         while(commentList[index][oldlen-1] ==  '\n')
14831           commentList[index][--oldlen] = NULLCHAR;
14832         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14833         safeStrCpy(commentList[index], old, oldlen + len + 6);
14834         free(old);
14835         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14836         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14837           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14838           while (*text == '\n') { text++; len--; }
14839           commentList[index][--oldlen] = NULLCHAR;
14840       }
14841         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14842         else          strcat(commentList[index], "\n");
14843         strcat(commentList[index], text);
14844         if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n");
14845         else          strcat(commentList[index], "\n");
14846     } else {
14847         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14848         if(addBraces)
14849           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14850         else commentList[index][0] = NULLCHAR;
14851         strcat(commentList[index], text);
14852         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14853         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14854     }
14855 }
14856
14857 static char *
14858 FindStr (char * text, char * sub_text)
14859 {
14860     char * result = strstr( text, sub_text );
14861
14862     if( result != NULL ) {
14863         result += strlen( sub_text );
14864     }
14865
14866     return result;
14867 }
14868
14869 /* [AS] Try to extract PV info from PGN comment */
14870 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14871 char *
14872 GetInfoFromComment (int index, char * text)
14873 {
14874     char * sep = text, *p;
14875
14876     if( text != NULL && index > 0 ) {
14877         int score = 0;
14878         int depth = 0;
14879         int time = -1, sec = 0, deci;
14880         char * s_eval = FindStr( text, "[%eval " );
14881         char * s_emt = FindStr( text, "[%emt " );
14882
14883         if( s_eval != NULL || s_emt != NULL ) {
14884             /* New style */
14885             char delim;
14886
14887             if( s_eval != NULL ) {
14888                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14889                     return text;
14890                 }
14891
14892                 if( delim != ']' ) {
14893                     return text;
14894                 }
14895             }
14896
14897             if( s_emt != NULL ) {
14898             }
14899                 return text;
14900         }
14901         else {
14902             /* We expect something like: [+|-]nnn.nn/dd */
14903             int score_lo = 0;
14904
14905             if(*text != '{') return text; // [HGM] braces: must be normal comment
14906
14907             sep = strchr( text, '/' );
14908             if( sep == NULL || sep < (text+4) ) {
14909                 return text;
14910             }
14911
14912             p = text;
14913             if(p[1] == '(') { // comment starts with PV
14914                p = strchr(p, ')'); // locate end of PV
14915                if(p == NULL || sep < p+5) return text;
14916                // at this point we have something like "{(.*) +0.23/6 ..."
14917                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14918                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14919                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14920             }
14921             time = -1; sec = -1; deci = -1;
14922             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14923                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14924                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14925                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14926                 return text;
14927             }
14928
14929             if( score_lo < 0 || score_lo >= 100 ) {
14930                 return text;
14931             }
14932
14933             if(sec >= 0) time = 600*time + 10*sec; else
14934             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14935
14936             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14937
14938             /* [HGM] PV time: now locate end of PV info */
14939             while( *++sep >= '0' && *sep <= '9'); // strip depth
14940             if(time >= 0)
14941             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14942             if(sec >= 0)
14943             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14944             if(deci >= 0)
14945             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14946             while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
14947         }
14948
14949         if( depth <= 0 ) {
14950             return text;
14951         }
14952
14953         if( time < 0 ) {
14954             time = -1;
14955         }
14956
14957         pvInfoList[index-1].depth = depth;
14958         pvInfoList[index-1].score = score;
14959         pvInfoList[index-1].time  = 10*time; // centi-sec
14960         if(*sep == '}') *sep = 0; else *--sep = '{';
14961         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14962     }
14963     return sep;
14964 }
14965
14966 void
14967 SendToProgram (char *message, ChessProgramState *cps)
14968 {
14969     int count, outCount, error;
14970     char buf[MSG_SIZ];
14971
14972     if (cps->pr == NoProc) return;
14973     Attention(cps);
14974
14975     if (appData.debugMode) {
14976         TimeMark now;
14977         GetTimeMark(&now);
14978         fprintf(debugFP, "%ld >%-6s: %s",
14979                 SubtractTimeMarks(&now, &programStartTime),
14980                 cps->which, message);
14981     }
14982
14983     count = strlen(message);
14984     outCount = OutputToProcess(cps->pr, message, count, &error);
14985     if (outCount < count && !exiting
14986                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14987       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14988       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14989         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14990             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14991                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14992                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14993                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14994             } else {
14995                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14996                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14997                 gameInfo.result = res;
14998             }
14999             gameInfo.resultDetails = StrSave(buf);
15000         }
15001         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15002         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15003     }
15004 }
15005
15006 void
15007 ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
15008 {
15009     char *end_str;
15010     char buf[MSG_SIZ];
15011     ChessProgramState *cps = (ChessProgramState *)closure;
15012
15013     if (isr != cps->isr) return; /* Killed intentionally */
15014     if (count <= 0) {
15015         if (count == 0) {
15016             RemoveInputSource(cps->isr);
15017             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
15018             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
15019                     _(cps->which), cps->program);
15020         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
15021                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
15022                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
15023                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
15024                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
15025                 } else {
15026                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
15027                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
15028                     gameInfo.result = res;
15029                 }
15030                 gameInfo.resultDetails = StrSave(buf);
15031             }
15032             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
15033             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
15034         } else {
15035             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
15036                     _(cps->which), cps->program);
15037             RemoveInputSource(cps->isr);
15038
15039             /* [AS] Program is misbehaving badly... kill it */
15040             if( count == -2 ) {
15041                 DestroyChildProcess( cps->pr, 9 );
15042                 cps->pr = NoProc;
15043             }
15044
15045             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
15046         }
15047         return;
15048     }
15049
15050     if ((end_str = strchr(message, '\r')) != NULL)
15051       *end_str = NULLCHAR;
15052     if ((end_str = strchr(message, '\n')) != NULL)
15053       *end_str = NULLCHAR;
15054
15055     if (appData.debugMode) {
15056         TimeMark now; int print = 1;
15057         char *quote = ""; char c; int i;
15058
15059         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
15060                 char start = message[0];
15061                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
15062                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
15063                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
15064                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
15065                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
15066                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
15067                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
15068                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
15069                    sscanf(message, "hint: %c", &c)!=1 && 
15070                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
15071                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
15072                     print = (appData.engineComments >= 2);
15073                 }
15074                 message[0] = start; // restore original message
15075         }
15076         if(print) {
15077                 GetTimeMark(&now);
15078                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
15079                         SubtractTimeMarks(&now, &programStartTime), cps->which,
15080                         quote,
15081                         message);
15082         }
15083     }
15084
15085     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
15086     if (appData.icsEngineAnalyze) {
15087         if (strstr(message, "whisper") != NULL ||
15088              strstr(message, "kibitz") != NULL ||
15089             strstr(message, "tellics") != NULL) return;
15090     }
15091
15092     HandleMachineMove(message, cps);
15093 }
15094
15095
15096 void
15097 SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st)
15098 {
15099     char buf[MSG_SIZ];
15100     int seconds;
15101
15102     if( timeControl_2 > 0 ) {
15103         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
15104             tc = timeControl_2;
15105         }
15106     }
15107     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
15108     inc /= cps->timeOdds;
15109     st  /= cps->timeOdds;
15110
15111     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
15112
15113     if (st > 0) {
15114       /* Set exact time per move, normally using st command */
15115       if (cps->stKludge) {
15116         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
15117         seconds = st % 60;
15118         if (seconds == 0) {
15119           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
15120         } else {
15121           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
15122         }
15123       } else {
15124         snprintf(buf, MSG_SIZ, "st %d\n", st);
15125       }
15126     } else {
15127       /* Set conventional or incremental time control, using level command */
15128       if (seconds == 0) {
15129         /* Note old gnuchess bug -- minutes:seconds used to not work.
15130            Fixed in later versions, but still avoid :seconds
15131            when seconds is 0. */
15132         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
15133       } else {
15134         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
15135                  seconds, inc/1000.);
15136       }
15137     }
15138     SendToProgram(buf, cps);
15139
15140     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
15141     /* Orthogonally, limit search to given depth */
15142     if (sd > 0) {
15143       if (cps->sdKludge) {
15144         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
15145       } else {
15146         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
15147       }
15148       SendToProgram(buf, cps);
15149     }
15150
15151     if(cps->nps >= 0) { /* [HGM] nps */
15152         if(cps->supportsNPS == FALSE)
15153           cps->nps = -1; // don't use if engine explicitly says not supported!
15154         else {
15155           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
15156           SendToProgram(buf, cps);
15157         }
15158     }
15159 }
15160
15161 ChessProgramState *
15162 WhitePlayer ()
15163 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
15164 {
15165     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
15166        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
15167         return &second;
15168     return &first;
15169 }
15170
15171 void
15172 SendTimeRemaining (ChessProgramState *cps, int machineWhite)
15173 {
15174     char message[MSG_SIZ];
15175     long time, otime;
15176
15177     /* Note: this routine must be called when the clocks are stopped
15178        or when they have *just* been set or switched; otherwise
15179        it will be off by the time since the current tick started.
15180     */
15181     if (machineWhite) {
15182         time = whiteTimeRemaining / 10;
15183         otime = blackTimeRemaining / 10;
15184     } else {
15185         time = blackTimeRemaining / 10;
15186         otime = whiteTimeRemaining / 10;
15187     }
15188     /* [HGM] translate opponent's time by time-odds factor */
15189     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
15190     if (appData.debugMode) {
15191         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
15192     }
15193
15194     if (time <= 0) time = 1;
15195     if (otime <= 0) otime = 1;
15196
15197     snprintf(message, MSG_SIZ, "time %ld\n", time);
15198     SendToProgram(message, cps);
15199
15200     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
15201     SendToProgram(message, cps);
15202 }
15203
15204 int
15205 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15206 {
15207   char buf[MSG_SIZ];
15208   int len = strlen(name);
15209   int val;
15210
15211   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15212     (*p) += len + 1;
15213     sscanf(*p, "%d", &val);
15214     *loc = (val != 0);
15215     while (**p && **p != ' ')
15216       (*p)++;
15217     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15218     SendToProgram(buf, cps);
15219     return TRUE;
15220   }
15221   return FALSE;
15222 }
15223
15224 int
15225 IntFeature (char **p, char *name, int *loc, ChessProgramState *cps)
15226 {
15227   char buf[MSG_SIZ];
15228   int len = strlen(name);
15229   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
15230     (*p) += len + 1;
15231     sscanf(*p, "%d", loc);
15232     while (**p && **p != ' ') (*p)++;
15233     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15234     SendToProgram(buf, cps);
15235     return TRUE;
15236   }
15237   return FALSE;
15238 }
15239
15240 int
15241 StringFeature (char **p, char *name, char loc[], ChessProgramState *cps)
15242 {
15243   char buf[MSG_SIZ];
15244   int len = strlen(name);
15245   if (strncmp((*p), name, len) == 0
15246       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
15247     (*p) += len + 2;
15248     sscanf(*p, "%[^\"]", loc);
15249     while (**p && **p != '\"') (*p)++;
15250     if (**p == '\"') (*p)++;
15251     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
15252     SendToProgram(buf, cps);
15253     return TRUE;
15254   }
15255   return FALSE;
15256 }
15257
15258 int
15259 ParseOption (Option *opt, ChessProgramState *cps)
15260 // [HGM] options: process the string that defines an engine option, and determine
15261 // name, type, default value, and allowed value range
15262 {
15263         char *p, *q, buf[MSG_SIZ];
15264         int n, min = (-1)<<31, max = 1<<31, def;
15265
15266         if(p = strstr(opt->name, " -spin ")) {
15267             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15268             if(max < min) max = min; // enforce consistency
15269             if(def < min) def = min;
15270             if(def > max) def = max;
15271             opt->value = def;
15272             opt->min = min;
15273             opt->max = max;
15274             opt->type = Spin;
15275         } else if((p = strstr(opt->name, " -slider "))) {
15276             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
15277             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
15278             if(max < min) max = min; // enforce consistency
15279             if(def < min) def = min;
15280             if(def > max) def = max;
15281             opt->value = def;
15282             opt->min = min;
15283             opt->max = max;
15284             opt->type = Spin; // Slider;
15285         } else if((p = strstr(opt->name, " -string "))) {
15286             opt->textValue = p+9;
15287             opt->type = TextBox;
15288         } else if((p = strstr(opt->name, " -file "))) {
15289             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15290             opt->textValue = p+7;
15291             opt->type = FileName; // FileName;
15292         } else if((p = strstr(opt->name, " -path "))) {
15293             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
15294             opt->textValue = p+7;
15295             opt->type = PathName; // PathName;
15296         } else if(p = strstr(opt->name, " -check ")) {
15297             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
15298             opt->value = (def != 0);
15299             opt->type = CheckBox;
15300         } else if(p = strstr(opt->name, " -combo ")) {
15301             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
15302             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
15303             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
15304             opt->value = n = 0;
15305             while(q = StrStr(q, " /// ")) {
15306                 n++; *q = 0;    // count choices, and null-terminate each of them
15307                 q += 5;
15308                 if(*q == '*') { // remember default, which is marked with * prefix
15309                     q++;
15310                     opt->value = n;
15311                 }
15312                 cps->comboList[cps->comboCnt++] = q;
15313             }
15314             cps->comboList[cps->comboCnt++] = NULL;
15315             opt->max = n + 1;
15316             opt->type = ComboBox;
15317         } else if(p = strstr(opt->name, " -button")) {
15318             opt->type = Button;
15319         } else if(p = strstr(opt->name, " -save")) {
15320             opt->type = SaveButton;
15321         } else return FALSE;
15322         *p = 0; // terminate option name
15323         // now look if the command-line options define a setting for this engine option.
15324         if(cps->optionSettings && cps->optionSettings[0])
15325             p = strstr(cps->optionSettings, opt->name); else p = NULL;
15326         if(p && (p == cps->optionSettings || p[-1] == ',')) {
15327           snprintf(buf, MSG_SIZ, "option %s", p);
15328                 if(p = strstr(buf, ",")) *p = 0;
15329                 if(q = strchr(buf, '=')) switch(opt->type) {
15330                     case ComboBox:
15331                         for(n=0; n<opt->max; n++)
15332                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
15333                         break;
15334                     case TextBox:
15335                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
15336                         break;
15337                     case Spin:
15338                     case CheckBox:
15339                         opt->value = atoi(q+1);
15340                     default:
15341                         break;
15342                 }
15343                 strcat(buf, "\n");
15344                 SendToProgram(buf, cps);
15345         }
15346         return TRUE;
15347 }
15348
15349 void
15350 FeatureDone (ChessProgramState *cps, int val)
15351 {
15352   DelayedEventCallback cb = GetDelayedEvent();
15353   if ((cb == InitBackEnd3 && cps == &first) ||
15354       (cb == SettingsMenuIfReady && cps == &second) ||
15355       (cb == LoadEngine) ||
15356       (cb == TwoMachinesEventIfReady)) {
15357     CancelDelayedEvent();
15358     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
15359   }
15360   cps->initDone = val;
15361 }
15362
15363 /* Parse feature command from engine */
15364 void
15365 ParseFeatures (char *args, ChessProgramState *cps)
15366 {
15367   char *p = args;
15368   char *q;
15369   int val;
15370   char buf[MSG_SIZ];
15371
15372   for (;;) {
15373     while (*p == ' ') p++;
15374     if (*p == NULLCHAR) return;
15375
15376     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
15377     if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue;
15378     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
15379     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
15380     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
15381     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
15382     if (BoolFeature(&p, "reuse", &val, cps)) {
15383       /* Engine can disable reuse, but can't enable it if user said no */
15384       if (!val) cps->reuse = FALSE;
15385       continue;
15386     }
15387     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
15388     if (StringFeature(&p, "myname", cps->tidy, cps)) {
15389       if (gameMode == TwoMachinesPlay) {
15390         DisplayTwoMachinesTitle();
15391       } else {
15392         DisplayTitle("");
15393       }
15394       continue;
15395     }
15396     if (StringFeature(&p, "variants", cps->variants, cps)) continue;
15397     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
15398     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
15399     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
15400     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
15401     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
15402     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
15403     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
15404     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
15405     if (IntFeature(&p, "done", &val, cps)) {
15406       FeatureDone(cps, val);
15407       continue;
15408     }
15409     /* Added by Tord: */
15410     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
15411     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
15412     /* End of additions by Tord */
15413
15414     /* [HGM] added features: */
15415     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
15416     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
15417     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
15418     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
15419     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
15420     if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue;
15421     if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) {
15422         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
15423           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
15424             SendToProgram(buf, cps);
15425             continue;
15426         }
15427         if(cps->nrOptions >= MAX_OPTIONS) {
15428             cps->nrOptions--;
15429             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
15430             DisplayError(buf, 0);
15431         }
15432         continue;
15433     }
15434     /* End of additions by HGM */
15435
15436     /* unknown feature: complain and skip */
15437     q = p;
15438     while (*q && *q != '=') q++;
15439     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
15440     SendToProgram(buf, cps);
15441     p = q;
15442     if (*p == '=') {
15443       p++;
15444       if (*p == '\"') {
15445         p++;
15446         while (*p && *p != '\"') p++;
15447         if (*p == '\"') p++;
15448       } else {
15449         while (*p && *p != ' ') p++;
15450       }
15451     }
15452   }
15453
15454 }
15455
15456 void
15457 PeriodicUpdatesEvent (int newState)
15458 {
15459     if (newState == appData.periodicUpdates)
15460       return;
15461
15462     appData.periodicUpdates=newState;
15463
15464     /* Display type changes, so update it now */
15465 //    DisplayAnalysis();
15466
15467     /* Get the ball rolling again... */
15468     if (newState) {
15469         AnalysisPeriodicEvent(1);
15470         StartAnalysisClock();
15471     }
15472 }
15473
15474 void
15475 PonderNextMoveEvent (int newState)
15476 {
15477     if (newState == appData.ponderNextMove) return;
15478     if (gameMode == EditPosition) EditPositionDone(TRUE);
15479     if (newState) {
15480         SendToProgram("hard\n", &first);
15481         if (gameMode == TwoMachinesPlay) {
15482             SendToProgram("hard\n", &second);
15483         }
15484     } else {
15485         SendToProgram("easy\n", &first);
15486         thinkOutput[0] = NULLCHAR;
15487         if (gameMode == TwoMachinesPlay) {
15488             SendToProgram("easy\n", &second);
15489         }
15490     }
15491     appData.ponderNextMove = newState;
15492 }
15493
15494 void
15495 NewSettingEvent (int option, int *feature, char *command, int value)
15496 {
15497     char buf[MSG_SIZ];
15498
15499     if (gameMode == EditPosition) EditPositionDone(TRUE);
15500     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15501     if(feature == NULL || *feature) SendToProgram(buf, &first);
15502     if (gameMode == TwoMachinesPlay) {
15503         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15504     }
15505 }
15506
15507 void
15508 ShowThinkingEvent ()
15509 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15510 {
15511     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15512     int newState = appData.showThinking
15513         // [HGM] thinking: other features now need thinking output as well
15514         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15515
15516     if (oldState == newState) return;
15517     oldState = newState;
15518     if (gameMode == EditPosition) EditPositionDone(TRUE);
15519     if (oldState) {
15520         SendToProgram("post\n", &first);
15521         if (gameMode == TwoMachinesPlay) {
15522             SendToProgram("post\n", &second);
15523         }
15524     } else {
15525         SendToProgram("nopost\n", &first);
15526         thinkOutput[0] = NULLCHAR;
15527         if (gameMode == TwoMachinesPlay) {
15528             SendToProgram("nopost\n", &second);
15529         }
15530     }
15531 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15532 }
15533
15534 void
15535 AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which)
15536 {
15537   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15538   if (pr == NoProc) return;
15539   AskQuestion(title, question, replyPrefix, pr);
15540 }
15541
15542 void
15543 TypeInEvent (char firstChar)
15544 {
15545     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15546         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15547         gameMode == AnalyzeMode || gameMode == EditGame || 
15548         gameMode == EditPosition || gameMode == IcsExamining ||
15549         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15550         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15551                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15552                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15553         gameMode == Training) PopUpMoveDialog(firstChar);
15554 }
15555
15556 void
15557 TypeInDoneEvent (char *move)
15558 {
15559         Board board;
15560         int n, fromX, fromY, toX, toY;
15561         char promoChar;
15562         ChessMove moveType;
15563
15564         // [HGM] FENedit
15565         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15566                 EditPositionPasteFEN(move);
15567                 return;
15568         }
15569         // [HGM] movenum: allow move number to be typed in any mode
15570         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15571           ToNrEvent(2*n-1);
15572           return;
15573         }
15574
15575       if (gameMode != EditGame && currentMove != forwardMostMove && 
15576         gameMode != Training) {
15577         DisplayMoveError(_("Displayed move is not current"));
15578       } else {
15579         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15580           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15581         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15582         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15583           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15584           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15585         } else {
15586           DisplayMoveError(_("Could not parse move"));
15587         }
15588       }
15589 }
15590
15591 void
15592 DisplayMove (int moveNumber)
15593 {
15594     char message[MSG_SIZ];
15595     char res[MSG_SIZ];
15596     char cpThinkOutput[MSG_SIZ];
15597
15598     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15599
15600     if (moveNumber == forwardMostMove - 1 ||
15601         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15602
15603         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15604
15605         if (strchr(cpThinkOutput, '\n')) {
15606             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15607         }
15608     } else {
15609         *cpThinkOutput = NULLCHAR;
15610     }
15611
15612     /* [AS] Hide thinking from human user */
15613     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15614         *cpThinkOutput = NULLCHAR;
15615         if( thinkOutput[0] != NULLCHAR ) {
15616             int i;
15617
15618             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15619                 cpThinkOutput[i] = '.';
15620             }
15621             cpThinkOutput[i] = NULLCHAR;
15622             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15623         }
15624     }
15625
15626     if (moveNumber == forwardMostMove - 1 &&
15627         gameInfo.resultDetails != NULL) {
15628         if (gameInfo.resultDetails[0] == NULLCHAR) {
15629           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15630         } else {
15631           snprintf(res, MSG_SIZ, " {%s} %s",
15632                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15633         }
15634     } else {
15635         res[0] = NULLCHAR;
15636     }
15637
15638     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15639         DisplayMessage(res, cpThinkOutput);
15640     } else {
15641       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15642                 WhiteOnMove(moveNumber) ? " " : ".. ",
15643                 parseList[moveNumber], res);
15644         DisplayMessage(message, cpThinkOutput);
15645     }
15646 }
15647
15648 void
15649 DisplayComment (int moveNumber, char *text)
15650 {
15651     char title[MSG_SIZ];
15652
15653     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15654       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15655     } else {
15656       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15657               WhiteOnMove(moveNumber) ? " " : ".. ",
15658               parseList[moveNumber]);
15659     }
15660     if (text != NULL && (appData.autoDisplayComment || commentUp))
15661         CommentPopUp(title, text);
15662 }
15663
15664 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15665  * might be busy thinking or pondering.  It can be omitted if your
15666  * gnuchess is configured to stop thinking immediately on any user
15667  * input.  However, that gnuchess feature depends on the FIONREAD
15668  * ioctl, which does not work properly on some flavors of Unix.
15669  */
15670 void
15671 Attention (ChessProgramState *cps)
15672 {
15673 #if ATTENTION
15674     if (!cps->useSigint) return;
15675     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15676     switch (gameMode) {
15677       case MachinePlaysWhite:
15678       case MachinePlaysBlack:
15679       case TwoMachinesPlay:
15680       case IcsPlayingWhite:
15681       case IcsPlayingBlack:
15682       case AnalyzeMode:
15683       case AnalyzeFile:
15684         /* Skip if we know it isn't thinking */
15685         if (!cps->maybeThinking) return;
15686         if (appData.debugMode)
15687           fprintf(debugFP, "Interrupting %s\n", cps->which);
15688         InterruptChildProcess(cps->pr);
15689         cps->maybeThinking = FALSE;
15690         break;
15691       default:
15692         break;
15693     }
15694 #endif /*ATTENTION*/
15695 }
15696
15697 int
15698 CheckFlags ()
15699 {
15700     if (whiteTimeRemaining <= 0) {
15701         if (!whiteFlag) {
15702             whiteFlag = TRUE;
15703             if (appData.icsActive) {
15704                 if (appData.autoCallFlag &&
15705                     gameMode == IcsPlayingBlack && !blackFlag) {
15706                   SendToICS(ics_prefix);
15707                   SendToICS("flag\n");
15708                 }
15709             } else {
15710                 if (blackFlag) {
15711                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15712                 } else {
15713                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15714                     if (appData.autoCallFlag) {
15715                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15716                         return TRUE;
15717                     }
15718                 }
15719             }
15720         }
15721     }
15722     if (blackTimeRemaining <= 0) {
15723         if (!blackFlag) {
15724             blackFlag = TRUE;
15725             if (appData.icsActive) {
15726                 if (appData.autoCallFlag &&
15727                     gameMode == IcsPlayingWhite && !whiteFlag) {
15728                   SendToICS(ics_prefix);
15729                   SendToICS("flag\n");
15730                 }
15731             } else {
15732                 if (whiteFlag) {
15733                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15734                 } else {
15735                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15736                     if (appData.autoCallFlag) {
15737                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15738                         return TRUE;
15739                     }
15740                 }
15741             }
15742         }
15743     }
15744     return FALSE;
15745 }
15746
15747 void
15748 CheckTimeControl ()
15749 {
15750     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15751         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15752
15753     /*
15754      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15755      */
15756     if ( !WhiteOnMove(forwardMostMove) ) {
15757         /* White made time control */
15758         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15759         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15760         /* [HGM] time odds: correct new time quota for time odds! */
15761                                             / WhitePlayer()->timeOdds;
15762         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15763     } else {
15764         lastBlack -= blackTimeRemaining;
15765         /* Black made time control */
15766         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15767                                             / WhitePlayer()->other->timeOdds;
15768         lastWhite = whiteTimeRemaining;
15769     }
15770 }
15771
15772 void
15773 DisplayBothClocks ()
15774 {
15775     int wom = gameMode == EditPosition ?
15776       !blackPlaysFirst : WhiteOnMove(currentMove);
15777     DisplayWhiteClock(whiteTimeRemaining, wom);
15778     DisplayBlackClock(blackTimeRemaining, !wom);
15779 }
15780
15781
15782 /* Timekeeping seems to be a portability nightmare.  I think everyone
15783    has ftime(), but I'm really not sure, so I'm including some ifdefs
15784    to use other calls if you don't.  Clocks will be less accurate if
15785    you have neither ftime nor gettimeofday.
15786 */
15787
15788 /* VS 2008 requires the #include outside of the function */
15789 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15790 #include <sys/timeb.h>
15791 #endif
15792
15793 /* Get the current time as a TimeMark */
15794 void
15795 GetTimeMark (TimeMark *tm)
15796 {
15797 #if HAVE_GETTIMEOFDAY
15798
15799     struct timeval timeVal;
15800     struct timezone timeZone;
15801
15802     gettimeofday(&timeVal, &timeZone);
15803     tm->sec = (long) timeVal.tv_sec;
15804     tm->ms = (int) (timeVal.tv_usec / 1000L);
15805
15806 #else /*!HAVE_GETTIMEOFDAY*/
15807 #if HAVE_FTIME
15808
15809 // include <sys/timeb.h> / moved to just above start of function
15810     struct timeb timeB;
15811
15812     ftime(&timeB);
15813     tm->sec = (long) timeB.time;
15814     tm->ms = (int) timeB.millitm;
15815
15816 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15817     tm->sec = (long) time(NULL);
15818     tm->ms = 0;
15819 #endif
15820 #endif
15821 }
15822
15823 /* Return the difference in milliseconds between two
15824    time marks.  We assume the difference will fit in a long!
15825 */
15826 long
15827 SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1)
15828 {
15829     return 1000L*(tm2->sec - tm1->sec) +
15830            (long) (tm2->ms - tm1->ms);
15831 }
15832
15833
15834 /*
15835  * Code to manage the game clocks.
15836  *
15837  * In tournament play, black starts the clock and then white makes a move.
15838  * We give the human user a slight advantage if he is playing white---the
15839  * clocks don't run until he makes his first move, so it takes zero time.
15840  * Also, we don't account for network lag, so we could get out of sync
15841  * with GNU Chess's clock -- but then, referees are always right.
15842  */
15843
15844 static TimeMark tickStartTM;
15845 static long intendedTickLength;
15846
15847 long
15848 NextTickLength (long timeRemaining)
15849 {
15850     long nominalTickLength, nextTickLength;
15851
15852     if (timeRemaining > 0L && timeRemaining <= 10000L)
15853       nominalTickLength = 100L;
15854     else
15855       nominalTickLength = 1000L;
15856     nextTickLength = timeRemaining % nominalTickLength;
15857     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15858
15859     return nextTickLength;
15860 }
15861
15862 /* Adjust clock one minute up or down */
15863 void
15864 AdjustClock (Boolean which, int dir)
15865 {
15866     if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; }
15867     if(which) blackTimeRemaining += 60000*dir;
15868     else      whiteTimeRemaining += 60000*dir;
15869     DisplayBothClocks();
15870     adjustedClock = TRUE;
15871 }
15872
15873 /* Stop clocks and reset to a fresh time control */
15874 void
15875 ResetClocks ()
15876 {
15877     (void) StopClockTimer();
15878     if (appData.icsActive) {
15879         whiteTimeRemaining = blackTimeRemaining = 0;
15880     } else if (searchTime) {
15881         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15882         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15883     } else { /* [HGM] correct new time quote for time odds */
15884         whiteTC = blackTC = fullTimeControlString;
15885         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15886         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15887     }
15888     if (whiteFlag || blackFlag) {
15889         DisplayTitle("");
15890         whiteFlag = blackFlag = FALSE;
15891     }
15892     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15893     DisplayBothClocks();
15894     adjustedClock = FALSE;
15895 }
15896
15897 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15898
15899 /* Decrement running clock by amount of time that has passed */
15900 void
15901 DecrementClocks ()
15902 {
15903     long timeRemaining;
15904     long lastTickLength, fudge;
15905     TimeMark now;
15906
15907     if (!appData.clockMode) return;
15908     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15909
15910     GetTimeMark(&now);
15911
15912     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15913
15914     /* Fudge if we woke up a little too soon */
15915     fudge = intendedTickLength - lastTickLength;
15916     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15917
15918     if (WhiteOnMove(forwardMostMove)) {
15919         if(whiteNPS >= 0) lastTickLength = 0;
15920         timeRemaining = whiteTimeRemaining -= lastTickLength;
15921         if(timeRemaining < 0 && !appData.icsActive) {
15922             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15923             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15924                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15925                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15926             }
15927         }
15928         DisplayWhiteClock(whiteTimeRemaining - fudge,
15929                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15930     } else {
15931         if(blackNPS >= 0) lastTickLength = 0;
15932         timeRemaining = blackTimeRemaining -= lastTickLength;
15933         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15934             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15935             if(suddenDeath) {
15936                 blackStartMove = forwardMostMove;
15937                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15938             }
15939         }
15940         DisplayBlackClock(blackTimeRemaining - fudge,
15941                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15942     }
15943     if (CheckFlags()) return;
15944
15945     tickStartTM = now;
15946     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15947     StartClockTimer(intendedTickLength);
15948
15949     /* if the time remaining has fallen below the alarm threshold, sound the
15950      * alarm. if the alarm has sounded and (due to a takeback or time control
15951      * with increment) the time remaining has increased to a level above the
15952      * threshold, reset the alarm so it can sound again.
15953      */
15954
15955     if (appData.icsActive && appData.icsAlarm) {
15956
15957         /* make sure we are dealing with the user's clock */
15958         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15959                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15960            )) return;
15961
15962         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15963             alarmSounded = FALSE;
15964         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15965             PlayAlarmSound();
15966             alarmSounded = TRUE;
15967         }
15968     }
15969 }
15970
15971
15972 /* A player has just moved, so stop the previously running
15973    clock and (if in clock mode) start the other one.
15974    We redisplay both clocks in case we're in ICS mode, because
15975    ICS gives us an update to both clocks after every move.
15976    Note that this routine is called *after* forwardMostMove
15977    is updated, so the last fractional tick must be subtracted
15978    from the color that is *not* on move now.
15979 */
15980 void
15981 SwitchClocks (int newMoveNr)
15982 {
15983     long lastTickLength;
15984     TimeMark now;
15985     int flagged = FALSE;
15986
15987     GetTimeMark(&now);
15988
15989     if (StopClockTimer() && appData.clockMode) {
15990         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15991         if (!WhiteOnMove(forwardMostMove)) {
15992             if(blackNPS >= 0) lastTickLength = 0;
15993             blackTimeRemaining -= lastTickLength;
15994            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15995 //         if(pvInfoList[forwardMostMove].time == -1)
15996                  pvInfoList[forwardMostMove].time =               // use GUI time
15997                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15998         } else {
15999            if(whiteNPS >= 0) lastTickLength = 0;
16000            whiteTimeRemaining -= lastTickLength;
16001            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
16002 //         if(pvInfoList[forwardMostMove].time == -1)
16003                  pvInfoList[forwardMostMove].time =
16004                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
16005         }
16006         flagged = CheckFlags();
16007     }
16008     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
16009     CheckTimeControl();
16010
16011     if (flagged || !appData.clockMode) return;
16012
16013     switch (gameMode) {
16014       case MachinePlaysBlack:
16015       case MachinePlaysWhite:
16016       case BeginningOfGame:
16017         if (pausing) return;
16018         break;
16019
16020       case EditGame:
16021       case PlayFromGameFile:
16022       case IcsExamining:
16023         return;
16024
16025       default:
16026         break;
16027     }
16028
16029     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
16030         if(WhiteOnMove(forwardMostMove))
16031              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
16032         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
16033     }
16034
16035     tickStartTM = now;
16036     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16037       whiteTimeRemaining : blackTimeRemaining);
16038     StartClockTimer(intendedTickLength);
16039 }
16040
16041
16042 /* Stop both clocks */
16043 void
16044 StopClocks ()
16045 {
16046     long lastTickLength;
16047     TimeMark now;
16048
16049     if (!StopClockTimer()) return;
16050     if (!appData.clockMode) return;
16051
16052     GetTimeMark(&now);
16053
16054     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
16055     if (WhiteOnMove(forwardMostMove)) {
16056         if(whiteNPS >= 0) lastTickLength = 0;
16057         whiteTimeRemaining -= lastTickLength;
16058         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
16059     } else {
16060         if(blackNPS >= 0) lastTickLength = 0;
16061         blackTimeRemaining -= lastTickLength;
16062         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
16063     }
16064     CheckFlags();
16065 }
16066
16067 /* Start clock of player on move.  Time may have been reset, so
16068    if clock is already running, stop and restart it. */
16069 void
16070 StartClocks ()
16071 {
16072     (void) StopClockTimer(); /* in case it was running already */
16073     DisplayBothClocks();
16074     if (CheckFlags()) return;
16075
16076     if (!appData.clockMode) return;
16077     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
16078
16079     GetTimeMark(&tickStartTM);
16080     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
16081       whiteTimeRemaining : blackTimeRemaining);
16082
16083    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
16084     whiteNPS = blackNPS = -1;
16085     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
16086        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
16087         whiteNPS = first.nps;
16088     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
16089        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
16090         blackNPS = first.nps;
16091     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
16092         whiteNPS = second.nps;
16093     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
16094         blackNPS = second.nps;
16095     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
16096
16097     StartClockTimer(intendedTickLength);
16098 }
16099
16100 char *
16101 TimeString (long ms)
16102 {
16103     long second, minute, hour, day;
16104     char *sign = "";
16105     static char buf[32];
16106
16107     if (ms > 0 && ms <= 9900) {
16108       /* convert milliseconds to tenths, rounding up */
16109       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
16110
16111       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
16112       return buf;
16113     }
16114
16115     /* convert milliseconds to seconds, rounding up */
16116     /* use floating point to avoid strangeness of integer division
16117        with negative dividends on many machines */
16118     second = (long) floor(((double) (ms + 999L)) / 1000.0);
16119
16120     if (second < 0) {
16121         sign = "-";
16122         second = -second;
16123     }
16124
16125     day = second / (60 * 60 * 24);
16126     second = second % (60 * 60 * 24);
16127     hour = second / (60 * 60);
16128     second = second % (60 * 60);
16129     minute = second / 60;
16130     second = second % 60;
16131
16132     if (day > 0)
16133       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
16134               sign, day, hour, minute, second);
16135     else if (hour > 0)
16136       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
16137     else
16138       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
16139
16140     return buf;
16141 }
16142
16143
16144 /*
16145  * This is necessary because some C libraries aren't ANSI C compliant yet.
16146  */
16147 char *
16148 StrStr (char *string, char *match)
16149 {
16150     int i, length;
16151
16152     length = strlen(match);
16153
16154     for (i = strlen(string) - length; i >= 0; i--, string++)
16155       if (!strncmp(match, string, length))
16156         return string;
16157
16158     return NULL;
16159 }
16160
16161 char *
16162 StrCaseStr (char *string, char *match)
16163 {
16164     int i, j, length;
16165
16166     length = strlen(match);
16167
16168     for (i = strlen(string) - length; i >= 0; i--, string++) {
16169         for (j = 0; j < length; j++) {
16170             if (ToLower(match[j]) != ToLower(string[j]))
16171               break;
16172         }
16173         if (j == length) return string;
16174     }
16175
16176     return NULL;
16177 }
16178
16179 #ifndef _amigados
16180 int
16181 StrCaseCmp (char *s1, char *s2)
16182 {
16183     char c1, c2;
16184
16185     for (;;) {
16186         c1 = ToLower(*s1++);
16187         c2 = ToLower(*s2++);
16188         if (c1 > c2) return 1;
16189         if (c1 < c2) return -1;
16190         if (c1 == NULLCHAR) return 0;
16191     }
16192 }
16193
16194
16195 int
16196 ToLower (int c)
16197 {
16198     return isupper(c) ? tolower(c) : c;
16199 }
16200
16201
16202 int
16203 ToUpper (int c)
16204 {
16205     return islower(c) ? toupper(c) : c;
16206 }
16207 #endif /* !_amigados    */
16208
16209 char *
16210 StrSave (char *s)
16211 {
16212   char *ret;
16213
16214   if ((ret = (char *) malloc(strlen(s) + 1)))
16215     {
16216       safeStrCpy(ret, s, strlen(s)+1);
16217     }
16218   return ret;
16219 }
16220
16221 char *
16222 StrSavePtr (char *s, char **savePtr)
16223 {
16224     if (*savePtr) {
16225         free(*savePtr);
16226     }
16227     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
16228       safeStrCpy(*savePtr, s, strlen(s)+1);
16229     }
16230     return(*savePtr);
16231 }
16232
16233 char *
16234 PGNDate ()
16235 {
16236     time_t clock;
16237     struct tm *tm;
16238     char buf[MSG_SIZ];
16239
16240     clock = time((time_t *)NULL);
16241     tm = localtime(&clock);
16242     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
16243             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
16244     return StrSave(buf);
16245 }
16246
16247
16248 char *
16249 PositionToFEN (int move, char *overrideCastling)
16250 {
16251     int i, j, fromX, fromY, toX, toY;
16252     int whiteToPlay;
16253     char buf[MSG_SIZ];
16254     char *p, *q;
16255     int emptycount;
16256     ChessSquare piece;
16257
16258     whiteToPlay = (gameMode == EditPosition) ?
16259       !blackPlaysFirst : (move % 2 == 0);
16260     p = buf;
16261
16262     /* Piece placement data */
16263     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16264         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
16265         emptycount = 0;
16266         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
16267             if (boards[move][i][j] == EmptySquare) {
16268                 emptycount++;
16269             } else { ChessSquare piece = boards[move][i][j];
16270                 if (emptycount > 0) {
16271                     if(emptycount<10) /* [HGM] can be >= 10 */
16272                         *p++ = '0' + emptycount;
16273                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16274                     emptycount = 0;
16275                 }
16276                 if(PieceToChar(piece) == '+') {
16277                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
16278                     *p++ = '+';
16279                     piece = (ChessSquare)(DEMOTED piece);
16280                 }
16281                 *p++ = PieceToChar(piece);
16282                 if(p[-1] == '~') {
16283                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
16284                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
16285                     *p++ = '~';
16286                 }
16287             }
16288         }
16289         if (emptycount > 0) {
16290             if(emptycount<10) /* [HGM] can be >= 10 */
16291                 *p++ = '0' + emptycount;
16292             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
16293             emptycount = 0;
16294         }
16295         *p++ = '/';
16296     }
16297     *(p - 1) = ' ';
16298
16299     /* [HGM] print Crazyhouse or Shogi holdings */
16300     if( gameInfo.holdingsWidth ) {
16301         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
16302         q = p;
16303         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
16304             piece = boards[move][i][BOARD_WIDTH-1];
16305             if( piece != EmptySquare )
16306               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
16307                   *p++ = PieceToChar(piece);
16308         }
16309         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
16310             piece = boards[move][BOARD_HEIGHT-i-1][0];
16311             if( piece != EmptySquare )
16312               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
16313                   *p++ = PieceToChar(piece);
16314         }
16315
16316         if( q == p ) *p++ = '-';
16317         *p++ = ']';
16318         *p++ = ' ';
16319     }
16320
16321     /* Active color */
16322     *p++ = whiteToPlay ? 'w' : 'b';
16323     *p++ = ' ';
16324
16325   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
16326     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
16327   } else {
16328   if(nrCastlingRights) {
16329      q = p;
16330      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
16331        /* [HGM] write directly from rights */
16332            if(boards[move][CASTLING][2] != NoRights &&
16333               boards[move][CASTLING][0] != NoRights   )
16334                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
16335            if(boards[move][CASTLING][2] != NoRights &&
16336               boards[move][CASTLING][1] != NoRights   )
16337                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
16338            if(boards[move][CASTLING][5] != NoRights &&
16339               boards[move][CASTLING][3] != NoRights   )
16340                 *p++ = boards[move][CASTLING][3] + AAA;
16341            if(boards[move][CASTLING][5] != NoRights &&
16342               boards[move][CASTLING][4] != NoRights   )
16343                 *p++ = boards[move][CASTLING][4] + AAA;
16344      } else {
16345
16346         /* [HGM] write true castling rights */
16347         if( nrCastlingRights == 6 ) {
16348             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
16349                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
16350             if(boards[move][CASTLING][1] == BOARD_LEFT &&
16351                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
16352             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
16353                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
16354             if(boards[move][CASTLING][4] == BOARD_LEFT &&
16355                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
16356         }
16357      }
16358      if (q == p) *p++ = '-'; /* No castling rights */
16359      *p++ = ' ';
16360   }
16361
16362   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16363      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16364     /* En passant target square */
16365     if (move > backwardMostMove) {
16366         fromX = moveList[move - 1][0] - AAA;
16367         fromY = moveList[move - 1][1] - ONE;
16368         toX = moveList[move - 1][2] - AAA;
16369         toY = moveList[move - 1][3] - ONE;
16370         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
16371             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
16372             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
16373             fromX == toX) {
16374             /* 2-square pawn move just happened */
16375             *p++ = toX + AAA;
16376             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16377         } else {
16378             *p++ = '-';
16379         }
16380     } else if(move == backwardMostMove) {
16381         // [HGM] perhaps we should always do it like this, and forget the above?
16382         if((signed char)boards[move][EP_STATUS] >= 0) {
16383             *p++ = boards[move][EP_STATUS] + AAA;
16384             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
16385         } else {
16386             *p++ = '-';
16387         }
16388     } else {
16389         *p++ = '-';
16390     }
16391     *p++ = ' ';
16392   }
16393   }
16394
16395     /* [HGM] find reversible plies */
16396     {   int i = 0, j=move;
16397
16398         if (appData.debugMode) { int k;
16399             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
16400             for(k=backwardMostMove; k<=forwardMostMove; k++)
16401                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
16402
16403         }
16404
16405         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
16406         if( j == backwardMostMove ) i += initialRulePlies;
16407         sprintf(p, "%d ", i);
16408         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
16409     }
16410     /* Fullmove number */
16411     sprintf(p, "%d", (move / 2) + 1);
16412
16413     return StrSave(buf);
16414 }
16415
16416 Boolean
16417 ParseFEN (Board board, int *blackPlaysFirst, char *fen)
16418 {
16419     int i, j;
16420     char *p, c;
16421     int emptycount;
16422     ChessSquare piece;
16423
16424     p = fen;
16425
16426     /* [HGM] by default clear Crazyhouse holdings, if present */
16427     if(gameInfo.holdingsWidth) {
16428        for(i=0; i<BOARD_HEIGHT; i++) {
16429            board[i][0]             = EmptySquare; /* black holdings */
16430            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16431            board[i][1]             = (ChessSquare) 0; /* black counts */
16432            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16433        }
16434     }
16435
16436     /* Piece placement data */
16437     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16438         j = 0;
16439         for (;;) {
16440             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16441                 if (*p == '/') p++;
16442                 emptycount = gameInfo.boardWidth - j;
16443                 while (emptycount--)
16444                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16445                 break;
16446 #if(BOARD_FILES >= 10)
16447             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16448                 p++; emptycount=10;
16449                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16450                 while (emptycount--)
16451                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16452 #endif
16453             } else if (isdigit(*p)) {
16454                 emptycount = *p++ - '0';
16455                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16456                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16457                 while (emptycount--)
16458                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16459             } else if (*p == '+' || isalpha(*p)) {
16460                 if (j >= gameInfo.boardWidth) return FALSE;
16461                 if(*p=='+') {
16462                     piece = CharToPiece(*++p);
16463                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16464                     piece = (ChessSquare) (PROMOTED piece ); p++;
16465                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16466                 } else piece = CharToPiece(*p++);
16467
16468                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16469                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16470                     piece = (ChessSquare) (PROMOTED piece);
16471                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16472                     p++;
16473                 }
16474                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16475             } else {
16476                 return FALSE;
16477             }
16478         }
16479     }
16480     while (*p == '/' || *p == ' ') p++;
16481
16482     /* [HGM] look for Crazyhouse holdings here */
16483     while(*p==' ') p++;
16484     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16485         if(*p == '[') p++;
16486         if(*p == '-' ) p++; /* empty holdings */ else {
16487             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16488             /* if we would allow FEN reading to set board size, we would   */
16489             /* have to add holdings and shift the board read so far here   */
16490             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16491                 p++;
16492                 if((int) piece >= (int) BlackPawn ) {
16493                     i = (int)piece - (int)BlackPawn;
16494                     i = PieceToNumber((ChessSquare)i);
16495                     if( i >= gameInfo.holdingsSize ) return FALSE;
16496                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16497                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16498                 } else {
16499                     i = (int)piece - (int)WhitePawn;
16500                     i = PieceToNumber((ChessSquare)i);
16501                     if( i >= gameInfo.holdingsSize ) return FALSE;
16502                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16503                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16504                 }
16505             }
16506         }
16507         if(*p == ']') p++;
16508     }
16509
16510     while(*p == ' ') p++;
16511
16512     /* Active color */
16513     c = *p++;
16514     if(appData.colorNickNames) {
16515       if( c == appData.colorNickNames[0] ) c = 'w'; else
16516       if( c == appData.colorNickNames[1] ) c = 'b';
16517     }
16518     switch (c) {
16519       case 'w':
16520         *blackPlaysFirst = FALSE;
16521         break;
16522       case 'b':
16523         *blackPlaysFirst = TRUE;
16524         break;
16525       default:
16526         return FALSE;
16527     }
16528
16529     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16530     /* return the extra info in global variiables             */
16531
16532     /* set defaults in case FEN is incomplete */
16533     board[EP_STATUS] = EP_UNKNOWN;
16534     for(i=0; i<nrCastlingRights; i++ ) {
16535         board[CASTLING][i] =
16536             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16537     }   /* assume possible unless obviously impossible */
16538     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16539     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16540     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16541                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16542     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16543     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16544     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16545                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16546     FENrulePlies = 0;
16547
16548     while(*p==' ') p++;
16549     if(nrCastlingRights) {
16550       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16551           /* castling indicator present, so default becomes no castlings */
16552           for(i=0; i<nrCastlingRights; i++ ) {
16553                  board[CASTLING][i] = NoRights;
16554           }
16555       }
16556       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16557              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16558              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16559              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16560         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16561
16562         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16563             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16564             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16565         }
16566         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16567             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16568         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16569                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16570         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16571                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16572         switch(c) {
16573           case'K':
16574               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16575               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16576               board[CASTLING][2] = whiteKingFile;
16577               break;
16578           case'Q':
16579               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16580               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16581               board[CASTLING][2] = whiteKingFile;
16582               break;
16583           case'k':
16584               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16585               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16586               board[CASTLING][5] = blackKingFile;
16587               break;
16588           case'q':
16589               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16590               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16591               board[CASTLING][5] = blackKingFile;
16592           case '-':
16593               break;
16594           default: /* FRC castlings */
16595               if(c >= 'a') { /* black rights */
16596                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16597                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16598                   if(i == BOARD_RGHT) break;
16599                   board[CASTLING][5] = i;
16600                   c -= AAA;
16601                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16602                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16603                   if(c > i)
16604                       board[CASTLING][3] = c;
16605                   else
16606                       board[CASTLING][4] = c;
16607               } else { /* white rights */
16608                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16609                     if(board[0][i] == WhiteKing) break;
16610                   if(i == BOARD_RGHT) break;
16611                   board[CASTLING][2] = i;
16612                   c -= AAA - 'a' + 'A';
16613                   if(board[0][c] >= WhiteKing) break;
16614                   if(c > i)
16615                       board[CASTLING][0] = c;
16616                   else
16617                       board[CASTLING][1] = c;
16618               }
16619         }
16620       }
16621       for(i=0; i<nrCastlingRights; i++)
16622         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16623     if (appData.debugMode) {
16624         fprintf(debugFP, "FEN castling rights:");
16625         for(i=0; i<nrCastlingRights; i++)
16626         fprintf(debugFP, " %d", board[CASTLING][i]);
16627         fprintf(debugFP, "\n");
16628     }
16629
16630       while(*p==' ') p++;
16631     }
16632
16633     /* read e.p. field in games that know e.p. capture */
16634     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16635        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16636       if(*p=='-') {
16637         p++; board[EP_STATUS] = EP_NONE;
16638       } else {
16639          char c = *p++ - AAA;
16640
16641          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16642          if(*p >= '0' && *p <='9') p++;
16643          board[EP_STATUS] = c;
16644       }
16645     }
16646
16647
16648     if(sscanf(p, "%d", &i) == 1) {
16649         FENrulePlies = i; /* 50-move ply counter */
16650         /* (The move number is still ignored)    */
16651     }
16652
16653     return TRUE;
16654 }
16655
16656 void
16657 EditPositionPasteFEN (char *fen)
16658 {
16659   if (fen != NULL) {
16660     Board initial_position;
16661
16662     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16663       DisplayError(_("Bad FEN position in clipboard"), 0);
16664       return ;
16665     } else {
16666       int savedBlackPlaysFirst = blackPlaysFirst;
16667       EditPositionEvent();
16668       blackPlaysFirst = savedBlackPlaysFirst;
16669       CopyBoard(boards[0], initial_position);
16670       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16671       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16672       DisplayBothClocks();
16673       DrawPosition(FALSE, boards[currentMove]);
16674     }
16675   }
16676 }
16677
16678 static char cseq[12] = "\\   ";
16679
16680 Boolean
16681 set_cont_sequence (char *new_seq)
16682 {
16683     int len;
16684     Boolean ret;
16685
16686     // handle bad attempts to set the sequence
16687         if (!new_seq)
16688                 return 0; // acceptable error - no debug
16689
16690     len = strlen(new_seq);
16691     ret = (len > 0) && (len < sizeof(cseq));
16692     if (ret)
16693       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16694     else if (appData.debugMode)
16695       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16696     return ret;
16697 }
16698
16699 /*
16700     reformat a source message so words don't cross the width boundary.  internal
16701     newlines are not removed.  returns the wrapped size (no null character unless
16702     included in source message).  If dest is NULL, only calculate the size required
16703     for the dest buffer.  lp argument indicats line position upon entry, and it's
16704     passed back upon exit.
16705 */
16706 int
16707 wrap (char *dest, char *src, int count, int width, int *lp)
16708 {
16709     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16710
16711     cseq_len = strlen(cseq);
16712     old_line = line = *lp;
16713     ansi = len = clen = 0;
16714
16715     for (i=0; i < count; i++)
16716     {
16717         if (src[i] == '\033')
16718             ansi = 1;
16719
16720         // if we hit the width, back up
16721         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16722         {
16723             // store i & len in case the word is too long
16724             old_i = i, old_len = len;
16725
16726             // find the end of the last word
16727             while (i && src[i] != ' ' && src[i] != '\n')
16728             {
16729                 i--;
16730                 len--;
16731             }
16732
16733             // word too long?  restore i & len before splitting it
16734             if ((old_i-i+clen) >= width)
16735             {
16736                 i = old_i;
16737                 len = old_len;
16738             }
16739
16740             // extra space?
16741             if (i && src[i-1] == ' ')
16742                 len--;
16743
16744             if (src[i] != ' ' && src[i] != '\n')
16745             {
16746                 i--;
16747                 if (len)
16748                     len--;
16749             }
16750
16751             // now append the newline and continuation sequence
16752             if (dest)
16753                 dest[len] = '\n';
16754             len++;
16755             if (dest)
16756                 strncpy(dest+len, cseq, cseq_len);
16757             len += cseq_len;
16758             line = cseq_len;
16759             clen = cseq_len;
16760             continue;
16761         }
16762
16763         if (dest)
16764             dest[len] = src[i];
16765         len++;
16766         if (!ansi)
16767             line++;
16768         if (src[i] == '\n')
16769             line = 0;
16770         if (src[i] == 'm')
16771             ansi = 0;
16772     }
16773     if (dest && appData.debugMode)
16774     {
16775         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16776             count, width, line, len, *lp);
16777         show_bytes(debugFP, src, count);
16778         fprintf(debugFP, "\ndest: ");
16779         show_bytes(debugFP, dest, len);
16780         fprintf(debugFP, "\n");
16781     }
16782     *lp = dest ? line : old_line;
16783
16784     return len;
16785 }
16786
16787 // [HGM] vari: routines for shelving variations
16788 Boolean modeRestore = FALSE;
16789
16790 void
16791 PushInner (int firstMove, int lastMove)
16792 {
16793         int i, j, nrMoves = lastMove - firstMove;
16794
16795         // push current tail of game on stack
16796         savedResult[storedGames] = gameInfo.result;
16797         savedDetails[storedGames] = gameInfo.resultDetails;
16798         gameInfo.resultDetails = NULL;
16799         savedFirst[storedGames] = firstMove;
16800         savedLast [storedGames] = lastMove;
16801         savedFramePtr[storedGames] = framePtr;
16802         framePtr -= nrMoves; // reserve space for the boards
16803         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16804             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16805             for(j=0; j<MOVE_LEN; j++)
16806                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16807             for(j=0; j<2*MOVE_LEN; j++)
16808                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16809             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16810             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16811             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16812             pvInfoList[firstMove+i-1].depth = 0;
16813             commentList[framePtr+i] = commentList[firstMove+i];
16814             commentList[firstMove+i] = NULL;
16815         }
16816
16817         storedGames++;
16818         forwardMostMove = firstMove; // truncate game so we can start variation
16819 }
16820
16821 void
16822 PushTail (int firstMove, int lastMove)
16823 {
16824         if(appData.icsActive) { // only in local mode
16825                 forwardMostMove = currentMove; // mimic old ICS behavior
16826                 return;
16827         }
16828         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16829
16830         PushInner(firstMove, lastMove);
16831         if(storedGames == 1) GreyRevert(FALSE);
16832         if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
16833 }
16834
16835 void
16836 PopInner (Boolean annotate)
16837 {
16838         int i, j, nrMoves;
16839         char buf[8000], moveBuf[20];
16840
16841         ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
16842         storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
16843         nrMoves = savedLast[storedGames] - currentMove;
16844         if(annotate) {
16845                 int cnt = 10;
16846                 if(!WhiteOnMove(currentMove))
16847                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16848                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16849                 for(i=currentMove; i<forwardMostMove; i++) {
16850                         if(WhiteOnMove(i))
16851                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16852                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16853                         strcat(buf, moveBuf);
16854                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16855                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16856                 }
16857                 strcat(buf, ")");
16858         }
16859         for(i=1; i<=nrMoves; i++) { // copy last variation back
16860             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16861             for(j=0; j<MOVE_LEN; j++)
16862                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16863             for(j=0; j<2*MOVE_LEN; j++)
16864                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16865             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16866             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16867             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16868             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16869             commentList[currentMove+i] = commentList[framePtr+i];
16870             commentList[framePtr+i] = NULL;
16871         }
16872         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16873         framePtr = savedFramePtr[storedGames];
16874         gameInfo.result = savedResult[storedGames];
16875         if(gameInfo.resultDetails != NULL) {
16876             free(gameInfo.resultDetails);
16877       }
16878         gameInfo.resultDetails = savedDetails[storedGames];
16879         forwardMostMove = currentMove + nrMoves;
16880 }
16881
16882 Boolean
16883 PopTail (Boolean annotate)
16884 {
16885         if(appData.icsActive) return FALSE; // only in local mode
16886         if(!storedGames) return FALSE; // sanity
16887         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16888
16889         PopInner(annotate);
16890         if(currentMove < forwardMostMove) ForwardEvent(); else
16891         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
16892
16893         if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
16894         return TRUE;
16895 }
16896
16897 void
16898 CleanupTail ()
16899 {       // remove all shelved variations
16900         int i;
16901         for(i=0; i<storedGames; i++) {
16902             if(savedDetails[i])
16903                 free(savedDetails[i]);
16904             savedDetails[i] = NULL;
16905         }
16906         for(i=framePtr; i<MAX_MOVES; i++) {
16907                 if(commentList[i]) free(commentList[i]);
16908                 commentList[i] = NULL;
16909         }
16910         framePtr = MAX_MOVES-1;
16911         storedGames = 0;
16912 }
16913
16914 void
16915 LoadVariation (int index, char *text)
16916 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16917         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16918         int level = 0, move;
16919
16920         if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
16921         // first find outermost bracketing variation
16922         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16923             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16924                 if(*p == '{') wait = '}'; else
16925                 if(*p == '[') wait = ']'; else
16926                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16927                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16928             }
16929             if(*p == wait) wait = NULLCHAR; // closing ]} found
16930             p++;
16931         }
16932         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16933         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16934         end[1] = NULLCHAR; // clip off comment beyond variation
16935         ToNrEvent(currentMove-1);
16936         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16937         // kludge: use ParsePV() to append variation to game
16938         move = currentMove;
16939         ParsePV(start, TRUE, TRUE);
16940         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16941         ClearPremoveHighlights();
16942         CommentPopDown();
16943         ToNrEvent(currentMove+1);
16944 }
16945