Fix writing searchTime in tourneyFile
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237 void DisplayTwoMachinesTitle P(());
238
239 #ifdef WIN32
240        extern void ConsoleCreate();
241 #endif
242
243 ChessProgramState *WhitePlayer();
244 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
245 int VerifyDisplayMode P(());
246
247 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
248 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
249 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
250 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
251 void ics_update_width P((int new_width));
252 extern char installDir[MSG_SIZ];
253 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 Boolean abortMatch;
255
256 extern int tinyLayout, smallLayout;
257 ChessProgramStats programStats;
258 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 int endPV = -1;
260 static int exiting = 0; /* [HGM] moved to top */
261 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
262 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
263 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
264 int partnerHighlight[2];
265 Boolean partnerBoardValid = 0;
266 char partnerStatus[MSG_SIZ];
267 Boolean partnerUp;
268 Boolean originalFlip;
269 Boolean twoBoards = 0;
270 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
271 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
272 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
273 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
274 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
275 int opponentKibitzes;
276 int lastSavedGame; /* [HGM] save: ID of game */
277 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
278 extern int chatCount;
279 int chattingPartner;
280 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
281 char lastMsg[MSG_SIZ];
282 ChessSquare pieceSweep = EmptySquare;
283 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
284 int promoDefaultAltered;
285
286 /* States for ics_getting_history */
287 #define H_FALSE 0
288 #define H_REQUESTED 1
289 #define H_GOT_REQ_HEADER 2
290 #define H_GOT_UNREQ_HEADER 3
291 #define H_GETTING_MOVES 4
292 #define H_GOT_UNWANTED_HEADER 5
293
294 /* whosays values for GameEnds */
295 #define GE_ICS 0
296 #define GE_ENGINE 1
297 #define GE_PLAYER 2
298 #define GE_FILE 3
299 #define GE_XBOARD 4
300 #define GE_ENGINE1 5
301 #define GE_ENGINE2 6
302
303 /* Maximum number of games in a cmail message */
304 #define CMAIL_MAX_GAMES 20
305
306 /* Different types of move when calling RegisterMove */
307 #define CMAIL_MOVE   0
308 #define CMAIL_RESIGN 1
309 #define CMAIL_DRAW   2
310 #define CMAIL_ACCEPT 3
311
312 /* Different types of result to remember for each game */
313 #define CMAIL_NOT_RESULT 0
314 #define CMAIL_OLD_RESULT 1
315 #define CMAIL_NEW_RESULT 2
316
317 /* Telnet protocol constants */
318 #define TN_WILL 0373
319 #define TN_WONT 0374
320 #define TN_DO   0375
321 #define TN_DONT 0376
322 #define TN_IAC  0377
323 #define TN_ECHO 0001
324 #define TN_SGA  0003
325 #define TN_PORT 23
326
327 char*
328 safeStrCpy( char *dst, const char *src, size_t count )
329 { // [HGM] made safe
330   int i;
331   assert( dst != NULL );
332   assert( src != NULL );
333   assert( count > 0 );
334
335   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
336   if(  i == count && dst[count-1] != NULLCHAR)
337     {
338       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
339       if(appData.debugMode)
340       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
341     }
342
343   return dst;
344 }
345
346 /* Some compiler can't cast u64 to double
347  * This function do the job for us:
348
349  * We use the highest bit for cast, this only
350  * works if the highest bit is not
351  * in use (This should not happen)
352  *
353  * We used this for all compiler
354  */
355 double
356 u64ToDouble(u64 value)
357 {
358   double r;
359   u64 tmp = value & u64Const(0x7fffffffffffffff);
360   r = (double)(s64)tmp;
361   if (value & u64Const(0x8000000000000000))
362        r +=  9.2233720368547758080e18; /* 2^63 */
363  return r;
364 }
365
366 /* Fake up flags for now, as we aren't keeping track of castling
367    availability yet. [HGM] Change of logic: the flag now only
368    indicates the type of castlings allowed by the rule of the game.
369    The actual rights themselves are maintained in the array
370    castlingRights, as part of the game history, and are not probed
371    by this function.
372  */
373 int
374 PosFlags(index)
375 {
376   int flags = F_ALL_CASTLE_OK;
377   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
378   switch (gameInfo.variant) {
379   case VariantSuicide:
380     flags &= ~F_ALL_CASTLE_OK;
381   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
382     flags |= F_IGNORE_CHECK;
383   case VariantLosers:
384     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385     break;
386   case VariantAtomic:
387     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388     break;
389   case VariantKriegspiel:
390     flags |= F_KRIEGSPIEL_CAPTURE;
391     break;
392   case VariantCapaRandom:
393   case VariantFischeRandom:
394     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
395   case VariantNoCastle:
396   case VariantShatranj:
397   case VariantCourier:
398   case VariantMakruk:
399   case VariantGrand:
400     flags &= ~F_ALL_CASTLE_OK;
401     break;
402   default:
403     break;
404   }
405   return flags;
406 }
407
408 FILE *gameFileFP, *debugFP;
409
410 /*
411     [AS] Note: sometimes, the sscanf() function is used to parse the input
412     into a fixed-size buffer. Because of this, we must be prepared to
413     receive strings as long as the size of the input buffer, which is currently
414     set to 4K for Windows and 8K for the rest.
415     So, we must either allocate sufficiently large buffers here, or
416     reduce the size of the input buffer in the input reading part.
417 */
418
419 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
420 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
421 char thinkOutput1[MSG_SIZ*10];
422
423 ChessProgramState first, second, pairing;
424
425 /* premove variables */
426 int premoveToX = 0;
427 int premoveToY = 0;
428 int premoveFromX = 0;
429 int premoveFromY = 0;
430 int premovePromoChar = 0;
431 int gotPremove = 0;
432 Boolean alarmSounded;
433 /* end premove variables */
434
435 char *ics_prefix = "$";
436 int ics_type = ICS_GENERIC;
437
438 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
439 int pauseExamForwardMostMove = 0;
440 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
441 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
442 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
443 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
444 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
445 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
446 int whiteFlag = FALSE, blackFlag = FALSE;
447 int userOfferedDraw = FALSE;
448 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
449 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
450 int cmailMoveType[CMAIL_MAX_GAMES];
451 long ics_clock_paused = 0;
452 ProcRef icsPR = NoProc, cmailPR = NoProc;
453 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
454 GameMode gameMode = BeginningOfGame;
455 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
456 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
457 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
458 int hiddenThinkOutputState = 0; /* [AS] */
459 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
460 int adjudicateLossPlies = 6;
461 char white_holding[64], black_holding[64];
462 TimeMark lastNodeCountTime;
463 long lastNodeCount=0;
464 int shiftKey; // [HGM] set by mouse handler
465
466 int have_sent_ICS_logon = 0;
467 int movesPerSession;
468 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
469 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
470 long timeControl_2; /* [AS] Allow separate time controls */
471 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
472 long timeRemaining[2][MAX_MOVES];
473 int matchGame = 0, nextGame = 0, roundNr = 0;
474 Boolean waitingForGame = FALSE;
475 TimeMark programStartTime, pauseStart;
476 char ics_handle[MSG_SIZ];
477 int have_set_title = 0;
478
479 /* animateTraining preserves the state of appData.animate
480  * when Training mode is activated. This allows the
481  * response to be animated when appData.animate == TRUE and
482  * appData.animateDragging == TRUE.
483  */
484 Boolean animateTraining;
485
486 GameInfo gameInfo;
487
488 AppData appData;
489
490 Board boards[MAX_MOVES];
491 /* [HGM] Following 7 needed for accurate legality tests: */
492 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
493 signed char  initialRights[BOARD_FILES];
494 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
495 int   initialRulePlies, FENrulePlies;
496 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 int loadFlag = 0;
498 Boolean shuffleOpenings;
499 int mute; // mute all sounds
500
501 // [HGM] vari: next 12 to save and restore variations
502 #define MAX_VARIATIONS 10
503 int framePtr = MAX_MOVES-1; // points to free stack entry
504 int storedGames = 0;
505 int savedFirst[MAX_VARIATIONS];
506 int savedLast[MAX_VARIATIONS];
507 int savedFramePtr[MAX_VARIATIONS];
508 char *savedDetails[MAX_VARIATIONS];
509 ChessMove savedResult[MAX_VARIATIONS];
510
511 void PushTail P((int firstMove, int lastMove));
512 Boolean PopTail P((Boolean annotate));
513 void PushInner P((int firstMove, int lastMove));
514 void PopInner P((Boolean annotate));
515 void CleanupTail P((void));
516
517 ChessSquare  FIDEArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackBishop, BlackKnight, BlackRook }
522 };
523
524 ChessSquare twoKingsArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
526         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
528         BlackKing, BlackKing, BlackKnight, BlackRook }
529 };
530
531 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
533         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
534     { BlackRook, BlackMan, BlackBishop, BlackQueen,
535         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
536 };
537
538 ChessSquare SpartanArray[2][BOARD_FILES] = {
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
542         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
543 };
544
545 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
546     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
547         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
548     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
549         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
550 };
551
552 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
554         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
556         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 };
558
559 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
560     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
561         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackMan, BlackFerz,
563         BlackKing, BlackMan, BlackKnight, BlackRook }
564 };
565
566
567 #if (BOARD_FILES>=10)
568 ChessSquare ShogiArray[2][BOARD_FILES] = {
569     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
570         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
571     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
572         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
573 };
574
575 ChessSquare XiangqiArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
577         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
579         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare CapablancaArray[2][BOARD_FILES] = {
583     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
584         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
586         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
587 };
588
589 ChessSquare GreatArray[2][BOARD_FILES] = {
590     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
591         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
592     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
593         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
594 };
595
596 ChessSquare JanusArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
598         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
599     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
600         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
601 };
602
603 ChessSquare GrandArray[2][BOARD_FILES] = {
604     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
605         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
606     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
607         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
608 };
609
610 #ifdef GOTHIC
611 ChessSquare GothicArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
613         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
615         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !GOTHIC
618 #define GothicArray CapablancaArray
619 #endif // !GOTHIC
620
621 #ifdef FALCON
622 ChessSquare FalconArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
624         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
626         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
627 };
628 #else // !FALCON
629 #define FalconArray CapablancaArray
630 #endif // !FALCON
631
632 #else // !(BOARD_FILES>=10)
633 #define XiangqiPosition FIDEArray
634 #define CapablancaArray FIDEArray
635 #define GothicArray FIDEArray
636 #define GreatArray FIDEArray
637 #endif // !(BOARD_FILES>=10)
638
639 #if (BOARD_FILES>=12)
640 ChessSquare CourierArray[2][BOARD_FILES] = {
641     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
642         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
643     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
644         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
645 };
646 #else // !(BOARD_FILES>=12)
647 #define CourierArray CapablancaArray
648 #endif // !(BOARD_FILES>=12)
649
650
651 Board initialPosition;
652
653
654 /* Convert str to a rating. Checks for special cases of "----",
655
656    "++++", etc. Also strips ()'s */
657 int
658 string_to_rating(str)
659   char *str;
660 {
661   while(*str && !isdigit(*str)) ++str;
662   if (!*str)
663     return 0;   /* One of the special "no rating" cases */
664   else
665     return atoi(str);
666 }
667
668 void
669 ClearProgramStats()
670 {
671     /* Init programStats */
672     programStats.movelist[0] = 0;
673     programStats.depth = 0;
674     programStats.nr_moves = 0;
675     programStats.moves_left = 0;
676     programStats.nodes = 0;
677     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
678     programStats.score = 0;
679     programStats.got_only_move = 0;
680     programStats.got_fail = 0;
681     programStats.line_is_book = 0;
682 }
683
684 void
685 CommonEngineInit()
686 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
687     if (appData.firstPlaysBlack) {
688         first.twoMachinesColor = "black\n";
689         second.twoMachinesColor = "white\n";
690     } else {
691         first.twoMachinesColor = "white\n";
692         second.twoMachinesColor = "black\n";
693     }
694
695     first.other = &second;
696     second.other = &first;
697
698     { float norm = 1;
699         if(appData.timeOddsMode) {
700             norm = appData.timeOdds[0];
701             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
702         }
703         first.timeOdds  = appData.timeOdds[0]/norm;
704         second.timeOdds = appData.timeOdds[1]/norm;
705     }
706
707     if(programVersion) free(programVersion);
708     if (appData.noChessProgram) {
709         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
710         sprintf(programVersion, "%s", PACKAGE_STRING);
711     } else {
712       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
713       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
714       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
715     }
716 }
717
718 void
719 UnloadEngine(ChessProgramState *cps)
720 {
721         /* Kill off first chess program */
722         if (cps->isr != NULL)
723           RemoveInputSource(cps->isr);
724         cps->isr = NULL;
725
726         if (cps->pr != NoProc) {
727             ExitAnalyzeMode();
728             DoSleep( appData.delayBeforeQuit );
729             SendToProgram("quit\n", cps);
730             DoSleep( appData.delayAfterQuit );
731             DestroyChildProcess(cps->pr, cps->useSigterm);
732         }
733         cps->pr = NoProc;
734         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
735 }
736
737 void
738 ClearOptions(ChessProgramState *cps)
739 {
740     int i;
741     cps->nrOptions = cps->comboCnt = 0;
742     for(i=0; i<MAX_OPTIONS; i++) {
743         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
744         cps->option[i].textValue = 0;
745     }
746 }
747
748 char *engineNames[] = {
749 "first",
750 "second"
751 };
752
753 void
754 InitEngine(ChessProgramState *cps, int n)
755 {   // [HGM] all engine initialiation put in a function that does one engine
756
757     ClearOptions(cps);
758
759     cps->which = engineNames[n];
760     cps->maybeThinking = FALSE;
761     cps->pr = NoProc;
762     cps->isr = NULL;
763     cps->sendTime = 2;
764     cps->sendDrawOffers = 1;
765
766     cps->program = appData.chessProgram[n];
767     cps->host = appData.host[n];
768     cps->dir = appData.directory[n];
769     cps->initString = appData.engInitString[n];
770     cps->computerString = appData.computerString[n];
771     cps->useSigint  = TRUE;
772     cps->useSigterm = TRUE;
773     cps->reuse = appData.reuse[n];
774     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
775     cps->useSetboard = FALSE;
776     cps->useSAN = FALSE;
777     cps->usePing = FALSE;
778     cps->lastPing = 0;
779     cps->lastPong = 0;
780     cps->usePlayother = FALSE;
781     cps->useColors = TRUE;
782     cps->useUsermove = FALSE;
783     cps->sendICS = FALSE;
784     cps->sendName = appData.icsActive;
785     cps->sdKludge = FALSE;
786     cps->stKludge = FALSE;
787     TidyProgramName(cps->program, cps->host, cps->tidy);
788     cps->matchWins = 0;
789     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
790     cps->analysisSupport = 2; /* detect */
791     cps->analyzing = FALSE;
792     cps->initDone = FALSE;
793
794     /* New features added by Tord: */
795     cps->useFEN960 = FALSE;
796     cps->useOOCastle = TRUE;
797     /* End of new features added by Tord. */
798     cps->fenOverride  = appData.fenOverride[n];
799
800     /* [HGM] time odds: set factor for each machine */
801     cps->timeOdds  = appData.timeOdds[n];
802
803     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
804     cps->accumulateTC = appData.accumulateTC[n];
805     cps->maxNrOfSessions = 1;
806
807     /* [HGM] debug */
808     cps->debug = FALSE;
809
810     cps->supportsNPS = UNKNOWN;
811     cps->memSize = FALSE;
812     cps->maxCores = FALSE;
813     cps->egtFormats[0] = NULLCHAR;
814
815     /* [HGM] options */
816     cps->optionSettings  = appData.engOptions[n];
817
818     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
819     cps->isUCI = appData.isUCI[n]; /* [AS] */
820     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
821
822     if (appData.protocolVersion[n] > PROTOVER
823         || appData.protocolVersion[n] < 1)
824       {
825         char buf[MSG_SIZ];
826         int len;
827
828         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
829                        appData.protocolVersion[n]);
830         if( (len > MSG_SIZ) && appData.debugMode )
831           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
832
833         DisplayFatalError(buf, 0, 2);
834       }
835     else
836       {
837         cps->protocolVersion = appData.protocolVersion[n];
838       }
839
840     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
841 }
842
843 ChessProgramState *savCps;
844
845 void
846 LoadEngine()
847 {
848     int i;
849     if(WaitForEngine(savCps, LoadEngine)) return;
850     CommonEngineInit(); // recalculate time odds
851     if(gameInfo.variant != StringToVariant(appData.variant)) {
852         // we changed variant when loading the engine; this forces us to reset
853         Reset(TRUE, savCps != &first);
854         EditGameEvent(); // for consistency with other path, as Reset changes mode
855     }
856     InitChessProgram(savCps, FALSE);
857     SendToProgram("force\n", savCps);
858     DisplayMessage("", "");
859     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
861     ThawUI();
862     SetGNUMode();
863 }
864
865 void
866 ReplaceEngine(ChessProgramState *cps, int n)
867 {
868     EditGameEvent();
869     UnloadEngine(cps);
870     appData.noChessProgram = FALSE;
871     appData.clockMode = TRUE;
872     InitEngine(cps, n);
873     UpdateLogos(TRUE);
874     if(n) return; // only startup first engine immediately; second can wait
875     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
876     LoadEngine();
877 }
878
879 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
880 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
881
882 static char resetOptions[] = 
883         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
884         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
885
886 void
887 Load(ChessProgramState *cps, int i)
888 {
889     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
890     if(engineLine[0]) { // an engine was selected from the combo box
891         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
892         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
893         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
894         ParseArgsFromString(buf);
895         SwapEngines(i);
896         ReplaceEngine(cps, i);
897         return;
898     }
899     p = engineName;
900     while(q = strchr(p, SLASH)) p = q+1;
901     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
902     if(engineDir[0] != NULLCHAR)
903         appData.directory[i] = engineDir;
904     else if(p != engineName) { // derive directory from engine path, when not given
905         p[-1] = 0;
906         appData.directory[i] = strdup(engineName);
907         p[-1] = SLASH;
908     } else appData.directory[i] = ".";
909     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
910     if(params[0]) {
911         snprintf(command, MSG_SIZ, "%s %s", p, params);
912         p = command;
913     }
914     appData.chessProgram[i] = strdup(p);
915     appData.isUCI[i] = isUCI;
916     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
917     appData.hasOwnBookUCI[i] = hasBook;
918     if(!nickName[0]) useNick = FALSE;
919     if(useNick) ASSIGN(appData.pgnName[i], nickName);
920     if(addToList) {
921         int len;
922         char quote;
923         q = firstChessProgramNames;
924         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
925         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
926         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
927                         quote, p, quote, appData.directory[i], 
928                         useNick ? " -fn \"" : "",
929                         useNick ? nickName : "",
930                         useNick ? "\"" : "",
931                         v1 ? " -firstProtocolVersion 1" : "",
932                         hasBook ? "" : " -fNoOwnBookUCI",
933                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
934                         storeVariant ? " -variant " : "",
935                         storeVariant ? VariantName(gameInfo.variant) : "");
936         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
937         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
938         if(q)   free(q);
939     }
940     ReplaceEngine(cps, i);
941 }
942
943 void
944 InitTimeControls()
945 {
946     int matched, min, sec;
947     /*
948      * Parse timeControl resource
949      */
950     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
951                           appData.movesPerSession)) {
952         char buf[MSG_SIZ];
953         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
954         DisplayFatalError(buf, 0, 2);
955     }
956
957     /*
958      * Parse searchTime resource
959      */
960     if (*appData.searchTime != NULLCHAR) {
961         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
962         if (matched == 1) {
963             searchTime = min * 60;
964         } else if (matched == 2) {
965             searchTime = min * 60 + sec;
966         } else {
967             char buf[MSG_SIZ];
968             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
969             DisplayFatalError(buf, 0, 2);
970         }
971     }
972 }
973
974 void
975 InitBackEnd1()
976 {
977
978     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
979     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
980
981     GetTimeMark(&programStartTime);
982     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
983     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
984
985     ClearProgramStats();
986     programStats.ok_to_send = 1;
987     programStats.seen_stat = 0;
988
989     /*
990      * Initialize game list
991      */
992     ListNew(&gameList);
993
994
995     /*
996      * Internet chess server status
997      */
998     if (appData.icsActive) {
999         appData.matchMode = FALSE;
1000         appData.matchGames = 0;
1001 #if ZIPPY
1002         appData.noChessProgram = !appData.zippyPlay;
1003 #else
1004         appData.zippyPlay = FALSE;
1005         appData.zippyTalk = FALSE;
1006         appData.noChessProgram = TRUE;
1007 #endif
1008         if (*appData.icsHelper != NULLCHAR) {
1009             appData.useTelnet = TRUE;
1010             appData.telnetProgram = appData.icsHelper;
1011         }
1012     } else {
1013         appData.zippyTalk = appData.zippyPlay = FALSE;
1014     }
1015
1016     /* [AS] Initialize pv info list [HGM] and game state */
1017     {
1018         int i, j;
1019
1020         for( i=0; i<=framePtr; i++ ) {
1021             pvInfoList[i].depth = -1;
1022             boards[i][EP_STATUS] = EP_NONE;
1023             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1024         }
1025     }
1026
1027     InitTimeControls();
1028
1029     /* [AS] Adjudication threshold */
1030     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1031
1032     InitEngine(&first, 0);
1033     InitEngine(&second, 1);
1034     CommonEngineInit();
1035
1036     pairing.which = "pairing"; // pairing engine
1037     pairing.pr = NoProc;
1038     pairing.isr = NULL;
1039     pairing.program = appData.pairingEngine;
1040     pairing.host = "localhost";
1041     pairing.dir = ".";
1042
1043     if (appData.icsActive) {
1044         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1045     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1046         appData.clockMode = FALSE;
1047         first.sendTime = second.sendTime = 0;
1048     }
1049
1050 #if ZIPPY
1051     /* Override some settings from environment variables, for backward
1052        compatibility.  Unfortunately it's not feasible to have the env
1053        vars just set defaults, at least in xboard.  Ugh.
1054     */
1055     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1056       ZippyInit();
1057     }
1058 #endif
1059
1060     if (!appData.icsActive) {
1061       char buf[MSG_SIZ];
1062       int len;
1063
1064       /* Check for variants that are supported only in ICS mode,
1065          or not at all.  Some that are accepted here nevertheless
1066          have bugs; see comments below.
1067       */
1068       VariantClass variant = StringToVariant(appData.variant);
1069       switch (variant) {
1070       case VariantBughouse:     /* need four players and two boards */
1071       case VariantKriegspiel:   /* need to hide pieces and move details */
1072         /* case VariantFischeRandom: (Fabien: moved below) */
1073         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1074         if( (len > MSG_SIZ) && appData.debugMode )
1075           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1076
1077         DisplayFatalError(buf, 0, 2);
1078         return;
1079
1080       case VariantUnknown:
1081       case VariantLoadable:
1082       case Variant29:
1083       case Variant30:
1084       case Variant31:
1085       case Variant32:
1086       case Variant33:
1087       case Variant34:
1088       case Variant35:
1089       case Variant36:
1090       default:
1091         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1092         if( (len > MSG_SIZ) && appData.debugMode )
1093           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094
1095         DisplayFatalError(buf, 0, 2);
1096         return;
1097
1098       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1099       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1100       case VariantGothic:     /* [HGM] should work */
1101       case VariantCapablanca: /* [HGM] should work */
1102       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1103       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1104       case VariantKnightmate: /* [HGM] should work */
1105       case VariantCylinder:   /* [HGM] untested */
1106       case VariantFalcon:     /* [HGM] untested */
1107       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1108                                  offboard interposition not understood */
1109       case VariantNormal:     /* definitely works! */
1110       case VariantWildCastle: /* pieces not automatically shuffled */
1111       case VariantNoCastle:   /* pieces not automatically shuffled */
1112       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1113       case VariantLosers:     /* should work except for win condition,
1114                                  and doesn't know captures are mandatory */
1115       case VariantSuicide:    /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantGiveaway:   /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantTwoKings:   /* should work */
1120       case VariantAtomic:     /* should work except for win condition */
1121       case Variant3Check:     /* should work except for win condition */
1122       case VariantShatranj:   /* should work except for all win conditions */
1123       case VariantMakruk:     /* should work except for draw countdown */
1124       case VariantBerolina:   /* might work if TestLegality is off */
1125       case VariantCapaRandom: /* should work */
1126       case VariantJanus:      /* should work */
1127       case VariantSuper:      /* experimental */
1128       case VariantGreat:      /* experimental, requires legality testing to be off */
1129       case VariantSChess:     /* S-Chess, should work */
1130       case VariantGrand:      /* should work */
1131       case VariantSpartan:    /* should work */
1132         break;
1133       }
1134     }
1135
1136 }
1137
1138 int NextIntegerFromString( char ** str, long * value )
1139 {
1140     int result = -1;
1141     char * s = *str;
1142
1143     while( *s == ' ' || *s == '\t' ) {
1144         s++;
1145     }
1146
1147     *value = 0;
1148
1149     if( *s >= '0' && *s <= '9' ) {
1150         while( *s >= '0' && *s <= '9' ) {
1151             *value = *value * 10 + (*s - '0');
1152             s++;
1153         }
1154
1155         result = 0;
1156     }
1157
1158     *str = s;
1159
1160     return result;
1161 }
1162
1163 int NextTimeControlFromString( char ** str, long * value )
1164 {
1165     long temp;
1166     int result = NextIntegerFromString( str, &temp );
1167
1168     if( result == 0 ) {
1169         *value = temp * 60; /* Minutes */
1170         if( **str == ':' ) {
1171             (*str)++;
1172             result = NextIntegerFromString( str, &temp );
1173             *value += temp; /* Seconds */
1174         }
1175     }
1176
1177     return result;
1178 }
1179
1180 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1181 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1182     int result = -1, type = 0; long temp, temp2;
1183
1184     if(**str != ':') return -1; // old params remain in force!
1185     (*str)++;
1186     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1187     if( NextIntegerFromString( str, &temp ) ) return -1;
1188     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1189
1190     if(**str != '/') {
1191         /* time only: incremental or sudden-death time control */
1192         if(**str == '+') { /* increment follows; read it */
1193             (*str)++;
1194             if(**str == '!') type = *(*str)++; // Bronstein TC
1195             if(result = NextIntegerFromString( str, &temp2)) return -1;
1196             *inc = temp2 * 1000;
1197             if(**str == '.') { // read fraction of increment
1198                 char *start = ++(*str);
1199                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1200                 temp2 *= 1000;
1201                 while(start++ < *str) temp2 /= 10;
1202                 *inc += temp2;
1203             }
1204         } else *inc = 0;
1205         *moves = 0; *tc = temp * 1000; *incType = type;
1206         return 0;
1207     }
1208
1209     (*str)++; /* classical time control */
1210     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1211
1212     if(result == 0) {
1213         *moves = temp;
1214         *tc    = temp2 * 1000;
1215         *inc   = 0;
1216         *incType = type;
1217     }
1218     return result;
1219 }
1220
1221 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1222 {   /* [HGM] get time to add from the multi-session time-control string */
1223     int incType, moves=1; /* kludge to force reading of first session */
1224     long time, increment;
1225     char *s = tcString;
1226
1227     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1228     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1229     do {
1230         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1231         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1232         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1233         if(movenr == -1) return time;    /* last move before new session     */
1234         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1235         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1236         if(!moves) return increment;     /* current session is incremental   */
1237         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1238     } while(movenr >= -1);               /* try again for next session       */
1239
1240     return 0; // no new time quota on this move
1241 }
1242
1243 int
1244 ParseTimeControl(tc, ti, mps)
1245      char *tc;
1246      float ti;
1247      int mps;
1248 {
1249   long tc1;
1250   long tc2;
1251   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1252   int min, sec=0;
1253
1254   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1255   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1256       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1257   if(ti > 0) {
1258
1259     if(mps)
1260       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1261     else 
1262       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1263   } else {
1264     if(mps)
1265       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1266     else 
1267       snprintf(buf, MSG_SIZ, ":%s", mytc);
1268   }
1269   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1270   
1271   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1272     return FALSE;
1273   }
1274
1275   if( *tc == '/' ) {
1276     /* Parse second time control */
1277     tc++;
1278
1279     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1280       return FALSE;
1281     }
1282
1283     if( tc2 == 0 ) {
1284       return FALSE;
1285     }
1286
1287     timeControl_2 = tc2 * 1000;
1288   }
1289   else {
1290     timeControl_2 = 0;
1291   }
1292
1293   if( tc1 == 0 ) {
1294     return FALSE;
1295   }
1296
1297   timeControl = tc1 * 1000;
1298
1299   if (ti >= 0) {
1300     timeIncrement = ti * 1000;  /* convert to ms */
1301     movesPerSession = 0;
1302   } else {
1303     timeIncrement = 0;
1304     movesPerSession = mps;
1305   }
1306   return TRUE;
1307 }
1308
1309 void
1310 InitBackEnd2()
1311 {
1312     if (appData.debugMode) {
1313         fprintf(debugFP, "%s\n", programVersion);
1314     }
1315
1316     set_cont_sequence(appData.wrapContSeq);
1317     if (appData.matchGames > 0) {
1318         appData.matchMode = TRUE;
1319     } else if (appData.matchMode) {
1320         appData.matchGames = 1;
1321     }
1322     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1323         appData.matchGames = appData.sameColorGames;
1324     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1325         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1326         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1327     }
1328     Reset(TRUE, FALSE);
1329     if (appData.noChessProgram || first.protocolVersion == 1) {
1330       InitBackEnd3();
1331     } else {
1332       /* kludge: allow timeout for initial "feature" commands */
1333       FreezeUI();
1334       DisplayMessage("", _("Starting chess program"));
1335       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1336     }
1337 }
1338
1339 int
1340 CalculateIndex(int index, int gameNr)
1341 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1342     int res;
1343     if(index > 0) return index; // fixed nmber
1344     if(index == 0) return 1;
1345     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1346     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1347     return res;
1348 }
1349
1350 int
1351 LoadGameOrPosition(int gameNr)
1352 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1353     if (*appData.loadGameFile != NULLCHAR) {
1354         if (!LoadGameFromFile(appData.loadGameFile,
1355                 CalculateIndex(appData.loadGameIndex, gameNr),
1356                               appData.loadGameFile, FALSE)) {
1357             DisplayFatalError(_("Bad game file"), 0, 1);
1358             return 0;
1359         }
1360     } else if (*appData.loadPositionFile != NULLCHAR) {
1361         if (!LoadPositionFromFile(appData.loadPositionFile,
1362                 CalculateIndex(appData.loadPositionIndex, gameNr),
1363                                   appData.loadPositionFile)) {
1364             DisplayFatalError(_("Bad position file"), 0, 1);
1365             return 0;
1366         }
1367     }
1368     return 1;
1369 }
1370
1371 void
1372 ReserveGame(int gameNr, char resChar)
1373 {
1374     FILE *tf = fopen(appData.tourneyFile, "r+");
1375     char *p, *q, c, buf[MSG_SIZ];
1376     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1377     safeStrCpy(buf, lastMsg, MSG_SIZ);
1378     DisplayMessage(_("Pick new game"), "");
1379     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1380     ParseArgsFromFile(tf);
1381     p = q = appData.results;
1382     if(appData.debugMode) {
1383       char *r = appData.participants;
1384       fprintf(debugFP, "results = '%s'\n", p);
1385       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1386       fprintf(debugFP, "\n");
1387     }
1388     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1389     nextGame = q - p;
1390     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1391     safeStrCpy(q, p, strlen(p) + 2);
1392     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1393     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1394     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1395         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1396         q[nextGame] = '*';
1397     }
1398     fseek(tf, -(strlen(p)+4), SEEK_END);
1399     c = fgetc(tf);
1400     if(c != '"') // depending on DOS or Unix line endings we can be one off
1401          fseek(tf, -(strlen(p)+2), SEEK_END);
1402     else fseek(tf, -(strlen(p)+3), SEEK_END);
1403     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1404     DisplayMessage(buf, "");
1405     free(p); appData.results = q;
1406     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1407        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1408         UnloadEngine(&first);  // next game belongs to other pairing;
1409         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1410     }
1411 }
1412
1413 void
1414 MatchEvent(int mode)
1415 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1416         int dummy;
1417         if(matchMode) { // already in match mode: switch it off
1418             abortMatch = TRUE;
1419             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1420             return;
1421         }
1422 //      if(gameMode != BeginningOfGame) {
1423 //          DisplayError(_("You can only start a match from the initial position."), 0);
1424 //          return;
1425 //      }
1426         abortMatch = FALSE;
1427         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1428         /* Set up machine vs. machine match */
1429         nextGame = 0;
1430         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1431         if(appData.tourneyFile[0]) {
1432             ReserveGame(-1, 0);
1433             if(nextGame > appData.matchGames) {
1434                 char buf[MSG_SIZ];
1435                 if(strchr(appData.results, '*') == NULL) {
1436                     FILE *f;
1437                     appData.tourneyCycles++;
1438                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1439                         fclose(f);
1440                         NextTourneyGame(-1, &dummy);
1441                         ReserveGame(-1, 0);
1442                         if(nextGame <= appData.matchGames) {
1443                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1444                             matchMode = mode;
1445                             ScheduleDelayedEvent(NextMatchGame, 10000);
1446                             return;
1447                         }
1448                     }
1449                 }
1450                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1451                 DisplayError(buf, 0);
1452                 appData.tourneyFile[0] = 0;
1453                 return;
1454             }
1455         } else
1456         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1457             DisplayFatalError(_("Can't have a match with no chess programs"),
1458                               0, 2);
1459             return;
1460         }
1461         matchMode = mode;
1462         matchGame = roundNr = 1;
1463         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1464         NextMatchGame();
1465 }
1466
1467 void
1468 InitBackEnd3 P((void))
1469 {
1470     GameMode initialMode;
1471     char buf[MSG_SIZ];
1472     int err, len;
1473
1474     InitChessProgram(&first, startedFromSetupPosition);
1475
1476     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1477         free(programVersion);
1478         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1479         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1480     }
1481
1482     if (appData.icsActive) {
1483 #ifdef WIN32
1484         /* [DM] Make a console window if needed [HGM] merged ifs */
1485         ConsoleCreate();
1486 #endif
1487         err = establish();
1488         if (err != 0)
1489           {
1490             if (*appData.icsCommPort != NULLCHAR)
1491               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1492                              appData.icsCommPort);
1493             else
1494               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1495                         appData.icsHost, appData.icsPort);
1496
1497             if( (len > MSG_SIZ) && appData.debugMode )
1498               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1499
1500             DisplayFatalError(buf, err, 1);
1501             return;
1502         }
1503         SetICSMode();
1504         telnetISR =
1505           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1506         fromUserISR =
1507           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1508         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1509             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1510     } else if (appData.noChessProgram) {
1511         SetNCPMode();
1512     } else {
1513         SetGNUMode();
1514     }
1515
1516     if (*appData.cmailGameName != NULLCHAR) {
1517         SetCmailMode();
1518         OpenLoopback(&cmailPR);
1519         cmailISR =
1520           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1521     }
1522
1523     ThawUI();
1524     DisplayMessage("", "");
1525     if (StrCaseCmp(appData.initialMode, "") == 0) {
1526       initialMode = BeginningOfGame;
1527       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1528         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1529         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1530         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1531         ModeHighlight();
1532       }
1533     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1534       initialMode = TwoMachinesPlay;
1535     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1536       initialMode = AnalyzeFile;
1537     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1538       initialMode = AnalyzeMode;
1539     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1540       initialMode = MachinePlaysWhite;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1542       initialMode = MachinePlaysBlack;
1543     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1544       initialMode = EditGame;
1545     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1546       initialMode = EditPosition;
1547     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1548       initialMode = Training;
1549     } else {
1550       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1551       if( (len > MSG_SIZ) && appData.debugMode )
1552         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1553
1554       DisplayFatalError(buf, 0, 2);
1555       return;
1556     }
1557
1558     if (appData.matchMode) {
1559         if(appData.tourneyFile[0]) { // start tourney from command line
1560             FILE *f;
1561             if(f = fopen(appData.tourneyFile, "r")) {
1562                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1563                 fclose(f);
1564                 appData.clockMode = TRUE;
1565                 SetGNUMode();
1566             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1567         }
1568         MatchEvent(TRUE);
1569     } else if (*appData.cmailGameName != NULLCHAR) {
1570         /* Set up cmail mode */
1571         ReloadCmailMsgEvent(TRUE);
1572     } else {
1573         /* Set up other modes */
1574         if (initialMode == AnalyzeFile) {
1575           if (*appData.loadGameFile == NULLCHAR) {
1576             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1577             return;
1578           }
1579         }
1580         if (*appData.loadGameFile != NULLCHAR) {
1581             (void) LoadGameFromFile(appData.loadGameFile,
1582                                     appData.loadGameIndex,
1583                                     appData.loadGameFile, TRUE);
1584         } else if (*appData.loadPositionFile != NULLCHAR) {
1585             (void) LoadPositionFromFile(appData.loadPositionFile,
1586                                         appData.loadPositionIndex,
1587                                         appData.loadPositionFile);
1588             /* [HGM] try to make self-starting even after FEN load */
1589             /* to allow automatic setup of fairy variants with wtm */
1590             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1591                 gameMode = BeginningOfGame;
1592                 setboardSpoiledMachineBlack = 1;
1593             }
1594             /* [HGM] loadPos: make that every new game uses the setup */
1595             /* from file as long as we do not switch variant          */
1596             if(!blackPlaysFirst) {
1597                 startedFromPositionFile = TRUE;
1598                 CopyBoard(filePosition, boards[0]);
1599             }
1600         }
1601         if (initialMode == AnalyzeMode) {
1602           if (appData.noChessProgram) {
1603             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1604             return;
1605           }
1606           if (appData.icsActive) {
1607             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1608             return;
1609           }
1610           AnalyzeModeEvent();
1611         } else if (initialMode == AnalyzeFile) {
1612           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1613           ShowThinkingEvent();
1614           AnalyzeFileEvent();
1615           AnalysisPeriodicEvent(1);
1616         } else if (initialMode == MachinePlaysWhite) {
1617           if (appData.noChessProgram) {
1618             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1619                               0, 2);
1620             return;
1621           }
1622           if (appData.icsActive) {
1623             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1624                               0, 2);
1625             return;
1626           }
1627           MachineWhiteEvent();
1628         } else if (initialMode == MachinePlaysBlack) {
1629           if (appData.noChessProgram) {
1630             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1631                               0, 2);
1632             return;
1633           }
1634           if (appData.icsActive) {
1635             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1636                               0, 2);
1637             return;
1638           }
1639           MachineBlackEvent();
1640         } else if (initialMode == TwoMachinesPlay) {
1641           if (appData.noChessProgram) {
1642             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1643                               0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1648                               0, 2);
1649             return;
1650           }
1651           TwoMachinesEvent();
1652         } else if (initialMode == EditGame) {
1653           EditGameEvent();
1654         } else if (initialMode == EditPosition) {
1655           EditPositionEvent();
1656         } else if (initialMode == Training) {
1657           if (*appData.loadGameFile == NULLCHAR) {
1658             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1659             return;
1660           }
1661           TrainingEvent();
1662         }
1663     }
1664 }
1665
1666 /*
1667  * Establish will establish a contact to a remote host.port.
1668  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1669  *  used to talk to the host.
1670  * Returns 0 if okay, error code if not.
1671  */
1672 int
1673 establish()
1674 {
1675     char buf[MSG_SIZ];
1676
1677     if (*appData.icsCommPort != NULLCHAR) {
1678         /* Talk to the host through a serial comm port */
1679         return OpenCommPort(appData.icsCommPort, &icsPR);
1680
1681     } else if (*appData.gateway != NULLCHAR) {
1682         if (*appData.remoteShell == NULLCHAR) {
1683             /* Use the rcmd protocol to run telnet program on a gateway host */
1684             snprintf(buf, sizeof(buf), "%s %s %s",
1685                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1686             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1687
1688         } else {
1689             /* Use the rsh program to run telnet program on a gateway host */
1690             if (*appData.remoteUser == NULLCHAR) {
1691                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1692                         appData.gateway, appData.telnetProgram,
1693                         appData.icsHost, appData.icsPort);
1694             } else {
1695                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1696                         appData.remoteShell, appData.gateway,
1697                         appData.remoteUser, appData.telnetProgram,
1698                         appData.icsHost, appData.icsPort);
1699             }
1700             return StartChildProcess(buf, "", &icsPR);
1701
1702         }
1703     } else if (appData.useTelnet) {
1704         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1705
1706     } else {
1707         /* TCP socket interface differs somewhat between
1708            Unix and NT; handle details in the front end.
1709            */
1710         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1711     }
1712 }
1713
1714 void EscapeExpand(char *p, char *q)
1715 {       // [HGM] initstring: routine to shape up string arguments
1716         while(*p++ = *q++) if(p[-1] == '\\')
1717             switch(*q++) {
1718                 case 'n': p[-1] = '\n'; break;
1719                 case 'r': p[-1] = '\r'; break;
1720                 case 't': p[-1] = '\t'; break;
1721                 case '\\': p[-1] = '\\'; break;
1722                 case 0: *p = 0; return;
1723                 default: p[-1] = q[-1]; break;
1724             }
1725 }
1726
1727 void
1728 show_bytes(fp, buf, count)
1729      FILE *fp;
1730      char *buf;
1731      int count;
1732 {
1733     while (count--) {
1734         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1735             fprintf(fp, "\\%03o", *buf & 0xff);
1736         } else {
1737             putc(*buf, fp);
1738         }
1739         buf++;
1740     }
1741     fflush(fp);
1742 }
1743
1744 /* Returns an errno value */
1745 int
1746 OutputMaybeTelnet(pr, message, count, outError)
1747      ProcRef pr;
1748      char *message;
1749      int count;
1750      int *outError;
1751 {
1752     char buf[8192], *p, *q, *buflim;
1753     int left, newcount, outcount;
1754
1755     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1756         *appData.gateway != NULLCHAR) {
1757         if (appData.debugMode) {
1758             fprintf(debugFP, ">ICS: ");
1759             show_bytes(debugFP, message, count);
1760             fprintf(debugFP, "\n");
1761         }
1762         return OutputToProcess(pr, message, count, outError);
1763     }
1764
1765     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1766     p = message;
1767     q = buf;
1768     left = count;
1769     newcount = 0;
1770     while (left) {
1771         if (q >= buflim) {
1772             if (appData.debugMode) {
1773                 fprintf(debugFP, ">ICS: ");
1774                 show_bytes(debugFP, buf, newcount);
1775                 fprintf(debugFP, "\n");
1776             }
1777             outcount = OutputToProcess(pr, buf, newcount, outError);
1778             if (outcount < newcount) return -1; /* to be sure */
1779             q = buf;
1780             newcount = 0;
1781         }
1782         if (*p == '\n') {
1783             *q++ = '\r';
1784             newcount++;
1785         } else if (((unsigned char) *p) == TN_IAC) {
1786             *q++ = (char) TN_IAC;
1787             newcount ++;
1788         }
1789         *q++ = *p++;
1790         newcount++;
1791         left--;
1792     }
1793     if (appData.debugMode) {
1794         fprintf(debugFP, ">ICS: ");
1795         show_bytes(debugFP, buf, newcount);
1796         fprintf(debugFP, "\n");
1797     }
1798     outcount = OutputToProcess(pr, buf, newcount, outError);
1799     if (outcount < newcount) return -1; /* to be sure */
1800     return count;
1801 }
1802
1803 void
1804 read_from_player(isr, closure, message, count, error)
1805      InputSourceRef isr;
1806      VOIDSTAR closure;
1807      char *message;
1808      int count;
1809      int error;
1810 {
1811     int outError, outCount;
1812     static int gotEof = 0;
1813
1814     /* Pass data read from player on to ICS */
1815     if (count > 0) {
1816         gotEof = 0;
1817         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1818         if (outCount < count) {
1819             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1820         }
1821     } else if (count < 0) {
1822         RemoveInputSource(isr);
1823         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1824     } else if (gotEof++ > 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1827     }
1828 }
1829
1830 void
1831 KeepAlive()
1832 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1833     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1834     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1835     SendToICS("date\n");
1836     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1837 }
1838
1839 /* added routine for printf style output to ics */
1840 void ics_printf(char *format, ...)
1841 {
1842     char buffer[MSG_SIZ];
1843     va_list args;
1844
1845     va_start(args, format);
1846     vsnprintf(buffer, sizeof(buffer), format, args);
1847     buffer[sizeof(buffer)-1] = '\0';
1848     SendToICS(buffer);
1849     va_end(args);
1850 }
1851
1852 void
1853 SendToICS(s)
1854      char *s;
1855 {
1856     int count, outCount, outError;
1857
1858     if (icsPR == NULL) return;
1859
1860     count = strlen(s);
1861     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1862     if (outCount < count) {
1863         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1864     }
1865 }
1866
1867 /* This is used for sending logon scripts to the ICS. Sending
1868    without a delay causes problems when using timestamp on ICC
1869    (at least on my machine). */
1870 void
1871 SendToICSDelayed(s,msdelay)
1872      char *s;
1873      long msdelay;
1874 {
1875     int count, outCount, outError;
1876
1877     if (icsPR == NULL) return;
1878
1879     count = strlen(s);
1880     if (appData.debugMode) {
1881         fprintf(debugFP, ">ICS: ");
1882         show_bytes(debugFP, s, count);
1883         fprintf(debugFP, "\n");
1884     }
1885     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1886                                       msdelay);
1887     if (outCount < count) {
1888         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1889     }
1890 }
1891
1892
1893 /* Remove all highlighting escape sequences in s
1894    Also deletes any suffix starting with '('
1895    */
1896 char *
1897 StripHighlightAndTitle(s)
1898      char *s;
1899 {
1900     static char retbuf[MSG_SIZ];
1901     char *p = retbuf;
1902
1903     while (*s != NULLCHAR) {
1904         while (*s == '\033') {
1905             while (*s != NULLCHAR && !isalpha(*s)) s++;
1906             if (*s != NULLCHAR) s++;
1907         }
1908         while (*s != NULLCHAR && *s != '\033') {
1909             if (*s == '(' || *s == '[') {
1910                 *p = NULLCHAR;
1911                 return retbuf;
1912             }
1913             *p++ = *s++;
1914         }
1915     }
1916     *p = NULLCHAR;
1917     return retbuf;
1918 }
1919
1920 /* Remove all highlighting escape sequences in s */
1921 char *
1922 StripHighlight(s)
1923      char *s;
1924 {
1925     static char retbuf[MSG_SIZ];
1926     char *p = retbuf;
1927
1928     while (*s != NULLCHAR) {
1929         while (*s == '\033') {
1930             while (*s != NULLCHAR && !isalpha(*s)) s++;
1931             if (*s != NULLCHAR) s++;
1932         }
1933         while (*s != NULLCHAR && *s != '\033') {
1934             *p++ = *s++;
1935         }
1936     }
1937     *p = NULLCHAR;
1938     return retbuf;
1939 }
1940
1941 char *variantNames[] = VARIANT_NAMES;
1942 char *
1943 VariantName(v)
1944      VariantClass v;
1945 {
1946     return variantNames[v];
1947 }
1948
1949
1950 /* Identify a variant from the strings the chess servers use or the
1951    PGN Variant tag names we use. */
1952 VariantClass
1953 StringToVariant(e)
1954      char *e;
1955 {
1956     char *p;
1957     int wnum = -1;
1958     VariantClass v = VariantNormal;
1959     int i, found = FALSE;
1960     char buf[MSG_SIZ];
1961     int len;
1962
1963     if (!e) return v;
1964
1965     /* [HGM] skip over optional board-size prefixes */
1966     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1967         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1968         while( *e++ != '_');
1969     }
1970
1971     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1972         v = VariantNormal;
1973         found = TRUE;
1974     } else
1975     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1976       if (StrCaseStr(e, variantNames[i])) {
1977         v = (VariantClass) i;
1978         found = TRUE;
1979         break;
1980       }
1981     }
1982
1983     if (!found) {
1984       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1985           || StrCaseStr(e, "wild/fr")
1986           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1987         v = VariantFischeRandom;
1988       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1989                  (i = 1, p = StrCaseStr(e, "w"))) {
1990         p += i;
1991         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1992         if (isdigit(*p)) {
1993           wnum = atoi(p);
1994         } else {
1995           wnum = -1;
1996         }
1997         switch (wnum) {
1998         case 0: /* FICS only, actually */
1999         case 1:
2000           /* Castling legal even if K starts on d-file */
2001           v = VariantWildCastle;
2002           break;
2003         case 2:
2004         case 3:
2005         case 4:
2006           /* Castling illegal even if K & R happen to start in
2007              normal positions. */
2008           v = VariantNoCastle;
2009           break;
2010         case 5:
2011         case 7:
2012         case 8:
2013         case 10:
2014         case 11:
2015         case 12:
2016         case 13:
2017         case 14:
2018         case 15:
2019         case 18:
2020         case 19:
2021           /* Castling legal iff K & R start in normal positions */
2022           v = VariantNormal;
2023           break;
2024         case 6:
2025         case 20:
2026         case 21:
2027           /* Special wilds for position setup; unclear what to do here */
2028           v = VariantLoadable;
2029           break;
2030         case 9:
2031           /* Bizarre ICC game */
2032           v = VariantTwoKings;
2033           break;
2034         case 16:
2035           v = VariantKriegspiel;
2036           break;
2037         case 17:
2038           v = VariantLosers;
2039           break;
2040         case 22:
2041           v = VariantFischeRandom;
2042           break;
2043         case 23:
2044           v = VariantCrazyhouse;
2045           break;
2046         case 24:
2047           v = VariantBughouse;
2048           break;
2049         case 25:
2050           v = Variant3Check;
2051           break;
2052         case 26:
2053           /* Not quite the same as FICS suicide! */
2054           v = VariantGiveaway;
2055           break;
2056         case 27:
2057           v = VariantAtomic;
2058           break;
2059         case 28:
2060           v = VariantShatranj;
2061           break;
2062
2063         /* Temporary names for future ICC types.  The name *will* change in
2064            the next xboard/WinBoard release after ICC defines it. */
2065         case 29:
2066           v = Variant29;
2067           break;
2068         case 30:
2069           v = Variant30;
2070           break;
2071         case 31:
2072           v = Variant31;
2073           break;
2074         case 32:
2075           v = Variant32;
2076           break;
2077         case 33:
2078           v = Variant33;
2079           break;
2080         case 34:
2081           v = Variant34;
2082           break;
2083         case 35:
2084           v = Variant35;
2085           break;
2086         case 36:
2087           v = Variant36;
2088           break;
2089         case 37:
2090           v = VariantShogi;
2091           break;
2092         case 38:
2093           v = VariantXiangqi;
2094           break;
2095         case 39:
2096           v = VariantCourier;
2097           break;
2098         case 40:
2099           v = VariantGothic;
2100           break;
2101         case 41:
2102           v = VariantCapablanca;
2103           break;
2104         case 42:
2105           v = VariantKnightmate;
2106           break;
2107         case 43:
2108           v = VariantFairy;
2109           break;
2110         case 44:
2111           v = VariantCylinder;
2112           break;
2113         case 45:
2114           v = VariantFalcon;
2115           break;
2116         case 46:
2117           v = VariantCapaRandom;
2118           break;
2119         case 47:
2120           v = VariantBerolina;
2121           break;
2122         case 48:
2123           v = VariantJanus;
2124           break;
2125         case 49:
2126           v = VariantSuper;
2127           break;
2128         case 50:
2129           v = VariantGreat;
2130           break;
2131         case -1:
2132           /* Found "wild" or "w" in the string but no number;
2133              must assume it's normal chess. */
2134           v = VariantNormal;
2135           break;
2136         default:
2137           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2138           if( (len > MSG_SIZ) && appData.debugMode )
2139             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2140
2141           DisplayError(buf, 0);
2142           v = VariantUnknown;
2143           break;
2144         }
2145       }
2146     }
2147     if (appData.debugMode) {
2148       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2149               e, wnum, VariantName(v));
2150     }
2151     return v;
2152 }
2153
2154 static int leftover_start = 0, leftover_len = 0;
2155 char star_match[STAR_MATCH_N][MSG_SIZ];
2156
2157 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2158    advance *index beyond it, and set leftover_start to the new value of
2159    *index; else return FALSE.  If pattern contains the character '*', it
2160    matches any sequence of characters not containing '\r', '\n', or the
2161    character following the '*' (if any), and the matched sequence(s) are
2162    copied into star_match.
2163    */
2164 int
2165 looking_at(buf, index, pattern)
2166      char *buf;
2167      int *index;
2168      char *pattern;
2169 {
2170     char *bufp = &buf[*index], *patternp = pattern;
2171     int star_count = 0;
2172     char *matchp = star_match[0];
2173
2174     for (;;) {
2175         if (*patternp == NULLCHAR) {
2176             *index = leftover_start = bufp - buf;
2177             *matchp = NULLCHAR;
2178             return TRUE;
2179         }
2180         if (*bufp == NULLCHAR) return FALSE;
2181         if (*patternp == '*') {
2182             if (*bufp == *(patternp + 1)) {
2183                 *matchp = NULLCHAR;
2184                 matchp = star_match[++star_count];
2185                 patternp += 2;
2186                 bufp++;
2187                 continue;
2188             } else if (*bufp == '\n' || *bufp == '\r') {
2189                 patternp++;
2190                 if (*patternp == NULLCHAR)
2191                   continue;
2192                 else
2193                   return FALSE;
2194             } else {
2195                 *matchp++ = *bufp++;
2196                 continue;
2197             }
2198         }
2199         if (*patternp != *bufp) return FALSE;
2200         patternp++;
2201         bufp++;
2202     }
2203 }
2204
2205 void
2206 SendToPlayer(data, length)
2207      char *data;
2208      int length;
2209 {
2210     int error, outCount;
2211     outCount = OutputToProcess(NoProc, data, length, &error);
2212     if (outCount < length) {
2213         DisplayFatalError(_("Error writing to display"), error, 1);
2214     }
2215 }
2216
2217 void
2218 PackHolding(packed, holding)
2219      char packed[];
2220      char *holding;
2221 {
2222     char *p = holding;
2223     char *q = packed;
2224     int runlength = 0;
2225     int curr = 9999;
2226     do {
2227         if (*p == curr) {
2228             runlength++;
2229         } else {
2230             switch (runlength) {
2231               case 0:
2232                 break;
2233               case 1:
2234                 *q++ = curr;
2235                 break;
2236               case 2:
2237                 *q++ = curr;
2238                 *q++ = curr;
2239                 break;
2240               default:
2241                 sprintf(q, "%d", runlength);
2242                 while (*q) q++;
2243                 *q++ = curr;
2244                 break;
2245             }
2246             runlength = 1;
2247             curr = *p;
2248         }
2249     } while (*p++);
2250     *q = NULLCHAR;
2251 }
2252
2253 /* Telnet protocol requests from the front end */
2254 void
2255 TelnetRequest(ddww, option)
2256      unsigned char ddww, option;
2257 {
2258     unsigned char msg[3];
2259     int outCount, outError;
2260
2261     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2262
2263     if (appData.debugMode) {
2264         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2265         switch (ddww) {
2266           case TN_DO:
2267             ddwwStr = "DO";
2268             break;
2269           case TN_DONT:
2270             ddwwStr = "DONT";
2271             break;
2272           case TN_WILL:
2273             ddwwStr = "WILL";
2274             break;
2275           case TN_WONT:
2276             ddwwStr = "WONT";
2277             break;
2278           default:
2279             ddwwStr = buf1;
2280             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2281             break;
2282         }
2283         switch (option) {
2284           case TN_ECHO:
2285             optionStr = "ECHO";
2286             break;
2287           default:
2288             optionStr = buf2;
2289             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2290             break;
2291         }
2292         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2293     }
2294     msg[0] = TN_IAC;
2295     msg[1] = ddww;
2296     msg[2] = option;
2297     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2298     if (outCount < 3) {
2299         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2300     }
2301 }
2302
2303 void
2304 DoEcho()
2305 {
2306     if (!appData.icsActive) return;
2307     TelnetRequest(TN_DO, TN_ECHO);
2308 }
2309
2310 void
2311 DontEcho()
2312 {
2313     if (!appData.icsActive) return;
2314     TelnetRequest(TN_DONT, TN_ECHO);
2315 }
2316
2317 void
2318 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2319 {
2320     /* put the holdings sent to us by the server on the board holdings area */
2321     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2322     char p;
2323     ChessSquare piece;
2324
2325     if(gameInfo.holdingsWidth < 2)  return;
2326     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2327         return; // prevent overwriting by pre-board holdings
2328
2329     if( (int)lowestPiece >= BlackPawn ) {
2330         holdingsColumn = 0;
2331         countsColumn = 1;
2332         holdingsStartRow = BOARD_HEIGHT-1;
2333         direction = -1;
2334     } else {
2335         holdingsColumn = BOARD_WIDTH-1;
2336         countsColumn = BOARD_WIDTH-2;
2337         holdingsStartRow = 0;
2338         direction = 1;
2339     }
2340
2341     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2342         board[i][holdingsColumn] = EmptySquare;
2343         board[i][countsColumn]   = (ChessSquare) 0;
2344     }
2345     while( (p=*holdings++) != NULLCHAR ) {
2346         piece = CharToPiece( ToUpper(p) );
2347         if(piece == EmptySquare) continue;
2348         /*j = (int) piece - (int) WhitePawn;*/
2349         j = PieceToNumber(piece);
2350         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2351         if(j < 0) continue;               /* should not happen */
2352         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2353         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2354         board[holdingsStartRow+j*direction][countsColumn]++;
2355     }
2356 }
2357
2358
2359 void
2360 VariantSwitch(Board board, VariantClass newVariant)
2361 {
2362    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2363    static Board oldBoard;
2364
2365    startedFromPositionFile = FALSE;
2366    if(gameInfo.variant == newVariant) return;
2367
2368    /* [HGM] This routine is called each time an assignment is made to
2369     * gameInfo.variant during a game, to make sure the board sizes
2370     * are set to match the new variant. If that means adding or deleting
2371     * holdings, we shift the playing board accordingly
2372     * This kludge is needed because in ICS observe mode, we get boards
2373     * of an ongoing game without knowing the variant, and learn about the
2374     * latter only later. This can be because of the move list we requested,
2375     * in which case the game history is refilled from the beginning anyway,
2376     * but also when receiving holdings of a crazyhouse game. In the latter
2377     * case we want to add those holdings to the already received position.
2378     */
2379
2380
2381    if (appData.debugMode) {
2382      fprintf(debugFP, "Switch board from %s to %s\n",
2383              VariantName(gameInfo.variant), VariantName(newVariant));
2384      setbuf(debugFP, NULL);
2385    }
2386    shuffleOpenings = 0;       /* [HGM] shuffle */
2387    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2388    switch(newVariant)
2389      {
2390      case VariantShogi:
2391        newWidth = 9;  newHeight = 9;
2392        gameInfo.holdingsSize = 7;
2393      case VariantBughouse:
2394      case VariantCrazyhouse:
2395        newHoldingsWidth = 2; break;
2396      case VariantGreat:
2397        newWidth = 10;
2398      case VariantSuper:
2399        newHoldingsWidth = 2;
2400        gameInfo.holdingsSize = 8;
2401        break;
2402      case VariantGothic:
2403      case VariantCapablanca:
2404      case VariantCapaRandom:
2405        newWidth = 10;
2406      default:
2407        newHoldingsWidth = gameInfo.holdingsSize = 0;
2408      };
2409
2410    if(newWidth  != gameInfo.boardWidth  ||
2411       newHeight != gameInfo.boardHeight ||
2412       newHoldingsWidth != gameInfo.holdingsWidth ) {
2413
2414      /* shift position to new playing area, if needed */
2415      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2416        for(i=0; i<BOARD_HEIGHT; i++)
2417          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2418            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2419              board[i][j];
2420        for(i=0; i<newHeight; i++) {
2421          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2422          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2423        }
2424      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2425        for(i=0; i<BOARD_HEIGHT; i++)
2426          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2427            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2428              board[i][j];
2429      }
2430      gameInfo.boardWidth  = newWidth;
2431      gameInfo.boardHeight = newHeight;
2432      gameInfo.holdingsWidth = newHoldingsWidth;
2433      gameInfo.variant = newVariant;
2434      InitDrawingSizes(-2, 0);
2435    } else gameInfo.variant = newVariant;
2436    CopyBoard(oldBoard, board);   // remember correctly formatted board
2437      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2438    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2439 }
2440
2441 static int loggedOn = FALSE;
2442
2443 /*-- Game start info cache: --*/
2444 int gs_gamenum;
2445 char gs_kind[MSG_SIZ];
2446 static char player1Name[128] = "";
2447 static char player2Name[128] = "";
2448 static char cont_seq[] = "\n\\   ";
2449 static int player1Rating = -1;
2450 static int player2Rating = -1;
2451 /*----------------------------*/
2452
2453 ColorClass curColor = ColorNormal;
2454 int suppressKibitz = 0;
2455
2456 // [HGM] seekgraph
2457 Boolean soughtPending = FALSE;
2458 Boolean seekGraphUp;
2459 #define MAX_SEEK_ADS 200
2460 #define SQUARE 0x80
2461 char *seekAdList[MAX_SEEK_ADS];
2462 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2463 float tcList[MAX_SEEK_ADS];
2464 char colorList[MAX_SEEK_ADS];
2465 int nrOfSeekAds = 0;
2466 int minRating = 1010, maxRating = 2800;
2467 int hMargin = 10, vMargin = 20, h, w;
2468 extern int squareSize, lineGap;
2469
2470 void
2471 PlotSeekAd(int i)
2472 {
2473         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2474         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2475         if(r < minRating+100 && r >=0 ) r = minRating+100;
2476         if(r > maxRating) r = maxRating;
2477         if(tc < 1.) tc = 1.;
2478         if(tc > 95.) tc = 95.;
2479         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2480         y = ((double)r - minRating)/(maxRating - minRating)
2481             * (h-vMargin-squareSize/8-1) + vMargin;
2482         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2483         if(strstr(seekAdList[i], " u ")) color = 1;
2484         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2485            !strstr(seekAdList[i], "bullet") &&
2486            !strstr(seekAdList[i], "blitz") &&
2487            !strstr(seekAdList[i], "standard") ) color = 2;
2488         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2489         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2490 }
2491
2492 void
2493 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2494 {
2495         char buf[MSG_SIZ], *ext = "";
2496         VariantClass v = StringToVariant(type);
2497         if(strstr(type, "wild")) {
2498             ext = type + 4; // append wild number
2499             if(v == VariantFischeRandom) type = "chess960"; else
2500             if(v == VariantLoadable) type = "setup"; else
2501             type = VariantName(v);
2502         }
2503         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2504         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2505             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2506             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2507             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2508             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2509             seekNrList[nrOfSeekAds] = nr;
2510             zList[nrOfSeekAds] = 0;
2511             seekAdList[nrOfSeekAds++] = StrSave(buf);
2512             if(plot) PlotSeekAd(nrOfSeekAds-1);
2513         }
2514 }
2515
2516 void
2517 EraseSeekDot(int i)
2518 {
2519     int x = xList[i], y = yList[i], d=squareSize/4, k;
2520     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2521     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2522     // now replot every dot that overlapped
2523     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2524         int xx = xList[k], yy = yList[k];
2525         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2526             DrawSeekDot(xx, yy, colorList[k]);
2527     }
2528 }
2529
2530 void
2531 RemoveSeekAd(int nr)
2532 {
2533         int i;
2534         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2535             EraseSeekDot(i);
2536             if(seekAdList[i]) free(seekAdList[i]);
2537             seekAdList[i] = seekAdList[--nrOfSeekAds];
2538             seekNrList[i] = seekNrList[nrOfSeekAds];
2539             ratingList[i] = ratingList[nrOfSeekAds];
2540             colorList[i]  = colorList[nrOfSeekAds];
2541             tcList[i] = tcList[nrOfSeekAds];
2542             xList[i]  = xList[nrOfSeekAds];
2543             yList[i]  = yList[nrOfSeekAds];
2544             zList[i]  = zList[nrOfSeekAds];
2545             seekAdList[nrOfSeekAds] = NULL;
2546             break;
2547         }
2548 }
2549
2550 Boolean
2551 MatchSoughtLine(char *line)
2552 {
2553     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2554     int nr, base, inc, u=0; char dummy;
2555
2556     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2557        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2558        (u=1) &&
2559        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2561         // match: compact and save the line
2562         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2563         return TRUE;
2564     }
2565     return FALSE;
2566 }
2567
2568 int
2569 DrawSeekGraph()
2570 {
2571     int i;
2572     if(!seekGraphUp) return FALSE;
2573     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2574     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2575
2576     DrawSeekBackground(0, 0, w, h);
2577     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2578     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2579     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2580         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2581         yy = h-1-yy;
2582         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2583         if(i%500 == 0) {
2584             char buf[MSG_SIZ];
2585             snprintf(buf, MSG_SIZ, "%d", i);
2586             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2587         }
2588     }
2589     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2590     for(i=1; i<100; i+=(i<10?1:5)) {
2591         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2592         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2593         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2594             char buf[MSG_SIZ];
2595             snprintf(buf, MSG_SIZ, "%d", i);
2596             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2597         }
2598     }
2599     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2600     return TRUE;
2601 }
2602
2603 int SeekGraphClick(ClickType click, int x, int y, int moving)
2604 {
2605     static int lastDown = 0, displayed = 0, lastSecond;
2606     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2607         if(click == Release || moving) return FALSE;
2608         nrOfSeekAds = 0;
2609         soughtPending = TRUE;
2610         SendToICS(ics_prefix);
2611         SendToICS("sought\n"); // should this be "sought all"?
2612     } else { // issue challenge based on clicked ad
2613         int dist = 10000; int i, closest = 0, second = 0;
2614         for(i=0; i<nrOfSeekAds; i++) {
2615             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2616             if(d < dist) { dist = d; closest = i; }
2617             second += (d - zList[i] < 120); // count in-range ads
2618             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2619         }
2620         if(dist < 120) {
2621             char buf[MSG_SIZ];
2622             second = (second > 1);
2623             if(displayed != closest || second != lastSecond) {
2624                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2625                 lastSecond = second; displayed = closest;
2626             }
2627             if(click == Press) {
2628                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2629                 lastDown = closest;
2630                 return TRUE;
2631             } // on press 'hit', only show info
2632             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2633             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2634             SendToICS(ics_prefix);
2635             SendToICS(buf);
2636             return TRUE; // let incoming board of started game pop down the graph
2637         } else if(click == Release) { // release 'miss' is ignored
2638             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2639             if(moving == 2) { // right up-click
2640                 nrOfSeekAds = 0; // refresh graph
2641                 soughtPending = TRUE;
2642                 SendToICS(ics_prefix);
2643                 SendToICS("sought\n"); // should this be "sought all"?
2644             }
2645             return TRUE;
2646         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2647         // press miss or release hit 'pop down' seek graph
2648         seekGraphUp = FALSE;
2649         DrawPosition(TRUE, NULL);
2650     }
2651     return TRUE;
2652 }
2653
2654 void
2655 read_from_ics(isr, closure, data, count, error)
2656      InputSourceRef isr;
2657      VOIDSTAR closure;
2658      char *data;
2659      int count;
2660      int error;
2661 {
2662 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2663 #define STARTED_NONE 0
2664 #define STARTED_MOVES 1
2665 #define STARTED_BOARD 2
2666 #define STARTED_OBSERVE 3
2667 #define STARTED_HOLDINGS 4
2668 #define STARTED_CHATTER 5
2669 #define STARTED_COMMENT 6
2670 #define STARTED_MOVES_NOHIDE 7
2671
2672     static int started = STARTED_NONE;
2673     static char parse[20000];
2674     static int parse_pos = 0;
2675     static char buf[BUF_SIZE + 1];
2676     static int firstTime = TRUE, intfSet = FALSE;
2677     static ColorClass prevColor = ColorNormal;
2678     static int savingComment = FALSE;
2679     static int cmatch = 0; // continuation sequence match
2680     char *bp;
2681     char str[MSG_SIZ];
2682     int i, oldi;
2683     int buf_len;
2684     int next_out;
2685     int tkind;
2686     int backup;    /* [DM] For zippy color lines */
2687     char *p;
2688     char talker[MSG_SIZ]; // [HGM] chat
2689     int channel;
2690
2691     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2692
2693     if (appData.debugMode) {
2694       if (!error) {
2695         fprintf(debugFP, "<ICS: ");
2696         show_bytes(debugFP, data, count);
2697         fprintf(debugFP, "\n");
2698       }
2699     }
2700
2701     if (appData.debugMode) { int f = forwardMostMove;
2702         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2703                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2704                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2705     }
2706     if (count > 0) {
2707         /* If last read ended with a partial line that we couldn't parse,
2708            prepend it to the new read and try again. */
2709         if (leftover_len > 0) {
2710             for (i=0; i<leftover_len; i++)
2711               buf[i] = buf[leftover_start + i];
2712         }
2713
2714     /* copy new characters into the buffer */
2715     bp = buf + leftover_len;
2716     buf_len=leftover_len;
2717     for (i=0; i<count; i++)
2718     {
2719         // ignore these
2720         if (data[i] == '\r')
2721             continue;
2722
2723         // join lines split by ICS?
2724         if (!appData.noJoin)
2725         {
2726             /*
2727                 Joining just consists of finding matches against the
2728                 continuation sequence, and discarding that sequence
2729                 if found instead of copying it.  So, until a match
2730                 fails, there's nothing to do since it might be the
2731                 complete sequence, and thus, something we don't want
2732                 copied.
2733             */
2734             if (data[i] == cont_seq[cmatch])
2735             {
2736                 cmatch++;
2737                 if (cmatch == strlen(cont_seq))
2738                 {
2739                     cmatch = 0; // complete match.  just reset the counter
2740
2741                     /*
2742                         it's possible for the ICS to not include the space
2743                         at the end of the last word, making our [correct]
2744                         join operation fuse two separate words.  the server
2745                         does this when the space occurs at the width setting.
2746                     */
2747                     if (!buf_len || buf[buf_len-1] != ' ')
2748                     {
2749                         *bp++ = ' ';
2750                         buf_len++;
2751                     }
2752                 }
2753                 continue;
2754             }
2755             else if (cmatch)
2756             {
2757                 /*
2758                     match failed, so we have to copy what matched before
2759                     falling through and copying this character.  In reality,
2760                     this will only ever be just the newline character, but
2761                     it doesn't hurt to be precise.
2762                 */
2763                 strncpy(bp, cont_seq, cmatch);
2764                 bp += cmatch;
2765                 buf_len += cmatch;
2766                 cmatch = 0;
2767             }
2768         }
2769
2770         // copy this char
2771         *bp++ = data[i];
2772         buf_len++;
2773     }
2774
2775         buf[buf_len] = NULLCHAR;
2776 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2777         next_out = 0;
2778         leftover_start = 0;
2779
2780         i = 0;
2781         while (i < buf_len) {
2782             /* Deal with part of the TELNET option negotiation
2783                protocol.  We refuse to do anything beyond the
2784                defaults, except that we allow the WILL ECHO option,
2785                which ICS uses to turn off password echoing when we are
2786                directly connected to it.  We reject this option
2787                if localLineEditing mode is on (always on in xboard)
2788                and we are talking to port 23, which might be a real
2789                telnet server that will try to keep WILL ECHO on permanently.
2790              */
2791             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2792                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2793                 unsigned char option;
2794                 oldi = i;
2795                 switch ((unsigned char) buf[++i]) {
2796                   case TN_WILL:
2797                     if (appData.debugMode)
2798                       fprintf(debugFP, "\n<WILL ");
2799                     switch (option = (unsigned char) buf[++i]) {
2800                       case TN_ECHO:
2801                         if (appData.debugMode)
2802                           fprintf(debugFP, "ECHO ");
2803                         /* Reply only if this is a change, according
2804                            to the protocol rules. */
2805                         if (remoteEchoOption) break;
2806                         if (appData.localLineEditing &&
2807                             atoi(appData.icsPort) == TN_PORT) {
2808                             TelnetRequest(TN_DONT, TN_ECHO);
2809                         } else {
2810                             EchoOff();
2811                             TelnetRequest(TN_DO, TN_ECHO);
2812                             remoteEchoOption = TRUE;
2813                         }
2814                         break;
2815                       default:
2816                         if (appData.debugMode)
2817                           fprintf(debugFP, "%d ", option);
2818                         /* Whatever this is, we don't want it. */
2819                         TelnetRequest(TN_DONT, option);
2820                         break;
2821                     }
2822                     break;
2823                   case TN_WONT:
2824                     if (appData.debugMode)
2825                       fprintf(debugFP, "\n<WONT ");
2826                     switch (option = (unsigned char) buf[++i]) {
2827                       case TN_ECHO:
2828                         if (appData.debugMode)
2829                           fprintf(debugFP, "ECHO ");
2830                         /* Reply only if this is a change, according
2831                            to the protocol rules. */
2832                         if (!remoteEchoOption) break;
2833                         EchoOn();
2834                         TelnetRequest(TN_DONT, TN_ECHO);
2835                         remoteEchoOption = FALSE;
2836                         break;
2837                       default:
2838                         if (appData.debugMode)
2839                           fprintf(debugFP, "%d ", (unsigned char) option);
2840                         /* Whatever this is, it must already be turned
2841                            off, because we never agree to turn on
2842                            anything non-default, so according to the
2843                            protocol rules, we don't reply. */
2844                         break;
2845                     }
2846                     break;
2847                   case TN_DO:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<DO ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       default:
2852                         /* Whatever this is, we refuse to do it. */
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "%d ", option);
2855                         TelnetRequest(TN_WONT, option);
2856                         break;
2857                     }
2858                     break;
2859                   case TN_DONT:
2860                     if (appData.debugMode)
2861                       fprintf(debugFP, "\n<DONT ");
2862                     switch (option = (unsigned char) buf[++i]) {
2863                       default:
2864                         if (appData.debugMode)
2865                           fprintf(debugFP, "%d ", option);
2866                         /* Whatever this is, we are already not doing
2867                            it, because we never agree to do anything
2868                            non-default, so according to the protocol
2869                            rules, we don't reply. */
2870                         break;
2871                     }
2872                     break;
2873                   case TN_IAC:
2874                     if (appData.debugMode)
2875                       fprintf(debugFP, "\n<IAC ");
2876                     /* Doubled IAC; pass it through */
2877                     i--;
2878                     break;
2879                   default:
2880                     if (appData.debugMode)
2881                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2882                     /* Drop all other telnet commands on the floor */
2883                     break;
2884                 }
2885                 if (oldi > next_out)
2886                   SendToPlayer(&buf[next_out], oldi - next_out);
2887                 if (++i > next_out)
2888                   next_out = i;
2889                 continue;
2890             }
2891
2892             /* OK, this at least will *usually* work */
2893             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2894                 loggedOn = TRUE;
2895             }
2896
2897             if (loggedOn && !intfSet) {
2898                 if (ics_type == ICS_ICC) {
2899                   snprintf(str, MSG_SIZ,
2900                           "/set-quietly interface %s\n/set-quietly style 12\n",
2901                           programVersion);
2902                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2903                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2904                 } else if (ics_type == ICS_CHESSNET) {
2905                   snprintf(str, MSG_SIZ, "/style 12\n");
2906                 } else {
2907                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2908                   strcat(str, programVersion);
2909                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2910                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2911                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2912 #ifdef WIN32
2913                   strcat(str, "$iset nohighlight 1\n");
2914 #endif
2915                   strcat(str, "$iset lock 1\n$style 12\n");
2916                 }
2917                 SendToICS(str);
2918                 NotifyFrontendLogin();
2919                 intfSet = TRUE;
2920             }
2921
2922             if (started == STARTED_COMMENT) {
2923                 /* Accumulate characters in comment */
2924                 parse[parse_pos++] = buf[i];
2925                 if (buf[i] == '\n') {
2926                     parse[parse_pos] = NULLCHAR;
2927                     if(chattingPartner>=0) {
2928                         char mess[MSG_SIZ];
2929                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2930                         OutputChatMessage(chattingPartner, mess);
2931                         chattingPartner = -1;
2932                         next_out = i+1; // [HGM] suppress printing in ICS window
2933                     } else
2934                     if(!suppressKibitz) // [HGM] kibitz
2935                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2936                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2937                         int nrDigit = 0, nrAlph = 0, j;
2938                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2939                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2940                         parse[parse_pos] = NULLCHAR;
2941                         // try to be smart: if it does not look like search info, it should go to
2942                         // ICS interaction window after all, not to engine-output window.
2943                         for(j=0; j<parse_pos; j++) { // count letters and digits
2944                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2945                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2946                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2947                         }
2948                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2949                             int depth=0; float score;
2950                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2951                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2952                                 pvInfoList[forwardMostMove-1].depth = depth;
2953                                 pvInfoList[forwardMostMove-1].score = 100*score;
2954                             }
2955                             OutputKibitz(suppressKibitz, parse);
2956                         } else {
2957                             char tmp[MSG_SIZ];
2958                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2959                             SendToPlayer(tmp, strlen(tmp));
2960                         }
2961                         next_out = i+1; // [HGM] suppress printing in ICS window
2962                     }
2963                     started = STARTED_NONE;
2964                 } else {
2965                     /* Don't match patterns against characters in comment */
2966                     i++;
2967                     continue;
2968                 }
2969             }
2970             if (started == STARTED_CHATTER) {
2971                 if (buf[i] != '\n') {
2972                     /* Don't match patterns against characters in chatter */
2973                     i++;
2974                     continue;
2975                 }
2976                 started = STARTED_NONE;
2977                 if(suppressKibitz) next_out = i+1;
2978             }
2979
2980             /* Kludge to deal with rcmd protocol */
2981             if (firstTime && looking_at(buf, &i, "\001*")) {
2982                 DisplayFatalError(&buf[1], 0, 1);
2983                 continue;
2984             } else {
2985                 firstTime = FALSE;
2986             }
2987
2988             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2989                 ics_type = ICS_ICC;
2990                 ics_prefix = "/";
2991                 if (appData.debugMode)
2992                   fprintf(debugFP, "ics_type %d\n", ics_type);
2993                 continue;
2994             }
2995             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2996                 ics_type = ICS_FICS;
2997                 ics_prefix = "$";
2998                 if (appData.debugMode)
2999                   fprintf(debugFP, "ics_type %d\n", ics_type);
3000                 continue;
3001             }
3002             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3003                 ics_type = ICS_CHESSNET;
3004                 ics_prefix = "/";
3005                 if (appData.debugMode)
3006                   fprintf(debugFP, "ics_type %d\n", ics_type);
3007                 continue;
3008             }
3009
3010             if (!loggedOn &&
3011                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3012                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3013                  looking_at(buf, &i, "will be \"*\""))) {
3014               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3015               continue;
3016             }
3017
3018             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3019               char buf[MSG_SIZ];
3020               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3021               DisplayIcsInteractionTitle(buf);
3022               have_set_title = TRUE;
3023             }
3024
3025             /* skip finger notes */
3026             if (started == STARTED_NONE &&
3027                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3028                  (buf[i] == '1' && buf[i+1] == '0')) &&
3029                 buf[i+2] == ':' && buf[i+3] == ' ') {
3030               started = STARTED_CHATTER;
3031               i += 3;
3032               continue;
3033             }
3034
3035             oldi = i;
3036             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3037             if(appData.seekGraph) {
3038                 if(soughtPending && MatchSoughtLine(buf+i)) {
3039                     i = strstr(buf+i, "rated") - buf;
3040                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3041                     next_out = leftover_start = i;
3042                     started = STARTED_CHATTER;
3043                     suppressKibitz = TRUE;
3044                     continue;
3045                 }
3046                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3047                         && looking_at(buf, &i, "* ads displayed")) {
3048                     soughtPending = FALSE;
3049                     seekGraphUp = TRUE;
3050                     DrawSeekGraph();
3051                     continue;
3052                 }
3053                 if(appData.autoRefresh) {
3054                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3055                         int s = (ics_type == ICS_ICC); // ICC format differs
3056                         if(seekGraphUp)
3057                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3058                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3059                         looking_at(buf, &i, "*% "); // eat prompt
3060                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3061                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3062                         next_out = i; // suppress
3063                         continue;
3064                     }
3065                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3066                         char *p = star_match[0];
3067                         while(*p) {
3068                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3069                             while(*p && *p++ != ' '); // next
3070                         }
3071                         looking_at(buf, &i, "*% "); // eat prompt
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i;
3074                         continue;
3075                     }
3076                 }
3077             }
3078
3079             /* skip formula vars */
3080             if (started == STARTED_NONE &&
3081                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3082               started = STARTED_CHATTER;
3083               i += 3;
3084               continue;
3085             }
3086
3087             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3088             if (appData.autoKibitz && started == STARTED_NONE &&
3089                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3090                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3091                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3092                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3093                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3094                         suppressKibitz = TRUE;
3095                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096                         next_out = i;
3097                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3098                                 && (gameMode == IcsPlayingWhite)) ||
3099                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3100                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3101                             started = STARTED_CHATTER; // own kibitz we simply discard
3102                         else {
3103                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3104                             parse_pos = 0; parse[0] = NULLCHAR;
3105                             savingComment = TRUE;
3106                             suppressKibitz = gameMode != IcsObserving ? 2 :
3107                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3108                         }
3109                         continue;
3110                 } else
3111                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3112                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3113                          && atoi(star_match[0])) {
3114                     // suppress the acknowledgements of our own autoKibitz
3115                     char *p;
3116                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3118                     SendToPlayer(star_match[0], strlen(star_match[0]));
3119                     if(looking_at(buf, &i, "*% ")) // eat prompt
3120                         suppressKibitz = FALSE;
3121                     next_out = i;
3122                     continue;
3123                 }
3124             } // [HGM] kibitz: end of patch
3125
3126             // [HGM] chat: intercept tells by users for which we have an open chat window
3127             channel = -1;
3128             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3129                                            looking_at(buf, &i, "* whispers:") ||
3130                                            looking_at(buf, &i, "* kibitzes:") ||
3131                                            looking_at(buf, &i, "* shouts:") ||
3132                                            looking_at(buf, &i, "* c-shouts:") ||
3133                                            looking_at(buf, &i, "--> * ") ||
3134                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3135                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3136                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3138                 int p;
3139                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3140                 chattingPartner = -1;
3141
3142                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3143                 for(p=0; p<MAX_CHAT; p++) {
3144                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3145                     talker[0] = '['; strcat(talker, "] ");
3146                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3147                     chattingPartner = p; break;
3148                     }
3149                 } else
3150                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3151                 for(p=0; p<MAX_CHAT; p++) {
3152                     if(!strcmp("kibitzes", chatPartner[p])) {
3153                         talker[0] = '['; strcat(talker, "] ");
3154                         chattingPartner = p; break;
3155                     }
3156                 } else
3157                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3158                 for(p=0; p<MAX_CHAT; p++) {
3159                     if(!strcmp("whispers", chatPartner[p])) {
3160                         talker[0] = '['; strcat(talker, "] ");
3161                         chattingPartner = p; break;
3162                     }
3163                 } else
3164                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3165                   if(buf[i-8] == '-' && buf[i-3] == 't')
3166                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3167                     if(!strcmp("c-shouts", chatPartner[p])) {
3168                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3169                         chattingPartner = p; break;
3170                     }
3171                   }
3172                   if(chattingPartner < 0)
3173                   for(p=0; p<MAX_CHAT; p++) {
3174                     if(!strcmp("shouts", chatPartner[p])) {
3175                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3176                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3177                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3178                         chattingPartner = p; break;
3179                     }
3180                   }
3181                 }
3182                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3183                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3184                     talker[0] = 0; Colorize(ColorTell, FALSE);
3185                     chattingPartner = p; break;
3186                 }
3187                 if(chattingPartner<0) i = oldi; else {
3188                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3189                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3190                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191                     started = STARTED_COMMENT;
3192                     parse_pos = 0; parse[0] = NULLCHAR;
3193                     savingComment = 3 + chattingPartner; // counts as TRUE
3194                     suppressKibitz = TRUE;
3195                     continue;
3196                 }
3197             } // [HGM] chat: end of patch
3198
3199           backup = i;
3200             if (appData.zippyTalk || appData.zippyPlay) {
3201                 /* [DM] Backup address for color zippy lines */
3202 #if ZIPPY
3203                if (loggedOn == TRUE)
3204                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3205                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3206 #endif
3207             } // [DM] 'else { ' deleted
3208                 if (
3209                     /* Regular tells and says */
3210                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3211                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3212                     looking_at(buf, &i, "* says: ") ||
3213                     /* Don't color "message" or "messages" output */
3214                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3215                     looking_at(buf, &i, "*. * at *:*: ") ||
3216                     looking_at(buf, &i, "--* (*:*): ") ||
3217                     /* Message notifications (same color as tells) */
3218                     looking_at(buf, &i, "* has left a message ") ||
3219                     looking_at(buf, &i, "* just sent you a message:\n") ||
3220                     /* Whispers and kibitzes */
3221                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3222                     looking_at(buf, &i, "* kibitzes: ") ||
3223                     /* Channel tells */
3224                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3225
3226                   if (tkind == 1 && strchr(star_match[0], ':')) {
3227                       /* Avoid "tells you:" spoofs in channels */
3228                      tkind = 3;
3229                   }
3230                   if (star_match[0][0] == NULLCHAR ||
3231                       strchr(star_match[0], ' ') ||
3232                       (tkind == 3 && strchr(star_match[1], ' '))) {
3233                     /* Reject bogus matches */
3234                     i = oldi;
3235                   } else {
3236                     if (appData.colorize) {
3237                       if (oldi > next_out) {
3238                         SendToPlayer(&buf[next_out], oldi - next_out);
3239                         next_out = oldi;
3240                       }
3241                       switch (tkind) {
3242                       case 1:
3243                         Colorize(ColorTell, FALSE);
3244                         curColor = ColorTell;
3245                         break;
3246                       case 2:
3247                         Colorize(ColorKibitz, FALSE);
3248                         curColor = ColorKibitz;
3249                         break;
3250                       case 3:
3251                         p = strrchr(star_match[1], '(');
3252                         if (p == NULL) {
3253                           p = star_match[1];
3254                         } else {
3255                           p++;
3256                         }
3257                         if (atoi(p) == 1) {
3258                           Colorize(ColorChannel1, FALSE);
3259                           curColor = ColorChannel1;
3260                         } else {
3261                           Colorize(ColorChannel, FALSE);
3262                           curColor = ColorChannel;
3263                         }
3264                         break;
3265                       case 5:
3266                         curColor = ColorNormal;
3267                         break;
3268                       }
3269                     }
3270                     if (started == STARTED_NONE && appData.autoComment &&
3271                         (gameMode == IcsObserving ||
3272                          gameMode == IcsPlayingWhite ||
3273                          gameMode == IcsPlayingBlack)) {
3274                       parse_pos = i - oldi;
3275                       memcpy(parse, &buf[oldi], parse_pos);
3276                       parse[parse_pos] = NULLCHAR;
3277                       started = STARTED_COMMENT;
3278                       savingComment = TRUE;
3279                     } else {
3280                       started = STARTED_CHATTER;
3281                       savingComment = FALSE;
3282                     }
3283                     loggedOn = TRUE;
3284                     continue;
3285                   }
3286                 }
3287
3288                 if (looking_at(buf, &i, "* s-shouts: ") ||
3289                     looking_at(buf, &i, "* c-shouts: ")) {
3290                     if (appData.colorize) {
3291                         if (oldi > next_out) {
3292                             SendToPlayer(&buf[next_out], oldi - next_out);
3293                             next_out = oldi;
3294                         }
3295                         Colorize(ColorSShout, FALSE);
3296                         curColor = ColorSShout;
3297                     }
3298                     loggedOn = TRUE;
3299                     started = STARTED_CHATTER;
3300                     continue;
3301                 }
3302
3303                 if (looking_at(buf, &i, "--->")) {
3304                     loggedOn = TRUE;
3305                     continue;
3306                 }
3307
3308                 if (looking_at(buf, &i, "* shouts: ") ||
3309                     looking_at(buf, &i, "--> ")) {
3310                     if (appData.colorize) {
3311                         if (oldi > next_out) {
3312                             SendToPlayer(&buf[next_out], oldi - next_out);
3313                             next_out = oldi;
3314                         }
3315                         Colorize(ColorShout, FALSE);
3316                         curColor = ColorShout;
3317                     }
3318                     loggedOn = TRUE;
3319                     started = STARTED_CHATTER;
3320                     continue;
3321                 }
3322
3323                 if (looking_at( buf, &i, "Challenge:")) {
3324                     if (appData.colorize) {
3325                         if (oldi > next_out) {
3326                             SendToPlayer(&buf[next_out], oldi - next_out);
3327                             next_out = oldi;
3328                         }
3329                         Colorize(ColorChallenge, FALSE);
3330                         curColor = ColorChallenge;
3331                     }
3332                     loggedOn = TRUE;
3333                     continue;
3334                 }
3335
3336                 if (looking_at(buf, &i, "* offers you") ||
3337                     looking_at(buf, &i, "* offers to be") ||
3338                     looking_at(buf, &i, "* would like to") ||
3339                     looking_at(buf, &i, "* requests to") ||
3340                     looking_at(buf, &i, "Your opponent offers") ||
3341                     looking_at(buf, &i, "Your opponent requests")) {
3342
3343                     if (appData.colorize) {
3344                         if (oldi > next_out) {
3345                             SendToPlayer(&buf[next_out], oldi - next_out);
3346                             next_out = oldi;
3347                         }
3348                         Colorize(ColorRequest, FALSE);
3349                         curColor = ColorRequest;
3350                     }
3351                     continue;
3352                 }
3353
3354                 if (looking_at(buf, &i, "* (*) seeking")) {
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorSeek, FALSE);
3361                         curColor = ColorSeek;
3362                     }
3363                     continue;
3364             }
3365
3366           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3367
3368             if (looking_at(buf, &i, "\\   ")) {
3369                 if (prevColor != ColorNormal) {
3370                     if (oldi > next_out) {
3371                         SendToPlayer(&buf[next_out], oldi - next_out);
3372                         next_out = oldi;
3373                     }
3374                     Colorize(prevColor, TRUE);
3375                     curColor = prevColor;
3376                 }
3377                 if (savingComment) {
3378                     parse_pos = i - oldi;
3379                     memcpy(parse, &buf[oldi], parse_pos);
3380                     parse[parse_pos] = NULLCHAR;
3381                     started = STARTED_COMMENT;
3382                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3383                         chattingPartner = savingComment - 3; // kludge to remember the box
3384                 } else {
3385                     started = STARTED_CHATTER;
3386                 }
3387                 continue;
3388             }
3389
3390             if (looking_at(buf, &i, "Black Strength :") ||
3391                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3392                 looking_at(buf, &i, "<10>") ||
3393                 looking_at(buf, &i, "#@#")) {
3394                 /* Wrong board style */
3395                 loggedOn = TRUE;
3396                 SendToICS(ics_prefix);
3397                 SendToICS("set style 12\n");
3398                 SendToICS(ics_prefix);
3399                 SendToICS("refresh\n");
3400                 continue;
3401             }
3402
3403             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3404                 ICSInitScript();
3405                 have_sent_ICS_logon = 1;
3406                 continue;
3407             }
3408
3409             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3410                 (looking_at(buf, &i, "\n<12> ") ||
3411                  looking_at(buf, &i, "<12> "))) {
3412                 loggedOn = TRUE;
3413                 if (oldi > next_out) {
3414                     SendToPlayer(&buf[next_out], oldi - next_out);
3415                 }
3416                 next_out = i;
3417                 started = STARTED_BOARD;
3418                 parse_pos = 0;
3419                 continue;
3420             }
3421
3422             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3423                 looking_at(buf, &i, "<b1> ")) {
3424                 if (oldi > next_out) {
3425                     SendToPlayer(&buf[next_out], oldi - next_out);
3426                 }
3427                 next_out = i;
3428                 started = STARTED_HOLDINGS;
3429                 parse_pos = 0;
3430                 continue;
3431             }
3432
3433             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3434                 loggedOn = TRUE;
3435                 /* Header for a move list -- first line */
3436
3437                 switch (ics_getting_history) {
3438                   case H_FALSE:
3439                     switch (gameMode) {
3440                       case IcsIdle:
3441                       case BeginningOfGame:
3442                         /* User typed "moves" or "oldmoves" while we
3443                            were idle.  Pretend we asked for these
3444                            moves and soak them up so user can step
3445                            through them and/or save them.
3446                            */
3447                         Reset(FALSE, TRUE);
3448                         gameMode = IcsObserving;
3449                         ModeHighlight();
3450                         ics_gamenum = -1;
3451                         ics_getting_history = H_GOT_UNREQ_HEADER;
3452                         break;
3453                       case EditGame: /*?*/
3454                       case EditPosition: /*?*/
3455                         /* Should above feature work in these modes too? */
3456                         /* For now it doesn't */
3457                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3458                         break;
3459                       default:
3460                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3461                         break;
3462                     }
3463                     break;
3464                   case H_REQUESTED:
3465                     /* Is this the right one? */
3466                     if (gameInfo.white && gameInfo.black &&
3467                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3468                         strcmp(gameInfo.black, star_match[2]) == 0) {
3469                         /* All is well */
3470                         ics_getting_history = H_GOT_REQ_HEADER;
3471                     }
3472                     break;
3473                   case H_GOT_REQ_HEADER:
3474                   case H_GOT_UNREQ_HEADER:
3475                   case H_GOT_UNWANTED_HEADER:
3476                   case H_GETTING_MOVES:
3477                     /* Should not happen */
3478                     DisplayError(_("Error gathering move list: two headers"), 0);
3479                     ics_getting_history = H_FALSE;
3480                     break;
3481                 }
3482
3483                 /* Save player ratings into gameInfo if needed */
3484                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3485                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3486                     (gameInfo.whiteRating == -1 ||
3487                      gameInfo.blackRating == -1)) {
3488
3489                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3490                     gameInfo.blackRating = string_to_rating(star_match[3]);
3491                     if (appData.debugMode)
3492                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3493                               gameInfo.whiteRating, gameInfo.blackRating);
3494                 }
3495                 continue;
3496             }
3497
3498             if (looking_at(buf, &i,
3499               "* * match, initial time: * minute*, increment: * second")) {
3500                 /* Header for a move list -- second line */
3501                 /* Initial board will follow if this is a wild game */
3502                 if (gameInfo.event != NULL) free(gameInfo.event);
3503                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3504                 gameInfo.event = StrSave(str);
3505                 /* [HGM] we switched variant. Translate boards if needed. */
3506                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i, "Move  ")) {
3511                 /* Beginning of a move list */
3512                 switch (ics_getting_history) {
3513                   case H_FALSE:
3514                     /* Normally should not happen */
3515                     /* Maybe user hit reset while we were parsing */
3516                     break;
3517                   case H_REQUESTED:
3518                     /* Happens if we are ignoring a move list that is not
3519                      * the one we just requested.  Common if the user
3520                      * tries to observe two games without turning off
3521                      * getMoveList */
3522                     break;
3523                   case H_GETTING_MOVES:
3524                     /* Should not happen */
3525                     DisplayError(_("Error gathering move list: nested"), 0);
3526                     ics_getting_history = H_FALSE;
3527                     break;
3528                   case H_GOT_REQ_HEADER:
3529                     ics_getting_history = H_GETTING_MOVES;
3530                     started = STARTED_MOVES;
3531                     parse_pos = 0;
3532                     if (oldi > next_out) {
3533                         SendToPlayer(&buf[next_out], oldi - next_out);
3534                     }
3535                     break;
3536                   case H_GOT_UNREQ_HEADER:
3537                     ics_getting_history = H_GETTING_MOVES;
3538                     started = STARTED_MOVES_NOHIDE;
3539                     parse_pos = 0;
3540                     break;
3541                   case H_GOT_UNWANTED_HEADER:
3542                     ics_getting_history = H_FALSE;
3543                     break;
3544                 }
3545                 continue;
3546             }
3547
3548             if (looking_at(buf, &i, "% ") ||
3549                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3550                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3551                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3552                     soughtPending = FALSE;
3553                     seekGraphUp = TRUE;
3554                     DrawSeekGraph();
3555                 }
3556                 if(suppressKibitz) next_out = i;
3557                 savingComment = FALSE;
3558                 suppressKibitz = 0;
3559                 switch (started) {
3560                   case STARTED_MOVES:
3561                   case STARTED_MOVES_NOHIDE:
3562                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3563                     parse[parse_pos + i - oldi] = NULLCHAR;
3564                     ParseGameHistory(parse);
3565 #if ZIPPY
3566                     if (appData.zippyPlay && first.initDone) {
3567                         FeedMovesToProgram(&first, forwardMostMove);
3568                         if (gameMode == IcsPlayingWhite) {
3569                             if (WhiteOnMove(forwardMostMove)) {
3570                                 if (first.sendTime) {
3571                                   if (first.useColors) {
3572                                     SendToProgram("black\n", &first);
3573                                   }
3574                                   SendTimeRemaining(&first, TRUE);
3575                                 }
3576                                 if (first.useColors) {
3577                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3578                                 }
3579                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3580                                 first.maybeThinking = TRUE;
3581                             } else {
3582                                 if (first.usePlayother) {
3583                                   if (first.sendTime) {
3584                                     SendTimeRemaining(&first, TRUE);
3585                                   }
3586                                   SendToProgram("playother\n", &first);
3587                                   firstMove = FALSE;
3588                                 } else {
3589                                   firstMove = TRUE;
3590                                 }
3591                             }
3592                         } else if (gameMode == IcsPlayingBlack) {
3593                             if (!WhiteOnMove(forwardMostMove)) {
3594                                 if (first.sendTime) {
3595                                   if (first.useColors) {
3596                                     SendToProgram("white\n", &first);
3597                                   }
3598                                   SendTimeRemaining(&first, FALSE);
3599                                 }
3600                                 if (first.useColors) {
3601                                   SendToProgram("black\n", &first);
3602                                 }
3603                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3604                                 first.maybeThinking = TRUE;
3605                             } else {
3606                                 if (first.usePlayother) {
3607                                   if (first.sendTime) {
3608                                     SendTimeRemaining(&first, FALSE);
3609                                   }
3610                                   SendToProgram("playother\n", &first);
3611                                   firstMove = FALSE;
3612                                 } else {
3613                                   firstMove = TRUE;
3614                                 }
3615                             }
3616                         }
3617                     }
3618 #endif
3619                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3620                         /* Moves came from oldmoves or moves command
3621                            while we weren't doing anything else.
3622                            */
3623                         currentMove = forwardMostMove;
3624                         ClearHighlights();/*!!could figure this out*/
3625                         flipView = appData.flipView;
3626                         DrawPosition(TRUE, boards[currentMove]);
3627                         DisplayBothClocks();
3628                         snprintf(str, MSG_SIZ, "%s vs. %s",
3629                                 gameInfo.white, gameInfo.black);
3630                         DisplayTitle(str);
3631                         gameMode = IcsIdle;
3632                     } else {
3633                         /* Moves were history of an active game */
3634                         if (gameInfo.resultDetails != NULL) {
3635                             free(gameInfo.resultDetails);
3636                             gameInfo.resultDetails = NULL;
3637                         }
3638                     }
3639                     HistorySet(parseList, backwardMostMove,
3640                                forwardMostMove, currentMove-1);
3641                     DisplayMove(currentMove - 1);
3642                     if (started == STARTED_MOVES) next_out = i;
3643                     started = STARTED_NONE;
3644                     ics_getting_history = H_FALSE;
3645                     break;
3646
3647                   case STARTED_OBSERVE:
3648                     started = STARTED_NONE;
3649                     SendToICS(ics_prefix);
3650                     SendToICS("refresh\n");
3651                     break;
3652
3653                   default:
3654                     break;
3655                 }
3656                 if(bookHit) { // [HGM] book: simulate book reply
3657                     static char bookMove[MSG_SIZ]; // a bit generous?
3658
3659                     programStats.nodes = programStats.depth = programStats.time =
3660                     programStats.score = programStats.got_only_move = 0;
3661                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3662
3663                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3664                     strcat(bookMove, bookHit);
3665                     HandleMachineMove(bookMove, &first);
3666                 }
3667                 continue;
3668             }
3669
3670             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3671                  started == STARTED_HOLDINGS ||
3672                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3673                 /* Accumulate characters in move list or board */
3674                 parse[parse_pos++] = buf[i];
3675             }
3676
3677             /* Start of game messages.  Mostly we detect start of game
3678                when the first board image arrives.  On some versions
3679                of the ICS, though, we need to do a "refresh" after starting
3680                to observe in order to get the current board right away. */
3681             if (looking_at(buf, &i, "Adding game * to observation list")) {
3682                 started = STARTED_OBSERVE;
3683                 continue;
3684             }
3685
3686             /* Handle auto-observe */
3687             if (appData.autoObserve &&
3688                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3689                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3690                 char *player;
3691                 /* Choose the player that was highlighted, if any. */
3692                 if (star_match[0][0] == '\033' ||
3693                     star_match[1][0] != '\033') {
3694                     player = star_match[0];
3695                 } else {
3696                     player = star_match[2];
3697                 }
3698                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3699                         ics_prefix, StripHighlightAndTitle(player));
3700                 SendToICS(str);
3701
3702                 /* Save ratings from notify string */
3703                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3704                 player1Rating = string_to_rating(star_match[1]);
3705                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3706                 player2Rating = string_to_rating(star_match[3]);
3707
3708                 if (appData.debugMode)
3709                   fprintf(debugFP,
3710                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3711                           player1Name, player1Rating,
3712                           player2Name, player2Rating);
3713
3714                 continue;
3715             }
3716
3717             /* Deal with automatic examine mode after a game,
3718                and with IcsObserving -> IcsExamining transition */
3719             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3720                 looking_at(buf, &i, "has made you an examiner of game *")) {
3721
3722                 int gamenum = atoi(star_match[0]);
3723                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3724                     gamenum == ics_gamenum) {
3725                     /* We were already playing or observing this game;
3726                        no need to refetch history */
3727                     gameMode = IcsExamining;
3728                     if (pausing) {
3729                         pauseExamForwardMostMove = forwardMostMove;
3730                     } else if (currentMove < forwardMostMove) {
3731                         ForwardInner(forwardMostMove);
3732                     }
3733                 } else {
3734                     /* I don't think this case really can happen */
3735                     SendToICS(ics_prefix);
3736                     SendToICS("refresh\n");
3737                 }
3738                 continue;
3739             }
3740
3741             /* Error messages */
3742 //          if (ics_user_moved) {
3743             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3744                 if (looking_at(buf, &i, "Illegal move") ||
3745                     looking_at(buf, &i, "Not a legal move") ||
3746                     looking_at(buf, &i, "Your king is in check") ||
3747                     looking_at(buf, &i, "It isn't your turn") ||
3748                     looking_at(buf, &i, "It is not your move")) {
3749                     /* Illegal move */
3750                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3751                         currentMove = forwardMostMove-1;
3752                         DisplayMove(currentMove - 1); /* before DMError */
3753                         DrawPosition(FALSE, boards[currentMove]);
3754                         SwitchClocks(forwardMostMove-1); // [HGM] race
3755                         DisplayBothClocks();
3756                     }
3757                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3758                     ics_user_moved = 0;
3759                     continue;
3760                 }
3761             }
3762
3763             if (looking_at(buf, &i, "still have time") ||
3764                 looking_at(buf, &i, "not out of time") ||
3765                 looking_at(buf, &i, "either player is out of time") ||
3766                 looking_at(buf, &i, "has timeseal; checking")) {
3767                 /* We must have called his flag a little too soon */
3768                 whiteFlag = blackFlag = FALSE;
3769                 continue;
3770             }
3771
3772             if (looking_at(buf, &i, "added * seconds to") ||
3773                 looking_at(buf, &i, "seconds were added to")) {
3774                 /* Update the clocks */
3775                 SendToICS(ics_prefix);
3776                 SendToICS("refresh\n");
3777                 continue;
3778             }
3779
3780             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3781                 ics_clock_paused = TRUE;
3782                 StopClocks();
3783                 continue;
3784             }
3785
3786             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3787                 ics_clock_paused = FALSE;
3788                 StartClocks();
3789                 continue;
3790             }
3791
3792             /* Grab player ratings from the Creating: message.
3793                Note we have to check for the special case when
3794                the ICS inserts things like [white] or [black]. */
3795             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3796                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3797                 /* star_matches:
3798                    0    player 1 name (not necessarily white)
3799                    1    player 1 rating
3800                    2    empty, white, or black (IGNORED)
3801                    3    player 2 name (not necessarily black)
3802                    4    player 2 rating
3803
3804                    The names/ratings are sorted out when the game
3805                    actually starts (below).
3806                 */
3807                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3808                 player1Rating = string_to_rating(star_match[1]);
3809                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3810                 player2Rating = string_to_rating(star_match[4]);
3811
3812                 if (appData.debugMode)
3813                   fprintf(debugFP,
3814                           "Ratings from 'Creating:' %s %d, %s %d\n",
3815                           player1Name, player1Rating,
3816                           player2Name, player2Rating);
3817
3818                 continue;
3819             }
3820
3821             /* Improved generic start/end-of-game messages */
3822             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3823                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3824                 /* If tkind == 0: */
3825                 /* star_match[0] is the game number */
3826                 /*           [1] is the white player's name */
3827                 /*           [2] is the black player's name */
3828                 /* For end-of-game: */
3829                 /*           [3] is the reason for the game end */
3830                 /*           [4] is a PGN end game-token, preceded by " " */
3831                 /* For start-of-game: */
3832                 /*           [3] begins with "Creating" or "Continuing" */
3833                 /*           [4] is " *" or empty (don't care). */
3834                 int gamenum = atoi(star_match[0]);
3835                 char *whitename, *blackname, *why, *endtoken;
3836                 ChessMove endtype = EndOfFile;
3837
3838                 if (tkind == 0) {
3839                   whitename = star_match[1];
3840                   blackname = star_match[2];
3841                   why = star_match[3];
3842                   endtoken = star_match[4];
3843                 } else {
3844                   whitename = star_match[1];
3845                   blackname = star_match[3];
3846                   why = star_match[5];
3847                   endtoken = star_match[6];
3848                 }
3849
3850                 /* Game start messages */
3851                 if (strncmp(why, "Creating ", 9) == 0 ||
3852                     strncmp(why, "Continuing ", 11) == 0) {
3853                     gs_gamenum = gamenum;
3854                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3855                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3856 #if ZIPPY
3857                     if (appData.zippyPlay) {
3858                         ZippyGameStart(whitename, blackname);
3859                     }
3860 #endif /*ZIPPY*/
3861                     partnerBoardValid = FALSE; // [HGM] bughouse
3862                     continue;
3863                 }
3864
3865                 /* Game end messages */
3866                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3867                     ics_gamenum != gamenum) {
3868                     continue;
3869                 }
3870                 while (endtoken[0] == ' ') endtoken++;
3871                 switch (endtoken[0]) {
3872                   case '*':
3873                   default:
3874                     endtype = GameUnfinished;
3875                     break;
3876                   case '0':
3877                     endtype = BlackWins;
3878                     break;
3879                   case '1':
3880                     if (endtoken[1] == '/')
3881                       endtype = GameIsDrawn;
3882                     else
3883                       endtype = WhiteWins;
3884                     break;
3885                 }
3886                 GameEnds(endtype, why, GE_ICS);
3887 #if ZIPPY
3888                 if (appData.zippyPlay && first.initDone) {
3889                     ZippyGameEnd(endtype, why);
3890                     if (first.pr == NULL) {
3891                       /* Start the next process early so that we'll
3892                          be ready for the next challenge */
3893                       StartChessProgram(&first);
3894                     }
3895                     /* Send "new" early, in case this command takes
3896                        a long time to finish, so that we'll be ready
3897                        for the next challenge. */
3898                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3899                     Reset(TRUE, TRUE);
3900                 }
3901 #endif /*ZIPPY*/
3902                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3903                 continue;
3904             }
3905
3906             if (looking_at(buf, &i, "Removing game * from observation") ||
3907                 looking_at(buf, &i, "no longer observing game *") ||
3908                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3909                 if (gameMode == IcsObserving &&
3910                     atoi(star_match[0]) == ics_gamenum)
3911                   {
3912                       /* icsEngineAnalyze */
3913                       if (appData.icsEngineAnalyze) {
3914                             ExitAnalyzeMode();
3915                             ModeHighlight();
3916                       }
3917                       StopClocks();
3918                       gameMode = IcsIdle;
3919                       ics_gamenum = -1;
3920                       ics_user_moved = FALSE;
3921                   }
3922                 continue;
3923             }
3924
3925             if (looking_at(buf, &i, "no longer examining game *")) {
3926                 if (gameMode == IcsExamining &&
3927                     atoi(star_match[0]) == ics_gamenum)
3928                   {
3929                       gameMode = IcsIdle;
3930                       ics_gamenum = -1;
3931                       ics_user_moved = FALSE;
3932                   }
3933                 continue;
3934             }
3935
3936             /* Advance leftover_start past any newlines we find,
3937                so only partial lines can get reparsed */
3938             if (looking_at(buf, &i, "\n")) {
3939                 prevColor = curColor;
3940                 if (curColor != ColorNormal) {
3941                     if (oldi > next_out) {
3942                         SendToPlayer(&buf[next_out], oldi - next_out);
3943                         next_out = oldi;
3944                     }
3945                     Colorize(ColorNormal, FALSE);
3946                     curColor = ColorNormal;
3947                 }
3948                 if (started == STARTED_BOARD) {
3949                     started = STARTED_NONE;
3950                     parse[parse_pos] = NULLCHAR;
3951                     ParseBoard12(parse);
3952                     ics_user_moved = 0;
3953
3954                     /* Send premove here */
3955                     if (appData.premove) {
3956                       char str[MSG_SIZ];
3957                       if (currentMove == 0 &&
3958                           gameMode == IcsPlayingWhite &&
3959                           appData.premoveWhite) {
3960                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3961                         if (appData.debugMode)
3962                           fprintf(debugFP, "Sending premove:\n");
3963                         SendToICS(str);
3964                       } else if (currentMove == 1 &&
3965                                  gameMode == IcsPlayingBlack &&
3966                                  appData.premoveBlack) {
3967                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3968                         if (appData.debugMode)
3969                           fprintf(debugFP, "Sending premove:\n");
3970                         SendToICS(str);
3971                       } else if (gotPremove) {
3972                         gotPremove = 0;
3973                         ClearPremoveHighlights();
3974                         if (appData.debugMode)
3975                           fprintf(debugFP, "Sending premove:\n");
3976                           UserMoveEvent(premoveFromX, premoveFromY,
3977                                         premoveToX, premoveToY,
3978                                         premovePromoChar);
3979                       }
3980                     }
3981
3982                     /* Usually suppress following prompt */
3983                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3984                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3985                         if (looking_at(buf, &i, "*% ")) {
3986                             savingComment = FALSE;
3987                             suppressKibitz = 0;
3988                         }
3989                     }
3990                     next_out = i;
3991                 } else if (started == STARTED_HOLDINGS) {
3992                     int gamenum;
3993                     char new_piece[MSG_SIZ];
3994                     started = STARTED_NONE;
3995                     parse[parse_pos] = NULLCHAR;
3996                     if (appData.debugMode)
3997                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3998                                                         parse, currentMove);
3999                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4000                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4001                         if (gameInfo.variant == VariantNormal) {
4002                           /* [HGM] We seem to switch variant during a game!
4003                            * Presumably no holdings were displayed, so we have
4004                            * to move the position two files to the right to
4005                            * create room for them!
4006                            */
4007                           VariantClass newVariant;
4008                           switch(gameInfo.boardWidth) { // base guess on board width
4009                                 case 9:  newVariant = VariantShogi; break;
4010                                 case 10: newVariant = VariantGreat; break;
4011                                 default: newVariant = VariantCrazyhouse; break;
4012                           }
4013                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4014                           /* Get a move list just to see the header, which
4015                              will tell us whether this is really bug or zh */
4016                           if (ics_getting_history == H_FALSE) {
4017                             ics_getting_history = H_REQUESTED;
4018                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4019                             SendToICS(str);
4020                           }
4021                         }
4022                         new_piece[0] = NULLCHAR;
4023                         sscanf(parse, "game %d white [%s black [%s <- %s",
4024                                &gamenum, white_holding, black_holding,
4025                                new_piece);
4026                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4027                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4028                         /* [HGM] copy holdings to board holdings area */
4029                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4030                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4031                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4032 #if ZIPPY
4033                         if (appData.zippyPlay && first.initDone) {
4034                             ZippyHoldings(white_holding, black_holding,
4035                                           new_piece);
4036                         }
4037 #endif /*ZIPPY*/
4038                         if (tinyLayout || smallLayout) {
4039                             char wh[16], bh[16];
4040                             PackHolding(wh, white_holding);
4041                             PackHolding(bh, black_holding);
4042                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4043                                     gameInfo.white, gameInfo.black);
4044                         } else {
4045                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4046                                     gameInfo.white, white_holding,
4047                                     gameInfo.black, black_holding);
4048                         }
4049                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4050                         DrawPosition(FALSE, boards[currentMove]);
4051                         DisplayTitle(str);
4052                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to partner-board holdings area */
4059                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4060                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4061                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4062                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4063                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4064                       }
4065                     }
4066                     /* Suppress following prompt */
4067                     if (looking_at(buf, &i, "*% ")) {
4068                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4069                         savingComment = FALSE;
4070                         suppressKibitz = 0;
4071                     }
4072                     next_out = i;
4073                 }
4074                 continue;
4075             }
4076
4077             i++;                /* skip unparsed character and loop back */
4078         }
4079
4080         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4081 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4082 //          SendToPlayer(&buf[next_out], i - next_out);
4083             started != STARTED_HOLDINGS && leftover_start > next_out) {
4084             SendToPlayer(&buf[next_out], leftover_start - next_out);
4085             next_out = i;
4086         }
4087
4088         leftover_len = buf_len - leftover_start;
4089         /* if buffer ends with something we couldn't parse,
4090            reparse it after appending the next read */
4091
4092     } else if (count == 0) {
4093         RemoveInputSource(isr);
4094         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4095     } else {
4096         DisplayFatalError(_("Error reading from ICS"), error, 1);
4097     }
4098 }
4099
4100
4101 /* Board style 12 looks like this:
4102
4103    <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
4104
4105  * The "<12> " is stripped before it gets to this routine.  The two
4106  * trailing 0's (flip state and clock ticking) are later addition, and
4107  * some chess servers may not have them, or may have only the first.
4108  * Additional trailing fields may be added in the future.
4109  */
4110
4111 #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"
4112
4113 #define RELATION_OBSERVING_PLAYED    0
4114 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4115 #define RELATION_PLAYING_MYMOVE      1
4116 #define RELATION_PLAYING_NOTMYMOVE  -1
4117 #define RELATION_EXAMINING           2
4118 #define RELATION_ISOLATED_BOARD     -3
4119 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4120
4121 void
4122 ParseBoard12(string)
4123      char *string;
4124 {
4125     GameMode newGameMode;
4126     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4127     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4128     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4129     char to_play, board_chars[200];
4130     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4131     char black[32], white[32];
4132     Board board;
4133     int prevMove = currentMove;
4134     int ticking = 2;
4135     ChessMove moveType;
4136     int fromX, fromY, toX, toY;
4137     char promoChar;
4138     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4139     char *bookHit = NULL; // [HGM] book
4140     Boolean weird = FALSE, reqFlag = FALSE;
4141
4142     fromX = fromY = toX = toY = -1;
4143
4144     newGame = FALSE;
4145
4146     if (appData.debugMode)
4147       fprintf(debugFP, _("Parsing board: %s\n"), string);
4148
4149     move_str[0] = NULLCHAR;
4150     elapsed_time[0] = NULLCHAR;
4151     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4152         int  i = 0, j;
4153         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4154             if(string[i] == ' ') { ranks++; files = 0; }
4155             else files++;
4156             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4157             i++;
4158         }
4159         for(j = 0; j <i; j++) board_chars[j] = string[j];
4160         board_chars[i] = '\0';
4161         string += i + 1;
4162     }
4163     n = sscanf(string, PATTERN, &to_play, &double_push,
4164                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4165                &gamenum, white, black, &relation, &basetime, &increment,
4166                &white_stren, &black_stren, &white_time, &black_time,
4167                &moveNum, str, elapsed_time, move_str, &ics_flip,
4168                &ticking);
4169
4170     if (n < 21) {
4171         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4172         DisplayError(str, 0);
4173         return;
4174     }
4175
4176     /* Convert the move number to internal form */
4177     moveNum = (moveNum - 1) * 2;
4178     if (to_play == 'B') moveNum++;
4179     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4180       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4181                         0, 1);
4182       return;
4183     }
4184
4185     switch (relation) {
4186       case RELATION_OBSERVING_PLAYED:
4187       case RELATION_OBSERVING_STATIC:
4188         if (gamenum == -1) {
4189             /* Old ICC buglet */
4190             relation = RELATION_OBSERVING_STATIC;
4191         }
4192         newGameMode = IcsObserving;
4193         break;
4194       case RELATION_PLAYING_MYMOVE:
4195       case RELATION_PLAYING_NOTMYMOVE:
4196         newGameMode =
4197           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4198             IcsPlayingWhite : IcsPlayingBlack;
4199         break;
4200       case RELATION_EXAMINING:
4201         newGameMode = IcsExamining;
4202         break;
4203       case RELATION_ISOLATED_BOARD:
4204       default:
4205         /* Just display this board.  If user was doing something else,
4206            we will forget about it until the next board comes. */
4207         newGameMode = IcsIdle;
4208         break;
4209       case RELATION_STARTING_POSITION:
4210         newGameMode = gameMode;
4211         break;
4212     }
4213
4214     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4215          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4216       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4217       char *toSqr;
4218       for (k = 0; k < ranks; k++) {
4219         for (j = 0; j < files; j++)
4220           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4221         if(gameInfo.holdingsWidth > 1) {
4222              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4223              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4224         }
4225       }
4226       CopyBoard(partnerBoard, board);
4227       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4228         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4229         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4230       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4231       if(toSqr = strchr(str, '-')) {
4232         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4233         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4234       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4235       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4236       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4237       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4238       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4239       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4240                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4241       DisplayMessage(partnerStatus, "");
4242         partnerBoardValid = TRUE;
4243       return;
4244     }
4245
4246     /* Modify behavior for initial board display on move listing
4247        of wild games.
4248        */
4249     switch (ics_getting_history) {
4250       case H_FALSE:
4251       case H_REQUESTED:
4252         break;
4253       case H_GOT_REQ_HEADER:
4254       case H_GOT_UNREQ_HEADER:
4255         /* This is the initial position of the current game */
4256         gamenum = ics_gamenum;
4257         moveNum = 0;            /* old ICS bug workaround */
4258         if (to_play == 'B') {
4259           startedFromSetupPosition = TRUE;
4260           blackPlaysFirst = TRUE;
4261           moveNum = 1;
4262           if (forwardMostMove == 0) forwardMostMove = 1;
4263           if (backwardMostMove == 0) backwardMostMove = 1;
4264           if (currentMove == 0) currentMove = 1;
4265         }
4266         newGameMode = gameMode;
4267         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4268         break;
4269       case H_GOT_UNWANTED_HEADER:
4270         /* This is an initial board that we don't want */
4271         return;
4272       case H_GETTING_MOVES:
4273         /* Should not happen */
4274         DisplayError(_("Error gathering move list: extra board"), 0);
4275         ics_getting_history = H_FALSE;
4276         return;
4277     }
4278
4279    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4280                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4281      /* [HGM] We seem to have switched variant unexpectedly
4282       * Try to guess new variant from board size
4283       */
4284           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4285           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4286           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4287           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4288           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4289           if(!weird) newVariant = VariantNormal;
4290           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4291           /* Get a move list just to see the header, which
4292              will tell us whether this is really bug or zh */
4293           if (ics_getting_history == H_FALSE) {
4294             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4295             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4296             SendToICS(str);
4297           }
4298     }
4299
4300     /* Take action if this is the first board of a new game, or of a
4301        different game than is currently being displayed.  */
4302     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4303         relation == RELATION_ISOLATED_BOARD) {
4304
4305         /* Forget the old game and get the history (if any) of the new one */
4306         if (gameMode != BeginningOfGame) {
4307           Reset(TRUE, TRUE);
4308         }
4309         newGame = TRUE;
4310         if (appData.autoRaiseBoard) BoardToTop();
4311         prevMove = -3;
4312         if (gamenum == -1) {
4313             newGameMode = IcsIdle;
4314         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4315                    appData.getMoveList && !reqFlag) {
4316             /* Need to get game history */
4317             ics_getting_history = H_REQUESTED;
4318             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4319             SendToICS(str);
4320         }
4321
4322         /* Initially flip the board to have black on the bottom if playing
4323            black or if the ICS flip flag is set, but let the user change
4324            it with the Flip View button. */
4325         flipView = appData.autoFlipView ?
4326           (newGameMode == IcsPlayingBlack) || ics_flip :
4327           appData.flipView;
4328
4329         /* Done with values from previous mode; copy in new ones */
4330         gameMode = newGameMode;
4331         ModeHighlight();
4332         ics_gamenum = gamenum;
4333         if (gamenum == gs_gamenum) {
4334             int klen = strlen(gs_kind);
4335             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4336             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4337             gameInfo.event = StrSave(str);
4338         } else {
4339             gameInfo.event = StrSave("ICS game");
4340         }
4341         gameInfo.site = StrSave(appData.icsHost);
4342         gameInfo.date = PGNDate();
4343         gameInfo.round = StrSave("-");
4344         gameInfo.white = StrSave(white);
4345         gameInfo.black = StrSave(black);
4346         timeControl = basetime * 60 * 1000;
4347         timeControl_2 = 0;
4348         timeIncrement = increment * 1000;
4349         movesPerSession = 0;
4350         gameInfo.timeControl = TimeControlTagValue();
4351         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4352   if (appData.debugMode) {
4353     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4354     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4355     setbuf(debugFP, NULL);
4356   }
4357
4358         gameInfo.outOfBook = NULL;
4359
4360         /* Do we have the ratings? */
4361         if (strcmp(player1Name, white) == 0 &&
4362             strcmp(player2Name, black) == 0) {
4363             if (appData.debugMode)
4364               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4365                       player1Rating, player2Rating);
4366             gameInfo.whiteRating = player1Rating;
4367             gameInfo.blackRating = player2Rating;
4368         } else if (strcmp(player2Name, white) == 0 &&
4369                    strcmp(player1Name, black) == 0) {
4370             if (appData.debugMode)
4371               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4372                       player2Rating, player1Rating);
4373             gameInfo.whiteRating = player2Rating;
4374             gameInfo.blackRating = player1Rating;
4375         }
4376         player1Name[0] = player2Name[0] = NULLCHAR;
4377
4378         /* Silence shouts if requested */
4379         if (appData.quietPlay &&
4380             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4381             SendToICS(ics_prefix);
4382             SendToICS("set shout 0\n");
4383         }
4384     }
4385
4386     /* Deal with midgame name changes */
4387     if (!newGame) {
4388         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4389             if (gameInfo.white) free(gameInfo.white);
4390             gameInfo.white = StrSave(white);
4391         }
4392         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4393             if (gameInfo.black) free(gameInfo.black);
4394             gameInfo.black = StrSave(black);
4395         }
4396     }
4397
4398     /* Throw away game result if anything actually changes in examine mode */
4399     if (gameMode == IcsExamining && !newGame) {
4400         gameInfo.result = GameUnfinished;
4401         if (gameInfo.resultDetails != NULL) {
4402             free(gameInfo.resultDetails);
4403             gameInfo.resultDetails = NULL;
4404         }
4405     }
4406
4407     /* In pausing && IcsExamining mode, we ignore boards coming
4408        in if they are in a different variation than we are. */
4409     if (pauseExamInvalid) return;
4410     if (pausing && gameMode == IcsExamining) {
4411         if (moveNum <= pauseExamForwardMostMove) {
4412             pauseExamInvalid = TRUE;
4413             forwardMostMove = pauseExamForwardMostMove;
4414             return;
4415         }
4416     }
4417
4418   if (appData.debugMode) {
4419     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4420   }
4421     /* Parse the board */
4422     for (k = 0; k < ranks; k++) {
4423       for (j = 0; j < files; j++)
4424         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4425       if(gameInfo.holdingsWidth > 1) {
4426            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4427            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4428       }
4429     }
4430     CopyBoard(boards[moveNum], board);
4431     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4432     if (moveNum == 0) {
4433         startedFromSetupPosition =
4434           !CompareBoards(board, initialPosition);
4435         if(startedFromSetupPosition)
4436             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4437     }
4438
4439     /* [HGM] Set castling rights. Take the outermost Rooks,
4440        to make it also work for FRC opening positions. Note that board12
4441        is really defective for later FRC positions, as it has no way to
4442        indicate which Rook can castle if they are on the same side of King.
4443        For the initial position we grant rights to the outermost Rooks,
4444        and remember thos rights, and we then copy them on positions
4445        later in an FRC game. This means WB might not recognize castlings with
4446        Rooks that have moved back to their original position as illegal,
4447        but in ICS mode that is not its job anyway.
4448     */
4449     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4450     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4451
4452         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4453             if(board[0][i] == WhiteRook) j = i;
4454         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4455         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4456             if(board[0][i] == WhiteRook) j = i;
4457         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4458         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4459             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4460         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4461         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4462             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4463         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4464
4465         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4466         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4467             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4468         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4469             if(board[BOARD_HEIGHT-1][k] == bKing)
4470                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4471         if(gameInfo.variant == VariantTwoKings) {
4472             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4473             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4474             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4475         }
4476     } else { int r;
4477         r = boards[moveNum][CASTLING][0] = initialRights[0];
4478         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4479         r = boards[moveNum][CASTLING][1] = initialRights[1];
4480         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4481         r = boards[moveNum][CASTLING][3] = initialRights[3];
4482         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4483         r = boards[moveNum][CASTLING][4] = initialRights[4];
4484         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4485         /* wildcastle kludge: always assume King has rights */
4486         r = boards[moveNum][CASTLING][2] = initialRights[2];
4487         r = boards[moveNum][CASTLING][5] = initialRights[5];
4488     }
4489     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4490     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4491
4492
4493     if (ics_getting_history == H_GOT_REQ_HEADER ||
4494         ics_getting_history == H_GOT_UNREQ_HEADER) {
4495         /* This was an initial position from a move list, not
4496            the current position */
4497         return;
4498     }
4499
4500     /* Update currentMove and known move number limits */
4501     newMove = newGame || moveNum > forwardMostMove;
4502
4503     if (newGame) {
4504         forwardMostMove = backwardMostMove = currentMove = moveNum;
4505         if (gameMode == IcsExamining && moveNum == 0) {
4506           /* Workaround for ICS limitation: we are not told the wild
4507              type when starting to examine a game.  But if we ask for
4508              the move list, the move list header will tell us */
4509             ics_getting_history = H_REQUESTED;
4510             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4511             SendToICS(str);
4512         }
4513     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4514                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4515 #if ZIPPY
4516         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4517         /* [HGM] applied this also to an engine that is silently watching        */
4518         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4519             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4520             gameInfo.variant == currentlyInitializedVariant) {
4521           takeback = forwardMostMove - moveNum;
4522           for (i = 0; i < takeback; i++) {
4523             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4524             SendToProgram("undo\n", &first);
4525           }
4526         }
4527 #endif
4528
4529         forwardMostMove = moveNum;
4530         if (!pausing || currentMove > forwardMostMove)
4531           currentMove = forwardMostMove;
4532     } else {
4533         /* New part of history that is not contiguous with old part */
4534         if (pausing && gameMode == IcsExamining) {
4535             pauseExamInvalid = TRUE;
4536             forwardMostMove = pauseExamForwardMostMove;
4537             return;
4538         }
4539         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4540 #if ZIPPY
4541             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4542                 // [HGM] when we will receive the move list we now request, it will be
4543                 // fed to the engine from the first move on. So if the engine is not
4544                 // in the initial position now, bring it there.
4545                 InitChessProgram(&first, 0);
4546             }
4547 #endif
4548             ics_getting_history = H_REQUESTED;
4549             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550             SendToICS(str);
4551         }
4552         forwardMostMove = backwardMostMove = currentMove = moveNum;
4553     }
4554
4555     /* Update the clocks */
4556     if (strchr(elapsed_time, '.')) {
4557       /* Time is in ms */
4558       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4559       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4560     } else {
4561       /* Time is in seconds */
4562       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4563       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4564     }
4565
4566
4567 #if ZIPPY
4568     if (appData.zippyPlay && newGame &&
4569         gameMode != IcsObserving && gameMode != IcsIdle &&
4570         gameMode != IcsExamining)
4571       ZippyFirstBoard(moveNum, basetime, increment);
4572 #endif
4573
4574     /* Put the move on the move list, first converting
4575        to canonical algebraic form. */
4576     if (moveNum > 0) {
4577   if (appData.debugMode) {
4578     if (appData.debugMode) { int f = forwardMostMove;
4579         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4580                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4581                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4582     }
4583     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4584     fprintf(debugFP, "moveNum = %d\n", moveNum);
4585     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4586     setbuf(debugFP, NULL);
4587   }
4588         if (moveNum <= backwardMostMove) {
4589             /* We don't know what the board looked like before
4590                this move.  Punt. */
4591           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4592             strcat(parseList[moveNum - 1], " ");
4593             strcat(parseList[moveNum - 1], elapsed_time);
4594             moveList[moveNum - 1][0] = NULLCHAR;
4595         } else if (strcmp(move_str, "none") == 0) {
4596             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4597             /* Again, we don't know what the board looked like;
4598                this is really the start of the game. */
4599             parseList[moveNum - 1][0] = NULLCHAR;
4600             moveList[moveNum - 1][0] = NULLCHAR;
4601             backwardMostMove = moveNum;
4602             startedFromSetupPosition = TRUE;
4603             fromX = fromY = toX = toY = -1;
4604         } else {
4605           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4606           //                 So we parse the long-algebraic move string in stead of the SAN move
4607           int valid; char buf[MSG_SIZ], *prom;
4608
4609           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4610                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4611           // str looks something like "Q/a1-a2"; kill the slash
4612           if(str[1] == '/')
4613             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4614           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4615           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4616                 strcat(buf, prom); // long move lacks promo specification!
4617           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4618                 if(appData.debugMode)
4619                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4620                 safeStrCpy(move_str, buf, MSG_SIZ);
4621           }
4622           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4623                                 &fromX, &fromY, &toX, &toY, &promoChar)
4624                || ParseOneMove(buf, moveNum - 1, &moveType,
4625                                 &fromX, &fromY, &toX, &toY, &promoChar);
4626           // end of long SAN patch
4627           if (valid) {
4628             (void) CoordsToAlgebraic(boards[moveNum - 1],
4629                                      PosFlags(moveNum - 1),
4630                                      fromY, fromX, toY, toX, promoChar,
4631                                      parseList[moveNum-1]);
4632             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4633               case MT_NONE:
4634               case MT_STALEMATE:
4635               default:
4636                 break;
4637               case MT_CHECK:
4638                 if(gameInfo.variant != VariantShogi)
4639                     strcat(parseList[moveNum - 1], "+");
4640                 break;
4641               case MT_CHECKMATE:
4642               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4643                 strcat(parseList[moveNum - 1], "#");
4644                 break;
4645             }
4646             strcat(parseList[moveNum - 1], " ");
4647             strcat(parseList[moveNum - 1], elapsed_time);
4648             /* currentMoveString is set as a side-effect of ParseOneMove */
4649             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4650             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4651             strcat(moveList[moveNum - 1], "\n");
4652
4653             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4654                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4655               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4656                 ChessSquare old, new = boards[moveNum][k][j];
4657                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4658                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4659                   if(old == new) continue;
4660                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4661                   else if(new == WhiteWazir || new == BlackWazir) {
4662                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4663                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4664                       else boards[moveNum][k][j] = old; // preserve type of Gold
4665                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4666                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4667               }
4668           } else {
4669             /* Move from ICS was illegal!?  Punt. */
4670             if (appData.debugMode) {
4671               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4672               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4673             }
4674             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4675             strcat(parseList[moveNum - 1], " ");
4676             strcat(parseList[moveNum - 1], elapsed_time);
4677             moveList[moveNum - 1][0] = NULLCHAR;
4678             fromX = fromY = toX = toY = -1;
4679           }
4680         }
4681   if (appData.debugMode) {
4682     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4683     setbuf(debugFP, NULL);
4684   }
4685
4686 #if ZIPPY
4687         /* Send move to chess program (BEFORE animating it). */
4688         if (appData.zippyPlay && !newGame && newMove &&
4689            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4690
4691             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4692                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4693                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4694                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4695                             move_str);
4696                     DisplayError(str, 0);
4697                 } else {
4698                     if (first.sendTime) {
4699                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4700                     }
4701                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4702                     if (firstMove && !bookHit) {
4703                         firstMove = FALSE;
4704                         if (first.useColors) {
4705                           SendToProgram(gameMode == IcsPlayingWhite ?
4706                                         "white\ngo\n" :
4707                                         "black\ngo\n", &first);
4708                         } else {
4709                           SendToProgram("go\n", &first);
4710                         }
4711                         first.maybeThinking = TRUE;
4712                     }
4713                 }
4714             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4715               if (moveList[moveNum - 1][0] == NULLCHAR) {
4716                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4717                 DisplayError(str, 0);
4718               } else {
4719                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4720                 SendMoveToProgram(moveNum - 1, &first);
4721               }
4722             }
4723         }
4724 #endif
4725     }
4726
4727     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4728         /* If move comes from a remote source, animate it.  If it
4729            isn't remote, it will have already been animated. */
4730         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4731             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4732         }
4733         if (!pausing && appData.highlightLastMove) {
4734             SetHighlights(fromX, fromY, toX, toY);
4735         }
4736     }
4737
4738     /* Start the clocks */
4739     whiteFlag = blackFlag = FALSE;
4740     appData.clockMode = !(basetime == 0 && increment == 0);
4741     if (ticking == 0) {
4742       ics_clock_paused = TRUE;
4743       StopClocks();
4744     } else if (ticking == 1) {
4745       ics_clock_paused = FALSE;
4746     }
4747     if (gameMode == IcsIdle ||
4748         relation == RELATION_OBSERVING_STATIC ||
4749         relation == RELATION_EXAMINING ||
4750         ics_clock_paused)
4751       DisplayBothClocks();
4752     else
4753       StartClocks();
4754
4755     /* Display opponents and material strengths */
4756     if (gameInfo.variant != VariantBughouse &&
4757         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4758         if (tinyLayout || smallLayout) {
4759             if(gameInfo.variant == VariantNormal)
4760               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4761                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4762                     basetime, increment);
4763             else
4764               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment, (int) gameInfo.variant);
4767         } else {
4768             if(gameInfo.variant == VariantNormal)
4769               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4770                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4771                     basetime, increment);
4772             else
4773               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4774                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4775                     basetime, increment, VariantName(gameInfo.variant));
4776         }
4777         DisplayTitle(str);
4778   if (appData.debugMode) {
4779     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4780   }
4781     }
4782
4783
4784     /* Display the board */
4785     if (!pausing && !appData.noGUI) {
4786
4787       if (appData.premove)
4788           if (!gotPremove ||
4789              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4790              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4791               ClearPremoveHighlights();
4792
4793       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4794         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4795       DrawPosition(j, boards[currentMove]);
4796
4797       DisplayMove(moveNum - 1);
4798       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4799             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4800               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4801         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4802       }
4803     }
4804
4805     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4806 #if ZIPPY
4807     if(bookHit) { // [HGM] book: simulate book reply
4808         static char bookMove[MSG_SIZ]; // a bit generous?
4809
4810         programStats.nodes = programStats.depth = programStats.time =
4811         programStats.score = programStats.got_only_move = 0;
4812         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4813
4814         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4815         strcat(bookMove, bookHit);
4816         HandleMachineMove(bookMove, &first);
4817     }
4818 #endif
4819 }
4820
4821 void
4822 GetMoveListEvent()
4823 {
4824     char buf[MSG_SIZ];
4825     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4826         ics_getting_history = H_REQUESTED;
4827         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4828         SendToICS(buf);
4829     }
4830 }
4831
4832 void
4833 AnalysisPeriodicEvent(force)
4834      int force;
4835 {
4836     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4837          && !force) || !appData.periodicUpdates)
4838       return;
4839
4840     /* Send . command to Crafty to collect stats */
4841     SendToProgram(".\n", &first);
4842
4843     /* Don't send another until we get a response (this makes
4844        us stop sending to old Crafty's which don't understand
4845        the "." command (sending illegal cmds resets node count & time,
4846        which looks bad)) */
4847     programStats.ok_to_send = 0;
4848 }
4849
4850 void ics_update_width(new_width)
4851         int new_width;
4852 {
4853         ics_printf("set width %d\n", new_width);
4854 }
4855
4856 void
4857 SendMoveToProgram(moveNum, cps)
4858      int moveNum;
4859      ChessProgramState *cps;
4860 {
4861     char buf[MSG_SIZ];
4862
4863     if (cps->useUsermove) {
4864       SendToProgram("usermove ", cps);
4865     }
4866     if (cps->useSAN) {
4867       char *space;
4868       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4869         int len = space - parseList[moveNum];
4870         memcpy(buf, parseList[moveNum], len);
4871         buf[len++] = '\n';
4872         buf[len] = NULLCHAR;
4873       } else {
4874         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4875       }
4876       SendToProgram(buf, cps);
4877     } else {
4878       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4879         AlphaRank(moveList[moveNum], 4);
4880         SendToProgram(moveList[moveNum], cps);
4881         AlphaRank(moveList[moveNum], 4); // and back
4882       } else
4883       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4884        * the engine. It would be nice to have a better way to identify castle
4885        * moves here. */
4886       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4887                                                                          && cps->useOOCastle) {
4888         int fromX = moveList[moveNum][0] - AAA;
4889         int fromY = moveList[moveNum][1] - ONE;
4890         int toX = moveList[moveNum][2] - AAA;
4891         int toY = moveList[moveNum][3] - ONE;
4892         if((boards[moveNum][fromY][fromX] == WhiteKing
4893             && boards[moveNum][toY][toX] == WhiteRook)
4894            || (boards[moveNum][fromY][fromX] == BlackKing
4895                && boards[moveNum][toY][toX] == BlackRook)) {
4896           if(toX > fromX) SendToProgram("O-O\n", cps);
4897           else SendToProgram("O-O-O\n", cps);
4898         }
4899         else SendToProgram(moveList[moveNum], cps);
4900       } else
4901       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4902         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4903           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4904                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4905         } else
4906           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4907                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4908         SendToProgram(buf, cps);
4909       }
4910       else SendToProgram(moveList[moveNum], cps);
4911       /* End of additions by Tord */
4912     }
4913
4914     /* [HGM] setting up the opening has brought engine in force mode! */
4915     /*       Send 'go' if we are in a mode where machine should play. */
4916     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4917         (gameMode == TwoMachinesPlay   ||
4918 #if ZIPPY
4919          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4920 #endif
4921          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4922         SendToProgram("go\n", cps);
4923   if (appData.debugMode) {
4924     fprintf(debugFP, "(extra)\n");
4925   }
4926     }
4927     setboardSpoiledMachineBlack = 0;
4928 }
4929
4930 void
4931 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4932      ChessMove moveType;
4933      int fromX, fromY, toX, toY;
4934      char promoChar;
4935 {
4936     char user_move[MSG_SIZ];
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\n");
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\n");
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(rf, ff, rt, ft, promoChar, move)
5073      int rf, ff, rt, ft;
5074      char promoChar;
5075      char move[7];
5076 {
5077     if (rf == DROP_RANK) {
5078       sprintf(move, "%c@%c%c\n",
5079                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5080     } else {
5081         if (promoChar == 'x' || promoChar == NULLCHAR) {
5082           sprintf(move, "%c%c%c%c\n",
5083                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5084         } else {
5085             sprintf(move, "%c%c%c%c%c\n",
5086                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5087         }
5088     }
5089 }
5090
5091 void
5092 ProcessICSInitScript(f)
5093      FILE *f;
5094 {
5095     char buf[MSG_SIZ];
5096
5097     while (fgets(buf, MSG_SIZ, f)) {
5098         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5099     }
5100
5101     fclose(f);
5102 }
5103
5104
5105 static int lastX, lastY, selectFlag, dragging;
5106
5107 void
5108 Sweep(int step)
5109 {
5110     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5111     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5112     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5113     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5114     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5115     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5116     do {
5117         promoSweep -= step;
5118         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5119         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5120         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5121         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5122         if(!step) step = 1;
5123     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5124             appData.testLegality && (promoSweep == king ||
5125             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5126     ChangeDragPiece(promoSweep);
5127 }
5128
5129 int PromoScroll(int x, int y)
5130 {
5131   int step = 0;
5132
5133   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5134   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5135   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5136   if(!step) return FALSE;
5137   lastX = x; lastY = y;
5138   if((promoSweep < BlackPawn) == flipView) step = -step;
5139   if(step > 0) selectFlag = 1;
5140   if(!selectFlag) Sweep(step);
5141   return FALSE;
5142 }
5143
5144 void
5145 NextPiece(int step)
5146 {
5147     ChessSquare piece = boards[currentMove][toY][toX];
5148     do {
5149         pieceSweep -= step;
5150         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5151         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5152         if(!step) step = -1;
5153     } while(PieceToChar(pieceSweep) == '.');
5154     boards[currentMove][toY][toX] = pieceSweep;
5155     DrawPosition(FALSE, boards[currentMove]);
5156     boards[currentMove][toY][toX] = piece;
5157 }
5158 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5159 void
5160 AlphaRank(char *move, int n)
5161 {
5162 //    char *p = move, c; int x, y;
5163
5164     if (appData.debugMode) {
5165         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5166     }
5167
5168     if(move[1]=='*' &&
5169        move[2]>='0' && move[2]<='9' &&
5170        move[3]>='a' && move[3]<='x'    ) {
5171         move[1] = '@';
5172         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5173         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5174     } else
5175     if(move[0]>='0' && move[0]<='9' &&
5176        move[1]>='a' && move[1]<='x' &&
5177        move[2]>='0' && move[2]<='9' &&
5178        move[3]>='a' && move[3]<='x'    ) {
5179         /* input move, Shogi -> normal */
5180         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5181         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5182         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5183         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5184     } else
5185     if(move[1]=='@' &&
5186        move[3]>='0' && move[3]<='9' &&
5187        move[2]>='a' && move[2]<='x'    ) {
5188         move[1] = '*';
5189         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5190         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5191     } else
5192     if(
5193        move[0]>='a' && move[0]<='x' &&
5194        move[3]>='0' && move[3]<='9' &&
5195        move[2]>='a' && move[2]<='x'    ) {
5196          /* output move, normal -> Shogi */
5197         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5198         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5199         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5200         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5201         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5202     }
5203     if (appData.debugMode) {
5204         fprintf(debugFP, "   out = '%s'\n", move);
5205     }
5206 }
5207
5208 char yy_textstr[8000];
5209
5210 /* Parser for moves from gnuchess, ICS, or user typein box */
5211 Boolean
5212 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5213      char *move;
5214      int moveNum;
5215      ChessMove *moveType;
5216      int *fromX, *fromY, *toX, *toY;
5217      char *promoChar;
5218 {
5219     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5220
5221     switch (*moveType) {
5222       case WhitePromotion:
5223       case BlackPromotion:
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226       case NormalMove:
5227       case WhiteCapturesEnPassant:
5228       case BlackCapturesEnPassant:
5229       case WhiteKingSideCastle:
5230       case WhiteQueenSideCastle:
5231       case BlackKingSideCastle:
5232       case BlackQueenSideCastle:
5233       case WhiteKingSideCastleWild:
5234       case WhiteQueenSideCastleWild:
5235       case BlackKingSideCastleWild:
5236       case BlackQueenSideCastleWild:
5237       /* Code added by Tord: */
5238       case WhiteHSideCastleFR:
5239       case WhiteASideCastleFR:
5240       case BlackHSideCastleFR:
5241       case BlackASideCastleFR:
5242       /* End of code added by Tord */
5243       case IllegalMove:         /* bug or odd chess variant */
5244         *fromX = currentMoveString[0] - AAA;
5245         *fromY = currentMoveString[1] - ONE;
5246         *toX = currentMoveString[2] - AAA;
5247         *toY = currentMoveString[3] - ONE;
5248         *promoChar = currentMoveString[4];
5249         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5250             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5251     if (appData.debugMode) {
5252         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5253     }
5254             *fromX = *fromY = *toX = *toY = 0;
5255             return FALSE;
5256         }
5257         if (appData.testLegality) {
5258           return (*moveType != IllegalMove);
5259         } else {
5260           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5261                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5262         }
5263
5264       case WhiteDrop:
5265       case BlackDrop:
5266         *fromX = *moveType == WhiteDrop ?
5267           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5268           (int) CharToPiece(ToLower(currentMoveString[0]));
5269         *fromY = DROP_RANK;
5270         *toX = currentMoveString[2] - AAA;
5271         *toY = currentMoveString[3] - ONE;
5272         *promoChar = NULLCHAR;
5273         return TRUE;
5274
5275       case AmbiguousMove:
5276       case ImpossibleMove:
5277       case EndOfFile:
5278       case ElapsedTime:
5279       case Comment:
5280       case PGNTag:
5281       case NAG:
5282       case WhiteWins:
5283       case BlackWins:
5284       case GameIsDrawn:
5285       default:
5286     if (appData.debugMode) {
5287         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5288     }
5289         /* bug? */
5290         *fromX = *fromY = *toX = *toY = 0;
5291         *promoChar = NULLCHAR;
5292         return FALSE;
5293     }
5294 }
5295
5296 Boolean pushed = FALSE;
5297 char *lastParseAttempt;
5298
5299 void
5300 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5301 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5302   int fromX, fromY, toX, toY; char promoChar;
5303   ChessMove moveType;
5304   Boolean valid;
5305   int nr = 0;
5306
5307   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5308     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5309     pushed = TRUE;
5310   }
5311   endPV = forwardMostMove;
5312   do {
5313     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5314     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5315     lastParseAttempt = pv;
5316     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5317 if(appData.debugMode){
5318 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);
5319 }
5320     if(!valid && nr == 0 &&
5321        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5322         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5323         // Hande case where played move is different from leading PV move
5324         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5325         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5326         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5327         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5328           endPV += 2; // if position different, keep this
5329           moveList[endPV-1][0] = fromX + AAA;
5330           moveList[endPV-1][1] = fromY + ONE;
5331           moveList[endPV-1][2] = toX + AAA;
5332           moveList[endPV-1][3] = toY + ONE;
5333           parseList[endPV-1][0] = NULLCHAR;
5334           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5335         }
5336       }
5337     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5338     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5339     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5340     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5341         valid++; // allow comments in PV
5342         continue;
5343     }
5344     nr++;
5345     if(endPV+1 > framePtr) break; // no space, truncate
5346     if(!valid) break;
5347     endPV++;
5348     CopyBoard(boards[endPV], boards[endPV-1]);
5349     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5350     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5351     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5352     CoordsToAlgebraic(boards[endPV - 1],
5353                              PosFlags(endPV - 1),
5354                              fromY, fromX, toY, toX, promoChar,
5355                              parseList[endPV - 1]);
5356   } while(valid);
5357   if(atEnd == 2) return; // used hidden, for PV conversion
5358   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5359   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5360   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5361                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5362   DrawPosition(TRUE, boards[currentMove]);
5363 }
5364
5365 int
5366 MultiPV(ChessProgramState *cps)
5367 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5368         int i;
5369         for(i=0; i<cps->nrOptions; i++)
5370             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5371                 return i;
5372         return -1;
5373 }
5374
5375 Boolean
5376 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5377 {
5378         int startPV, multi, lineStart, origIndex = index;
5379         char *p, buf2[MSG_SIZ];
5380
5381         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5382         lastX = x; lastY = y;
5383         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5384         lineStart = startPV = index;
5385         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5386         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5387         index = startPV;
5388         do{ while(buf[index] && buf[index] != '\n') index++;
5389         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5390         buf[index] = 0;
5391         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5392                 int n = first.option[multi].value;
5393                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5394                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5395                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5396                 first.option[multi].value = n;
5397                 *start = *end = 0;
5398                 return FALSE;
5399         }
5400         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5401         *start = startPV; *end = index-1;
5402         return TRUE;
5403 }
5404
5405 char *
5406 PvToSAN(char *pv)
5407 {
5408         static char buf[10*MSG_SIZ];
5409         int i, k=0, savedEnd=endPV;
5410         *buf = NULLCHAR;
5411         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5412         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5413         for(i = forwardMostMove; i<endPV; i++){
5414             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5415             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5416             k += strlen(buf+k);
5417         }
5418         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5419         if(forwardMostMove < savedEnd) PopInner(0);
5420         endPV = savedEnd;
5421         return buf;
5422 }
5423
5424 Boolean
5425 LoadPV(int x, int y)
5426 { // called on right mouse click to load PV
5427   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5428   lastX = x; lastY = y;
5429   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5430   return TRUE;
5431 }
5432
5433 void
5434 UnLoadPV()
5435 {
5436   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5437   if(endPV < 0) return;
5438   endPV = -1;
5439   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5440         Boolean saveAnimate = appData.animate;
5441         if(pushed) {
5442             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5443                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5444             } else storedGames--; // abandon shelved tail of original game
5445         }
5446         pushed = FALSE;
5447         forwardMostMove = currentMove;
5448         currentMove = oldFMM;
5449         appData.animate = FALSE;
5450         ToNrEvent(forwardMostMove);
5451         appData.animate = saveAnimate;
5452   }
5453   currentMove = forwardMostMove;
5454   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5455   ClearPremoveHighlights();
5456   DrawPosition(TRUE, boards[currentMove]);
5457 }
5458
5459 void
5460 MovePV(int x, int y, int h)
5461 { // step through PV based on mouse coordinates (called on mouse move)
5462   int margin = h>>3, step = 0;
5463
5464   // we must somehow check if right button is still down (might be released off board!)
5465   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5466   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5467   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5468   if(!step) return;
5469   lastX = x; lastY = y;
5470
5471   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5472   if(endPV < 0) return;
5473   if(y < margin) step = 1; else
5474   if(y > h - margin) step = -1;
5475   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5476   currentMove += step;
5477   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5478   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5479                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5480   DrawPosition(FALSE, boards[currentMove]);
5481 }
5482
5483
5484 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5485 // All positions will have equal probability, but the current method will not provide a unique
5486 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5487 #define DARK 1
5488 #define LITE 2
5489 #define ANY 3
5490
5491 int squaresLeft[4];
5492 int piecesLeft[(int)BlackPawn];
5493 int seed, nrOfShuffles;
5494
5495 void GetPositionNumber()
5496 {       // sets global variable seed
5497         int i;
5498
5499         seed = appData.defaultFrcPosition;
5500         if(seed < 0) { // randomize based on time for negative FRC position numbers
5501                 for(i=0; i<50; i++) seed += random();
5502                 seed = random() ^ random() >> 8 ^ random() << 8;
5503                 if(seed<0) seed = -seed;
5504         }
5505 }
5506
5507 int put(Board board, int pieceType, int rank, int n, int shade)
5508 // put the piece on the (n-1)-th empty squares of the given shade
5509 {
5510         int i;
5511
5512         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5513                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5514                         board[rank][i] = (ChessSquare) pieceType;
5515                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5516                         squaresLeft[ANY]--;
5517                         piecesLeft[pieceType]--;
5518                         return i;
5519                 }
5520         }
5521         return -1;
5522 }
5523
5524
5525 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5526 // calculate where the next piece goes, (any empty square), and put it there
5527 {
5528         int i;
5529
5530         i = seed % squaresLeft[shade];
5531         nrOfShuffles *= squaresLeft[shade];
5532         seed /= squaresLeft[shade];
5533         put(board, pieceType, rank, i, shade);
5534 }
5535
5536 void AddTwoPieces(Board board, int pieceType, int rank)
5537 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5538 {
5539         int i, n=squaresLeft[ANY], j=n-1, k;
5540
5541         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5542         i = seed % k;  // pick one
5543         nrOfShuffles *= k;
5544         seed /= k;
5545         while(i >= j) i -= j--;
5546         j = n - 1 - j; i += j;
5547         put(board, pieceType, rank, j, ANY);
5548         put(board, pieceType, rank, i, ANY);
5549 }
5550
5551 void SetUpShuffle(Board board, int number)
5552 {
5553         int i, p, first=1;
5554
5555         GetPositionNumber(); nrOfShuffles = 1;
5556
5557         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5558         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5559         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5560
5561         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5562
5563         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5564             p = (int) board[0][i];
5565             if(p < (int) BlackPawn) piecesLeft[p] ++;
5566             board[0][i] = EmptySquare;
5567         }
5568
5569         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5570             // shuffles restricted to allow normal castling put KRR first
5571             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5572                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5573             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5574                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5575             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5576                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5577             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5578                 put(board, WhiteRook, 0, 0, ANY);
5579             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5580         }
5581
5582         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5583             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5584             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5585                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5586                 while(piecesLeft[p] >= 2) {
5587                     AddOnePiece(board, p, 0, LITE);
5588                     AddOnePiece(board, p, 0, DARK);
5589                 }
5590                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5591             }
5592
5593         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5594             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5595             // but we leave King and Rooks for last, to possibly obey FRC restriction
5596             if(p == (int)WhiteRook) continue;
5597             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5598             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5599         }
5600
5601         // now everything is placed, except perhaps King (Unicorn) and Rooks
5602
5603         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5604             // Last King gets castling rights
5605             while(piecesLeft[(int)WhiteUnicorn]) {
5606                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5607                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5608             }
5609
5610             while(piecesLeft[(int)WhiteKing]) {
5611                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5612                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5613             }
5614
5615
5616         } else {
5617             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5618             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5619         }
5620
5621         // Only Rooks can be left; simply place them all
5622         while(piecesLeft[(int)WhiteRook]) {
5623                 i = put(board, WhiteRook, 0, 0, ANY);
5624                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5625                         if(first) {
5626                                 first=0;
5627                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5628                         }
5629                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5630                 }
5631         }
5632         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5633             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5634         }
5635
5636         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5637 }
5638
5639 int SetCharTable( char *table, const char * map )
5640 /* [HGM] moved here from winboard.c because of its general usefulness */
5641 /*       Basically a safe strcpy that uses the last character as King */
5642 {
5643     int result = FALSE; int NrPieces;
5644
5645     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5646                     && NrPieces >= 12 && !(NrPieces&1)) {
5647         int i; /* [HGM] Accept even length from 12 to 34 */
5648
5649         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5650         for( i=0; i<NrPieces/2-1; i++ ) {
5651             table[i] = map[i];
5652             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5653         }
5654         table[(int) WhiteKing]  = map[NrPieces/2-1];
5655         table[(int) BlackKing]  = map[NrPieces-1];
5656
5657         result = TRUE;
5658     }
5659
5660     return result;
5661 }
5662
5663 void 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(redraw)
5701      int redraw;
5702 {
5703     ChessSquare (* pieces)[BOARD_FILES];
5704     int i, j, pawnRow, overrule,
5705     oldx = gameInfo.boardWidth,
5706     oldy = gameInfo.boardHeight,
5707     oldh = gameInfo.holdingsWidth;
5708     static int oldv;
5709
5710     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5711
5712     /* [AS] Initialize pv info list [HGM] and game status */
5713     {
5714         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5715             pvInfoList[i].depth = 0;
5716             boards[i][EP_STATUS] = EP_NONE;
5717             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5718         }
5719
5720         initialRulePlies = 0; /* 50-move counter start */
5721
5722         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5723         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5724     }
5725
5726
5727     /* [HGM] logic here is completely changed. In stead of full positions */
5728     /* the initialized data only consist of the two backranks. The switch */
5729     /* selects which one we will use, which is than copied to the Board   */
5730     /* initialPosition, which for the rest is initialized by Pawns and    */
5731     /* empty squares. This initial position is then copied to boards[0],  */
5732     /* possibly after shuffling, so that it remains available.            */
5733
5734     gameInfo.holdingsWidth = 0; /* default board sizes */
5735     gameInfo.boardWidth    = 8;
5736     gameInfo.boardHeight   = 8;
5737     gameInfo.holdingsSize  = 0;
5738     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5739     for(i=0; i<BOARD_FILES-2; i++)
5740       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5741     initialPosition[EP_STATUS] = EP_NONE;
5742     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5743     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5744          SetCharTable(pieceNickName, appData.pieceNickNames);
5745     else SetCharTable(pieceNickName, "............");
5746     pieces = FIDEArray;
5747
5748     switch (gameInfo.variant) {
5749     case VariantFischeRandom:
5750       shuffleOpenings = TRUE;
5751     default:
5752       break;
5753     case VariantShatranj:
5754       pieces = ShatranjArray;
5755       nrCastlingRights = 0;
5756       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5757       break;
5758     case VariantMakruk:
5759       pieces = makrukArray;
5760       nrCastlingRights = 0;
5761       startedFromSetupPosition = TRUE;
5762       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5763       break;
5764     case VariantTwoKings:
5765       pieces = twoKingsArray;
5766       break;
5767     case VariantGrand:
5768       pieces = GrandArray;
5769       nrCastlingRights = 0;
5770       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5771       gameInfo.boardWidth = 10;
5772       gameInfo.boardHeight = 10;
5773       gameInfo.holdingsSize = 7;
5774       break;
5775     case VariantCapaRandom:
5776       shuffleOpenings = TRUE;
5777     case VariantCapablanca:
5778       pieces = CapablancaArray;
5779       gameInfo.boardWidth = 10;
5780       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5781       break;
5782     case VariantGothic:
5783       pieces = GothicArray;
5784       gameInfo.boardWidth = 10;
5785       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5786       break;
5787     case VariantSChess:
5788       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5789       gameInfo.holdingsSize = 7;
5790       break;
5791     case VariantJanus:
5792       pieces = JanusArray;
5793       gameInfo.boardWidth = 10;
5794       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5795       nrCastlingRights = 6;
5796         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5797         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5798         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5799         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5800         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5801         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5802       break;
5803     case VariantFalcon:
5804       pieces = FalconArray;
5805       gameInfo.boardWidth = 10;
5806       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5807       break;
5808     case VariantXiangqi:
5809       pieces = XiangqiArray;
5810       gameInfo.boardWidth  = 9;
5811       gameInfo.boardHeight = 10;
5812       nrCastlingRights = 0;
5813       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5814       break;
5815     case VariantShogi:
5816       pieces = ShogiArray;
5817       gameInfo.boardWidth  = 9;
5818       gameInfo.boardHeight = 9;
5819       gameInfo.holdingsSize = 7;
5820       nrCastlingRights = 0;
5821       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5822       break;
5823     case VariantCourier:
5824       pieces = CourierArray;
5825       gameInfo.boardWidth  = 12;
5826       nrCastlingRights = 0;
5827       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5828       break;
5829     case VariantKnightmate:
5830       pieces = KnightmateArray;
5831       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5832       break;
5833     case VariantSpartan:
5834       pieces = SpartanArray;
5835       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5836       break;
5837     case VariantFairy:
5838       pieces = fairyArray;
5839       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5840       break;
5841     case VariantGreat:
5842       pieces = GreatArray;
5843       gameInfo.boardWidth = 10;
5844       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5845       gameInfo.holdingsSize = 8;
5846       break;
5847     case VariantSuper:
5848       pieces = FIDEArray;
5849       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5850       gameInfo.holdingsSize = 8;
5851       startedFromSetupPosition = TRUE;
5852       break;
5853     case VariantCrazyhouse:
5854     case VariantBughouse:
5855       pieces = FIDEArray;
5856       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5857       gameInfo.holdingsSize = 5;
5858       break;
5859     case VariantWildCastle:
5860       pieces = FIDEArray;
5861       /* !!?shuffle with kings guaranteed to be on d or e file */
5862       shuffleOpenings = 1;
5863       break;
5864     case VariantNoCastle:
5865       pieces = FIDEArray;
5866       nrCastlingRights = 0;
5867       /* !!?unconstrained back-rank shuffle */
5868       shuffleOpenings = 1;
5869       break;
5870     }
5871
5872     overrule = 0;
5873     if(appData.NrFiles >= 0) {
5874         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5875         gameInfo.boardWidth = appData.NrFiles;
5876     }
5877     if(appData.NrRanks >= 0) {
5878         gameInfo.boardHeight = appData.NrRanks;
5879     }
5880     if(appData.holdingsSize >= 0) {
5881         i = appData.holdingsSize;
5882         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5883         gameInfo.holdingsSize = i;
5884     }
5885     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5886     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5887         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5888
5889     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5890     if(pawnRow < 1) pawnRow = 1;
5891     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5892
5893     /* User pieceToChar list overrules defaults */
5894     if(appData.pieceToCharTable != NULL)
5895         SetCharTable(pieceToChar, appData.pieceToCharTable);
5896
5897     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5898
5899         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5900             s = (ChessSquare) 0; /* account holding counts in guard band */
5901         for( i=0; i<BOARD_HEIGHT; i++ )
5902             initialPosition[i][j] = s;
5903
5904         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5905         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5906         initialPosition[pawnRow][j] = WhitePawn;
5907         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5908         if(gameInfo.variant == VariantXiangqi) {
5909             if(j&1) {
5910                 initialPosition[pawnRow][j] =
5911                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5912                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5913                    initialPosition[2][j] = WhiteCannon;
5914                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5915                 }
5916             }
5917         }
5918         if(gameInfo.variant == VariantGrand) {
5919             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5920                initialPosition[0][j] = WhiteRook;
5921                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5922             }
5923         }
5924         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5925     }
5926     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5927
5928             j=BOARD_LEFT+1;
5929             initialPosition[1][j] = WhiteBishop;
5930             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5931             j=BOARD_RGHT-2;
5932             initialPosition[1][j] = WhiteRook;
5933             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5934     }
5935
5936     if( nrCastlingRights == -1) {
5937         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5938         /*       This sets default castling rights from none to normal corners   */
5939         /* Variants with other castling rights must set them themselves above    */
5940         nrCastlingRights = 6;
5941
5942         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5943         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5944         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5945         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5946         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5947         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5948      }
5949
5950      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5951      if(gameInfo.variant == VariantGreat) { // promotion commoners
5952         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5953         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5954         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5955         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5956      }
5957      if( gameInfo.variant == VariantSChess ) {
5958       initialPosition[1][0] = BlackMarshall;
5959       initialPosition[2][0] = BlackAngel;
5960       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5961       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5962       initialPosition[1][1] = initialPosition[2][1] = 
5963       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5964      }
5965   if (appData.debugMode) {
5966     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5967   }
5968     if(shuffleOpenings) {
5969         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5970         startedFromSetupPosition = TRUE;
5971     }
5972     if(startedFromPositionFile) {
5973       /* [HGM] loadPos: use PositionFile for every new game */
5974       CopyBoard(initialPosition, filePosition);
5975       for(i=0; i<nrCastlingRights; i++)
5976           initialRights[i] = filePosition[CASTLING][i];
5977       startedFromSetupPosition = TRUE;
5978     }
5979
5980     CopyBoard(boards[0], initialPosition);
5981
5982     if(oldx != gameInfo.boardWidth ||
5983        oldy != gameInfo.boardHeight ||
5984        oldv != gameInfo.variant ||
5985        oldh != gameInfo.holdingsWidth
5986                                          )
5987             InitDrawingSizes(-2 ,0);
5988
5989     oldv = gameInfo.variant;
5990     if (redraw)
5991       DrawPosition(TRUE, boards[currentMove]);
5992 }
5993
5994 void
5995 SendBoard(cps, moveNum)
5996      ChessProgramState *cps;
5997      int moveNum;
5998 {
5999     char message[MSG_SIZ];
6000
6001     if (cps->useSetboard) {
6002       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6003       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6004       SendToProgram(message, cps);
6005       free(fen);
6006
6007     } else {
6008       ChessSquare *bp;
6009       int i, j;
6010       /* Kludge to set black to move, avoiding the troublesome and now
6011        * deprecated "black" command.
6012        */
6013       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6014         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6015
6016       SendToProgram("edit\n", cps);
6017       SendToProgram("#\n", cps);
6018       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6019         bp = &boards[moveNum][i][BOARD_LEFT];
6020         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6021           if ((int) *bp < (int) BlackPawn) {
6022             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6023                     AAA + j, ONE + i);
6024             if(message[0] == '+' || message[0] == '~') {
6025               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6026                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6027                         AAA + j, ONE + i);
6028             }
6029             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6030                 message[1] = BOARD_RGHT   - 1 - j + '1';
6031                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6032             }
6033             SendToProgram(message, cps);
6034           }
6035         }
6036       }
6037
6038       SendToProgram("c\n", cps);
6039       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6040         bp = &boards[moveNum][i][BOARD_LEFT];
6041         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6042           if (((int) *bp != (int) EmptySquare)
6043               && ((int) *bp >= (int) BlackPawn)) {
6044             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6045                     AAA + j, ONE + i);
6046             if(message[0] == '+' || message[0] == '~') {
6047               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6048                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6049                         AAA + j, ONE + i);
6050             }
6051             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6052                 message[1] = BOARD_RGHT   - 1 - j + '1';
6053                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6054             }
6055             SendToProgram(message, cps);
6056           }
6057         }
6058       }
6059
6060       SendToProgram(".\n", cps);
6061     }
6062     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6063 }
6064
6065 ChessSquare
6066 DefaultPromoChoice(int white)
6067 {
6068     ChessSquare result;
6069     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6070         result = WhiteFerz; // no choice
6071     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6072         result= WhiteKing; // in Suicide Q is the last thing we want
6073     else if(gameInfo.variant == VariantSpartan)
6074         result = white ? WhiteQueen : WhiteAngel;
6075     else result = WhiteQueen;
6076     if(!white) result = WHITE_TO_BLACK result;
6077     return result;
6078 }
6079
6080 static int autoQueen; // [HGM] oneclick
6081
6082 int
6083 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6084 {
6085     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6086     /* [HGM] add Shogi promotions */
6087     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6088     ChessSquare piece;
6089     ChessMove moveType;
6090     Boolean premove;
6091
6092     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6093     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6094
6095     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6096       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6097         return FALSE;
6098
6099     piece = boards[currentMove][fromY][fromX];
6100     if(gameInfo.variant == VariantShogi) {
6101         promotionZoneSize = BOARD_HEIGHT/3;
6102         highestPromotingPiece = (int)WhiteFerz;
6103     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6104         promotionZoneSize = 3;
6105     }
6106
6107     // Treat Lance as Pawn when it is not representing Amazon
6108     if(gameInfo.variant != VariantSuper) {
6109         if(piece == WhiteLance) piece = WhitePawn; else
6110         if(piece == BlackLance) piece = BlackPawn;
6111     }
6112
6113     // next weed out all moves that do not touch the promotion zone at all
6114     if((int)piece >= BlackPawn) {
6115         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6116              return FALSE;
6117         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6118     } else {
6119         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6120            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6121     }
6122
6123     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6124
6125     // weed out mandatory Shogi promotions
6126     if(gameInfo.variant == VariantShogi) {
6127         if(piece >= BlackPawn) {
6128             if(toY == 0 && piece == BlackPawn ||
6129                toY == 0 && piece == BlackQueen ||
6130                toY <= 1 && piece == BlackKnight) {
6131                 *promoChoice = '+';
6132                 return FALSE;
6133             }
6134         } else {
6135             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6136                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6137                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6138                 *promoChoice = '+';
6139                 return FALSE;
6140             }
6141         }
6142     }
6143
6144     // weed out obviously illegal Pawn moves
6145     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6146         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6147         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6148         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6149         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6150         // note we are not allowed to test for valid (non-)capture, due to premove
6151     }
6152
6153     // we either have a choice what to promote to, or (in Shogi) whether to promote
6154     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6155         *promoChoice = PieceToChar(BlackFerz);  // no choice
6156         return FALSE;
6157     }
6158     // no sense asking what we must promote to if it is going to explode...
6159     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6160         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6161         return FALSE;
6162     }
6163     // give caller the default choice even if we will not make it
6164     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6165     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6166     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6167                            && gameInfo.variant != VariantShogi
6168                            && gameInfo.variant != VariantSuper) return FALSE;
6169     if(autoQueen) return FALSE; // predetermined
6170
6171     // suppress promotion popup on illegal moves that are not premoves
6172     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6173               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6174     if(appData.testLegality && !premove) {
6175         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6176                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6177         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6178             return FALSE;
6179     }
6180
6181     return TRUE;
6182 }
6183
6184 int
6185 InPalace(row, column)
6186      int row, column;
6187 {   /* [HGM] for Xiangqi */
6188     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6189          column < (BOARD_WIDTH + 4)/2 &&
6190          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6191     return FALSE;
6192 }
6193
6194 int
6195 PieceForSquare (x, y)
6196      int x;
6197      int y;
6198 {
6199   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6200      return -1;
6201   else
6202      return boards[currentMove][y][x];
6203 }
6204
6205 int
6206 OKToStartUserMove(x, y)
6207      int x, 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 PlayFromGameFile:
6227       case AnalyzeFile:
6228       case TwoMachinesPlay:
6229       case EndOfGame:
6230         return FALSE;
6231
6232       case IcsObserving:
6233       case IcsIdle:
6234         return FALSE;
6235
6236       case MachinePlaysWhite:
6237       case IcsPlayingBlack:
6238         if (appData.zippyPlay) return FALSE;
6239         if (white_piece) {
6240             DisplayMoveError(_("You are playing Black"));
6241             return FALSE;
6242         }
6243         break;
6244
6245       case MachinePlaysBlack:
6246       case IcsPlayingWhite:
6247         if (appData.zippyPlay) return FALSE;
6248         if (!white_piece) {
6249             DisplayMoveError(_("You are playing White"));
6250             return FALSE;
6251         }
6252         break;
6253
6254       case EditGame:
6255         if (!white_piece && WhiteOnMove(currentMove)) {
6256             DisplayMoveError(_("It is White's turn"));
6257             return FALSE;
6258         }
6259         if (white_piece && !WhiteOnMove(currentMove)) {
6260             DisplayMoveError(_("It is Black's turn"));
6261             return FALSE;
6262         }
6263         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6264             /* Editing correspondence game history */
6265             /* Could disallow this or prompt for confirmation */
6266             cmailOldMove = -1;
6267         }
6268         break;
6269
6270       case BeginningOfGame:
6271         if (appData.icsActive) return FALSE;
6272         if (!appData.noChessProgram) {
6273             if (!white_piece) {
6274                 DisplayMoveError(_("You are playing White"));
6275                 return FALSE;
6276             }
6277         }
6278         break;
6279
6280       case Training:
6281         if (!white_piece && WhiteOnMove(currentMove)) {
6282             DisplayMoveError(_("It is White's turn"));
6283             return FALSE;
6284         }
6285         if (white_piece && !WhiteOnMove(currentMove)) {
6286             DisplayMoveError(_("It is Black's turn"));
6287             return FALSE;
6288         }
6289         break;
6290
6291       default:
6292       case IcsExamining:
6293         break;
6294     }
6295     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6296         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6297         && gameMode != AnalyzeFile && gameMode != Training) {
6298         DisplayMoveError(_("Displayed position is not current"));
6299         return FALSE;
6300     }
6301     return TRUE;
6302 }
6303
6304 Boolean
6305 OnlyMove(int *x, int *y, Boolean captures) {
6306     DisambiguateClosure cl;
6307     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6308     switch(gameMode) {
6309       case MachinePlaysBlack:
6310       case IcsPlayingWhite:
6311       case BeginningOfGame:
6312         if(!WhiteOnMove(currentMove)) return FALSE;
6313         break;
6314       case MachinePlaysWhite:
6315       case IcsPlayingBlack:
6316         if(WhiteOnMove(currentMove)) return FALSE;
6317         break;
6318       case EditGame:
6319         break;
6320       default:
6321         return FALSE;
6322     }
6323     cl.pieceIn = EmptySquare;
6324     cl.rfIn = *y;
6325     cl.ffIn = *x;
6326     cl.rtIn = -1;
6327     cl.ftIn = -1;
6328     cl.promoCharIn = NULLCHAR;
6329     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6330     if( cl.kind == NormalMove ||
6331         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6332         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6333         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6334       fromX = cl.ff;
6335       fromY = cl.rf;
6336       *x = cl.ft;
6337       *y = cl.rt;
6338       return TRUE;
6339     }
6340     if(cl.kind != ImpossibleMove) return FALSE;
6341     cl.pieceIn = EmptySquare;
6342     cl.rfIn = -1;
6343     cl.ffIn = -1;
6344     cl.rtIn = *y;
6345     cl.ftIn = *x;
6346     cl.promoCharIn = NULLCHAR;
6347     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6348     if( cl.kind == NormalMove ||
6349         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6350         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6351         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6352       fromX = cl.ff;
6353       fromY = cl.rf;
6354       *x = cl.ft;
6355       *y = cl.rt;
6356       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6357       return TRUE;
6358     }
6359     return FALSE;
6360 }
6361
6362 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6363 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6364 int lastLoadGameUseList = FALSE;
6365 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6366 ChessMove lastLoadGameStart = EndOfFile;
6367
6368 void
6369 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6370      int fromX, fromY, toX, toY;
6371      int promoChar;
6372 {
6373     ChessMove moveType;
6374     ChessSquare pdown, pup;
6375
6376     /* Check if the user is playing in turn.  This is complicated because we
6377        let the user "pick up" a piece before it is his turn.  So the piece he
6378        tried to pick up may have been captured by the time he puts it down!
6379        Therefore we use the color the user is supposed to be playing in this
6380        test, not the color of the piece that is currently on the starting
6381        square---except in EditGame mode, where the user is playing both
6382        sides; fortunately there the capture race can't happen.  (It can
6383        now happen in IcsExamining mode, but that's just too bad.  The user
6384        will get a somewhat confusing message in that case.)
6385        */
6386
6387     switch (gameMode) {
6388       case PlayFromGameFile:
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 EditGame:
6415       case IcsExamining:
6416       case BeginningOfGame:
6417       case AnalyzeMode:
6418       case Training:
6419         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6420         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6421             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6422             /* User is moving for Black */
6423             if (WhiteOnMove(currentMove)) {
6424                 DisplayMoveError(_("It is White's turn"));
6425                 return;
6426             }
6427         } else {
6428             /* User is moving for White */
6429             if (!WhiteOnMove(currentMove)) {
6430                 DisplayMoveError(_("It is Black's turn"));
6431                 return;
6432             }
6433         }
6434         break;
6435
6436       case IcsPlayingBlack:
6437         /* User is moving for Black */
6438         if (WhiteOnMove(currentMove)) {
6439             if (!appData.premove) {
6440                 DisplayMoveError(_("It is White's turn"));
6441             } else if (toX >= 0 && toY >= 0) {
6442                 premoveToX = toX;
6443                 premoveToY = toY;
6444                 premoveFromX = fromX;
6445                 premoveFromY = fromY;
6446                 premovePromoChar = promoChar;
6447                 gotPremove = 1;
6448                 if (appData.debugMode)
6449                     fprintf(debugFP, "Got premove: fromX %d,"
6450                             "fromY %d, toX %d, toY %d\n",
6451                             fromX, fromY, toX, toY);
6452             }
6453             return;
6454         }
6455         break;
6456
6457       case IcsPlayingWhite:
6458         /* User is moving for White */
6459         if (!WhiteOnMove(currentMove)) {
6460             if (!appData.premove) {
6461                 DisplayMoveError(_("It is Black's turn"));
6462             } else if (toX >= 0 && toY >= 0) {
6463                 premoveToX = toX;
6464                 premoveToY = toY;
6465                 premoveFromX = fromX;
6466                 premoveFromY = fromY;
6467                 premovePromoChar = promoChar;
6468                 gotPremove = 1;
6469                 if (appData.debugMode)
6470                     fprintf(debugFP, "Got premove: fromX %d,"
6471                             "fromY %d, toX %d, toY %d\n",
6472                             fromX, fromY, toX, toY);
6473             }
6474             return;
6475         }
6476         break;
6477
6478       default:
6479         break;
6480
6481       case EditPosition:
6482         /* EditPosition, empty square, or different color piece;
6483            click-click move is possible */
6484         if (toX == -2 || toY == -2) {
6485             boards[0][fromY][fromX] = EmptySquare;
6486             DrawPosition(FALSE, boards[currentMove]);
6487             return;
6488         } else if (toX >= 0 && toY >= 0) {
6489             boards[0][toY][toX] = boards[0][fromY][fromX];
6490             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6491                 if(boards[0][fromY][0] != EmptySquare) {
6492                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6493                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6494                 }
6495             } else
6496             if(fromX == BOARD_RGHT+1) {
6497                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6498                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6499                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6500                 }
6501             } else
6502             boards[0][fromY][fromX] = EmptySquare;
6503             DrawPosition(FALSE, boards[currentMove]);
6504             return;
6505         }
6506         return;
6507     }
6508
6509     if(toX < 0 || toY < 0) return;
6510     pdown = boards[currentMove][fromY][fromX];
6511     pup = boards[currentMove][toY][toX];
6512
6513     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6514     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6515          if( pup != EmptySquare ) return;
6516          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6517            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6518                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6519            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6520            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6521            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6522            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6523          fromY = DROP_RANK;
6524     }
6525
6526     /* [HGM] always test for legality, to get promotion info */
6527     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6528                                          fromY, fromX, toY, toX, promoChar);
6529     /* [HGM] but possibly ignore an IllegalMove result */
6530     if (appData.testLegality) {
6531         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6532             DisplayMoveError(_("Illegal move"));
6533             return;
6534         }
6535     }
6536
6537     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6538 }
6539
6540 /* Common tail of UserMoveEvent and DropMenuEvent */
6541 int
6542 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6543      ChessMove moveType;
6544      int fromX, fromY, toX, toY;
6545      /*char*/int promoChar;
6546 {
6547     char *bookHit = 0;
6548
6549     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6550         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6551         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6552         if(WhiteOnMove(currentMove)) {
6553             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6554         } else {
6555             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6556         }
6557     }
6558
6559     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6560        move type in caller when we know the move is a legal promotion */
6561     if(moveType == NormalMove && promoChar)
6562         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6563
6564     /* [HGM] <popupFix> The following if has been moved here from
6565        UserMoveEvent(). Because it seemed to belong here (why not allow
6566        piece drops in training games?), and because it can only be
6567        performed after it is known to what we promote. */
6568     if (gameMode == Training) {
6569       /* compare the move played on the board to the next move in the
6570        * game. If they match, display the move and the opponent's response.
6571        * If they don't match, display an error message.
6572        */
6573       int saveAnimate;
6574       Board testBoard;
6575       CopyBoard(testBoard, boards[currentMove]);
6576       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6577
6578       if (CompareBoards(testBoard, boards[currentMove+1])) {
6579         ForwardInner(currentMove+1);
6580
6581         /* Autoplay the opponent's response.
6582          * if appData.animate was TRUE when Training mode was entered,
6583          * the response will be animated.
6584          */
6585         saveAnimate = appData.animate;
6586         appData.animate = animateTraining;
6587         ForwardInner(currentMove+1);
6588         appData.animate = saveAnimate;
6589
6590         /* check for the end of the game */
6591         if (currentMove >= forwardMostMove) {
6592           gameMode = PlayFromGameFile;
6593           ModeHighlight();
6594           SetTrainingModeOff();
6595           DisplayInformation(_("End of game"));
6596         }
6597       } else {
6598         DisplayError(_("Incorrect move"), 0);
6599       }
6600       return 1;
6601     }
6602
6603   /* Ok, now we know that the move is good, so we can kill
6604      the previous line in Analysis Mode */
6605   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6606                                 && currentMove < forwardMostMove) {
6607     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6608     else forwardMostMove = currentMove;
6609   }
6610
6611   /* If we need the chess program but it's dead, restart it */
6612   ResurrectChessProgram();
6613
6614   /* A user move restarts a paused game*/
6615   if (pausing)
6616     PauseEvent();
6617
6618   thinkOutput[0] = NULLCHAR;
6619
6620   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6621
6622   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6623     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6624     return 1;
6625   }
6626
6627   if (gameMode == BeginningOfGame) {
6628     if (appData.noChessProgram) {
6629       gameMode = EditGame;
6630       SetGameInfo();
6631     } else {
6632       char buf[MSG_SIZ];
6633       gameMode = MachinePlaysBlack;
6634       StartClocks();
6635       SetGameInfo();
6636       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6637       DisplayTitle(buf);
6638       if (first.sendName) {
6639         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6640         SendToProgram(buf, &first);
6641       }
6642       StartClocks();
6643     }
6644     ModeHighlight();
6645   }
6646
6647   /* Relay move to ICS or chess engine */
6648   if (appData.icsActive) {
6649     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6650         gameMode == IcsExamining) {
6651       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6652         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6653         SendToICS("draw ");
6654         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6655       }
6656       // also send plain move, in case ICS does not understand atomic claims
6657       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6658       ics_user_moved = 1;
6659     }
6660   } else {
6661     if (first.sendTime && (gameMode == BeginningOfGame ||
6662                            gameMode == MachinePlaysWhite ||
6663                            gameMode == MachinePlaysBlack)) {
6664       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6665     }
6666     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6667          // [HGM] book: if program might be playing, let it use book
6668         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6669         first.maybeThinking = TRUE;
6670     } else SendMoveToProgram(forwardMostMove-1, &first);
6671     if (currentMove == cmailOldMove + 1) {
6672       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6673     }
6674   }
6675
6676   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6677
6678   switch (gameMode) {
6679   case EditGame:
6680     if(appData.testLegality)
6681     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6682     case MT_NONE:
6683     case MT_CHECK:
6684       break;
6685     case MT_CHECKMATE:
6686     case MT_STAINMATE:
6687       if (WhiteOnMove(currentMove)) {
6688         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6689       } else {
6690         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6691       }
6692       break;
6693     case MT_STALEMATE:
6694       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6695       break;
6696     }
6697     break;
6698
6699   case MachinePlaysBlack:
6700   case MachinePlaysWhite:
6701     /* disable certain menu options while machine is thinking */
6702     SetMachineThinkingEnables();
6703     break;
6704
6705   default:
6706     break;
6707   }
6708
6709   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6710   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6711
6712   if(bookHit) { // [HGM] book: simulate book reply
6713         static char bookMove[MSG_SIZ]; // a bit generous?
6714
6715         programStats.nodes = programStats.depth = programStats.time =
6716         programStats.score = programStats.got_only_move = 0;
6717         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6718
6719         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6720         strcat(bookMove, bookHit);
6721         HandleMachineMove(bookMove, &first);
6722   }
6723   return 1;
6724 }
6725
6726 void
6727 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6728      Board board;
6729      int flags;
6730      ChessMove kind;
6731      int rf, ff, rt, ft;
6732      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 ||
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 CanPromote(ChessSquare piece, int y)
6780 {
6781         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6782         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6783         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6784            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6785            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6786                                                   gameInfo.variant == VariantMakruk) return FALSE;
6787         return (piece == BlackPawn && y == 1 ||
6788                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6789                 piece == BlackLance && y == 1 ||
6790                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6791 }
6792
6793 void LeftClick(ClickType clickType, int xPix, int yPix)
6794 {
6795     int x, y;
6796     Boolean saveAnimate;
6797     static int second = 0, promotionChoice = 0, clearFlag = 0;
6798     char promoChoice = NULLCHAR;
6799     ChessSquare piece;
6800
6801     if(appData.seekGraph && appData.icsActive && loggedOn &&
6802         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6803         SeekGraphClick(clickType, xPix, yPix, 0);
6804         return;
6805     }
6806
6807     if (clickType == Press) ErrorPopDown();
6808     MarkTargetSquares(1);
6809
6810     x = EventToSquare(xPix, BOARD_WIDTH);
6811     y = EventToSquare(yPix, BOARD_HEIGHT);
6812     if (!flipView && y >= 0) {
6813         y = BOARD_HEIGHT - 1 - y;
6814     }
6815     if (flipView && x >= 0) {
6816         x = BOARD_WIDTH - 1 - x;
6817     }
6818
6819     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6820         defaultPromoChoice = promoSweep;
6821         promoSweep = EmptySquare;   // terminate sweep
6822         promoDefaultAltered = TRUE;
6823         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6824     }
6825
6826     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6827         if(clickType == Release) return; // ignore upclick of click-click destination
6828         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6829         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6830         if(gameInfo.holdingsWidth &&
6831                 (WhiteOnMove(currentMove)
6832                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6833                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6834             // click in right holdings, for determining promotion piece
6835             ChessSquare p = boards[currentMove][y][x];
6836             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6837             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6838             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6839                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6840                 fromX = fromY = -1;
6841                 return;
6842             }
6843         }
6844         DrawPosition(FALSE, boards[currentMove]);
6845         return;
6846     }
6847
6848     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6849     if(clickType == Press
6850             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6851               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6852               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6853         return;
6854
6855     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6856         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6857
6858     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6859         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6860                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6861         defaultPromoChoice = DefaultPromoChoice(side);
6862     }
6863
6864     autoQueen = appData.alwaysPromoteToQueen;
6865
6866     if (fromX == -1) {
6867       int originalY = y;
6868       gatingPiece = EmptySquare;
6869       if (clickType != Press) {
6870         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6871             DragPieceEnd(xPix, yPix); dragging = 0;
6872             DrawPosition(FALSE, NULL);
6873         }
6874         return;
6875       }
6876       fromX = x; fromY = y;
6877       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6878          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6879          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6880             /* First square */
6881             if (OKToStartUserMove(fromX, fromY)) {
6882                 second = 0;
6883                 MarkTargetSquares(0);
6884                 DragPieceBegin(xPix, yPix); dragging = 1;
6885                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6886                     promoSweep = defaultPromoChoice;
6887                     selectFlag = 0; lastX = xPix; lastY = yPix;
6888                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6889                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6890                 }
6891                 if (appData.highlightDragging) {
6892                     SetHighlights(fromX, fromY, -1, -1);
6893                 }
6894             } else fromX = fromY = -1;
6895             return;
6896         }
6897     }
6898
6899     /* fromX != -1 */
6900     if (clickType == Press && gameMode != EditPosition) {
6901         ChessSquare fromP;
6902         ChessSquare toP;
6903         int frc;
6904
6905         // ignore off-board to clicks
6906         if(y < 0 || x < 0) return;
6907
6908         /* Check if clicking again on the same color piece */
6909         fromP = boards[currentMove][fromY][fromX];
6910         toP = boards[currentMove][y][x];
6911         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6912         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6913              WhitePawn <= toP && toP <= WhiteKing &&
6914              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6915              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6916             (BlackPawn <= fromP && fromP <= BlackKing &&
6917              BlackPawn <= toP && toP <= BlackKing &&
6918              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6919              !(fromP == BlackKing && toP == BlackRook && frc))) {
6920             /* Clicked again on same color piece -- changed his mind */
6921             second = (x == fromX && y == fromY);
6922             promoDefaultAltered = FALSE;
6923            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6924             if (appData.highlightDragging) {
6925                 SetHighlights(x, y, -1, -1);
6926             } else {
6927                 ClearHighlights();
6928             }
6929             if (OKToStartUserMove(x, y)) {
6930                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6931                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6932                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6933                  gatingPiece = boards[currentMove][fromY][fromX];
6934                 else gatingPiece = EmptySquare;
6935                 fromX = x;
6936                 fromY = y; dragging = 1;
6937                 MarkTargetSquares(0);
6938                 DragPieceBegin(xPix, yPix);
6939                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6940                     promoSweep = defaultPromoChoice;
6941                     selectFlag = 0; lastX = xPix; lastY = yPix;
6942                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6943                 }
6944             }
6945            }
6946            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6947            second = FALSE; 
6948         }
6949         // ignore clicks on holdings
6950         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6951     }
6952
6953     if (clickType == Release && x == fromX && y == fromY) {
6954         DragPieceEnd(xPix, yPix); dragging = 0;
6955         if(clearFlag) {
6956             // a deferred attempt to click-click move an empty square on top of a piece
6957             boards[currentMove][y][x] = EmptySquare;
6958             ClearHighlights();
6959             DrawPosition(FALSE, boards[currentMove]);
6960             fromX = fromY = -1; clearFlag = 0;
6961             return;
6962         }
6963         if (appData.animateDragging) {
6964             /* Undo animation damage if any */
6965             DrawPosition(FALSE, NULL);
6966         }
6967         if (second) {
6968             /* Second up/down in same square; just abort move */
6969             second = 0;
6970             fromX = fromY = -1;
6971             gatingPiece = EmptySquare;
6972             ClearHighlights();
6973             gotPremove = 0;
6974             ClearPremoveHighlights();
6975         } else {
6976             /* First upclick in same square; start click-click mode */
6977             SetHighlights(x, y, -1, -1);
6978         }
6979         return;
6980     }
6981
6982     clearFlag = 0;
6983
6984     /* we now have a different from- and (possibly off-board) to-square */
6985     /* Completed move */
6986     toX = x;
6987     toY = y;
6988     saveAnimate = appData.animate;
6989     if (clickType == Press) {
6990         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6991             // must be Edit Position mode with empty-square selected
6992             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6993             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6994             return;
6995         }
6996         /* Finish clickclick move */
6997         if (appData.animate || appData.highlightLastMove) {
6998             SetHighlights(fromX, fromY, toX, toY);
6999         } else {
7000             ClearHighlights();
7001         }
7002     } else {
7003         /* Finish drag move */
7004         if (appData.highlightLastMove) {
7005             SetHighlights(fromX, fromY, toX, toY);
7006         } else {
7007             ClearHighlights();
7008         }
7009         DragPieceEnd(xPix, yPix); dragging = 0;
7010         /* Don't animate move and drag both */
7011         appData.animate = FALSE;
7012     }
7013
7014     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7015     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7016         ChessSquare piece = boards[currentMove][fromY][fromX];
7017         if(gameMode == EditPosition && piece != EmptySquare &&
7018            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7019             int n;
7020
7021             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7022                 n = PieceToNumber(piece - (int)BlackPawn);
7023                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7024                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7025                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7026             } else
7027             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7028                 n = PieceToNumber(piece);
7029                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7030                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7031                 boards[currentMove][n][BOARD_WIDTH-2]++;
7032             }
7033             boards[currentMove][fromY][fromX] = EmptySquare;
7034         }
7035         ClearHighlights();
7036         fromX = fromY = -1;
7037         DrawPosition(TRUE, boards[currentMove]);
7038         return;
7039     }
7040
7041     // off-board moves should not be highlighted
7042     if(x < 0 || y < 0) ClearHighlights();
7043
7044     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7045
7046     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
7047         SetHighlights(fromX, fromY, toX, toY);
7048         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7049             // [HGM] super: promotion to captured piece selected from holdings
7050             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7051             promotionChoice = TRUE;
7052             // kludge follows to temporarily execute move on display, without promoting yet
7053             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7054             boards[currentMove][toY][toX] = p;
7055             DrawPosition(FALSE, boards[currentMove]);
7056             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7057             boards[currentMove][toY][toX] = q;
7058             DisplayMessage("Click in holdings to choose piece", "");
7059             return;
7060         }
7061         PromotionPopUp();
7062     } else {
7063         int oldMove = currentMove;
7064         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7065         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7066         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7067         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7068            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7069             DrawPosition(TRUE, boards[currentMove]);
7070         fromX = fromY = -1;
7071     }
7072     appData.animate = saveAnimate;
7073     if (appData.animate || appData.animateDragging) {
7074         /* Undo animation damage if needed */
7075         DrawPosition(FALSE, NULL);
7076     }
7077 }
7078
7079 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7080 {   // front-end-free part taken out of PieceMenuPopup
7081     int whichMenu; int xSqr, ySqr;
7082
7083     if(seekGraphUp) { // [HGM] seekgraph
7084         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7085         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7086         return -2;
7087     }
7088
7089     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7090          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7091         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7092         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7093         if(action == Press)   {
7094             originalFlip = flipView;
7095             flipView = !flipView; // temporarily flip board to see game from partners perspective
7096             DrawPosition(TRUE, partnerBoard);
7097             DisplayMessage(partnerStatus, "");
7098             partnerUp = TRUE;
7099         } else if(action == Release) {
7100             flipView = originalFlip;
7101             DrawPosition(TRUE, boards[currentMove]);
7102             partnerUp = FALSE;
7103         }
7104         return -2;
7105     }
7106
7107     xSqr = EventToSquare(x, BOARD_WIDTH);
7108     ySqr = EventToSquare(y, BOARD_HEIGHT);
7109     if (action == Release) {
7110         if(pieceSweep != EmptySquare) {
7111             EditPositionMenuEvent(pieceSweep, toX, toY);
7112             pieceSweep = EmptySquare;
7113         } else UnLoadPV(); // [HGM] pv
7114     }
7115     if (action != Press) return -2; // return code to be ignored
7116     switch (gameMode) {
7117       case IcsExamining:
7118         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7119       case EditPosition:
7120         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7121         if (xSqr < 0 || ySqr < 0) return -1;
7122         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7123         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7124         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7125         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7126         NextPiece(0);
7127         return -2;
7128       case IcsObserving:
7129         if(!appData.icsEngineAnalyze) return -1;
7130       case IcsPlayingWhite:
7131       case IcsPlayingBlack:
7132         if(!appData.zippyPlay) goto noZip;
7133       case AnalyzeMode:
7134       case AnalyzeFile:
7135       case MachinePlaysWhite:
7136       case MachinePlaysBlack:
7137       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7138         if (!appData.dropMenu) {
7139           LoadPV(x, y);
7140           return 2; // flag front-end to grab mouse events
7141         }
7142         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7143            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7144       case EditGame:
7145       noZip:
7146         if (xSqr < 0 || ySqr < 0) return -1;
7147         if (!appData.dropMenu || appData.testLegality &&
7148             gameInfo.variant != VariantBughouse &&
7149             gameInfo.variant != VariantCrazyhouse) return -1;
7150         whichMenu = 1; // drop menu
7151         break;
7152       default:
7153         return -1;
7154     }
7155
7156     if (((*fromX = xSqr) < 0) ||
7157         ((*fromY = ySqr) < 0)) {
7158         *fromX = *fromY = -1;
7159         return -1;
7160     }
7161     if (flipView)
7162       *fromX = BOARD_WIDTH - 1 - *fromX;
7163     else
7164       *fromY = BOARD_HEIGHT - 1 - *fromY;
7165
7166     return whichMenu;
7167 }
7168
7169 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7170 {
7171 //    char * hint = lastHint;
7172     FrontEndProgramStats stats;
7173
7174     stats.which = cps == &first ? 0 : 1;
7175     stats.depth = cpstats->depth;
7176     stats.nodes = cpstats->nodes;
7177     stats.score = cpstats->score;
7178     stats.time = cpstats->time;
7179     stats.pv = cpstats->movelist;
7180     stats.hint = lastHint;
7181     stats.an_move_index = 0;
7182     stats.an_move_count = 0;
7183
7184     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7185         stats.hint = cpstats->move_name;
7186         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7187         stats.an_move_count = cpstats->nr_moves;
7188     }
7189
7190     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
7191
7192     SetProgramStats( &stats );
7193 }
7194
7195 void
7196 ClearEngineOutputPane(int which)
7197 {
7198     static FrontEndProgramStats dummyStats;
7199     dummyStats.which = which;
7200     dummyStats.pv = "#";
7201     SetProgramStats( &dummyStats );
7202 }
7203
7204 #define MAXPLAYERS 500
7205
7206 char *
7207 TourneyStandings(int display)
7208 {
7209     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7210     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7211     char result, *p, *names[MAXPLAYERS];
7212
7213     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7214         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7215     names[0] = p = strdup(appData.participants);
7216     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7217
7218     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7219
7220     while(result = appData.results[nr]) {
7221         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7222         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7223         wScore = bScore = 0;
7224         switch(result) {
7225           case '+': wScore = 2; break;
7226           case '-': bScore = 2; break;
7227           case '=': wScore = bScore = 1; break;
7228           case ' ':
7229           case '*': return strdup("busy"); // tourney not finished
7230         }
7231         score[w] += wScore;
7232         score[b] += bScore;
7233         games[w]++;
7234         games[b]++;
7235         nr++;
7236     }
7237     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7238     for(w=0; w<nPlayers; w++) {
7239         bScore = -1;
7240         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7241         ranking[w] = b; points[w] = bScore; score[b] = -2;
7242     }
7243     p = malloc(nPlayers*34+1);
7244     for(w=0; w<nPlayers && w<display; w++)
7245         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7246     free(names[0]);
7247     return p;
7248 }
7249
7250 void
7251 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7252 {       // count all piece types
7253         int p, f, r;
7254         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7255         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7256         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7257                 p = board[r][f];
7258                 pCnt[p]++;
7259                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7260                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7261                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7262                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7263                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7264                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7265         }
7266 }
7267
7268 int
7269 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7270 {
7271         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7272         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7273
7274         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7275         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7276         if(myPawns == 2 && nMine == 3) // KPP
7277             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7278         if(myPawns == 1 && nMine == 2) // KP
7279             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7280         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7281             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7282         if(myPawns) return FALSE;
7283         if(pCnt[WhiteRook+side])
7284             return pCnt[BlackRook-side] ||
7285                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7286                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7287                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7288         if(pCnt[WhiteCannon+side]) {
7289             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7290             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7291         }
7292         if(pCnt[WhiteKnight+side])
7293             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7294         return FALSE;
7295 }
7296
7297 int
7298 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7299 {
7300         VariantClass v = gameInfo.variant;
7301
7302         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7303         if(v == VariantShatranj) return TRUE; // always winnable through baring
7304         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7305         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7306
7307         if(v == VariantXiangqi) {
7308                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7309
7310                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7311                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7312                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7313                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7314                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7315                 if(stale) // we have at least one last-rank P plus perhaps C
7316                     return majors // KPKX
7317                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7318                 else // KCA*E*
7319                     return pCnt[WhiteFerz+side] // KCAK
7320                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7321                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7322                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7323
7324         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7325                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7326
7327                 if(nMine == 1) return FALSE; // bare King
7328                 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
7329                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7330                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7331                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7332                 if(pCnt[WhiteKnight+side])
7333                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7334                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7335                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7336                 if(nBishops)
7337                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7338                 if(pCnt[WhiteAlfil+side])
7339                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7340                 if(pCnt[WhiteWazir+side])
7341                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7342         }
7343
7344         return TRUE;
7345 }
7346
7347 int
7348 Adjudicate(ChessProgramState *cps)
7349 {       // [HGM] some adjudications useful with buggy engines
7350         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7351         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7352         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7353         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7354         int k, count = 0; static int bare = 1;
7355         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7356         Boolean canAdjudicate = !appData.icsActive;
7357
7358         // most tests only when we understand the game, i.e. legality-checking on
7359             if( appData.testLegality )
7360             {   /* [HGM] Some more adjudications for obstinate engines */
7361                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7362                 static int moveCount = 6;
7363                 ChessMove result;
7364                 char *reason = NULL;
7365
7366                 /* Count what is on board. */
7367                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7368
7369                 /* Some material-based adjudications that have to be made before stalemate test */
7370                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7371                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7372                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7373                      if(canAdjudicate && appData.checkMates) {
7374                          if(engineOpponent)
7375                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7376                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7377                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7378                          return 1;
7379                      }
7380                 }
7381
7382                 /* Bare King in Shatranj (loses) or Losers (wins) */
7383                 if( nrW == 1 || nrB == 1) {
7384                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7385                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7386                      if(canAdjudicate && appData.checkMates) {
7387                          if(engineOpponent)
7388                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7389                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7390                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7391                          return 1;
7392                      }
7393                   } else
7394                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7395                   {    /* bare King */
7396                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7397                         if(canAdjudicate && appData.checkMates) {
7398                             /* but only adjudicate if adjudication enabled */
7399                             if(engineOpponent)
7400                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7401                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7402                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7403                             return 1;
7404                         }
7405                   }
7406                 } else bare = 1;
7407
7408
7409             // don't wait for engine to announce game end if we can judge ourselves
7410             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7411               case MT_CHECK:
7412                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7413                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7414                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7415                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7416                             checkCnt++;
7417                         if(checkCnt >= 2) {
7418                             reason = "Xboard adjudication: 3rd check";
7419                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7420                             break;
7421                         }
7422                     }
7423                 }
7424               case MT_NONE:
7425               default:
7426                 break;
7427               case MT_STALEMATE:
7428               case MT_STAINMATE:
7429                 reason = "Xboard adjudication: Stalemate";
7430                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7431                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7432                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7433                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7434                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7435                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7436                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7437                                                                         EP_CHECKMATE : EP_WINS);
7438                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7439                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7440                 }
7441                 break;
7442               case MT_CHECKMATE:
7443                 reason = "Xboard adjudication: Checkmate";
7444                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7445                 break;
7446             }
7447
7448                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7449                     case EP_STALEMATE:
7450                         result = GameIsDrawn; break;
7451                     case EP_CHECKMATE:
7452                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7453                     case EP_WINS:
7454                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7455                     default:
7456                         result = EndOfFile;
7457                 }
7458                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7459                     if(engineOpponent)
7460                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7461                     GameEnds( result, reason, GE_XBOARD );
7462                     return 1;
7463                 }
7464
7465                 /* Next absolutely insufficient mating material. */
7466                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7467                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7468                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7469
7470                      /* always flag draws, for judging claims */
7471                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7472
7473                      if(canAdjudicate && appData.materialDraws) {
7474                          /* but only adjudicate them if adjudication enabled */
7475                          if(engineOpponent) {
7476                            SendToProgram("force\n", engineOpponent); // suppress reply
7477                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7478                          }
7479                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7480                          return 1;
7481                      }
7482                 }
7483
7484                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7485                 if(gameInfo.variant == VariantXiangqi ?
7486                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7487                  : nrW + nrB == 4 &&
7488                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7489                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7490                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7491                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7492                    ) ) {
7493                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7494                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7495                           if(engineOpponent) {
7496                             SendToProgram("force\n", engineOpponent); // suppress reply
7497                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7498                           }
7499                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7500                           return 1;
7501                      }
7502                 } else moveCount = 6;
7503             }
7504         if (appData.debugMode) { int i;
7505             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7506                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7507                     appData.drawRepeats);
7508             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7509               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7510
7511         }
7512
7513         // Repetition draws and 50-move rule can be applied independently of legality testing
7514
7515                 /* Check for rep-draws */
7516                 count = 0;
7517                 for(k = forwardMostMove-2;
7518                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7519                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7520                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7521                     k-=2)
7522                 {   int rights=0;
7523                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7524                         /* compare castling rights */
7525                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7526                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7527                                 rights++; /* King lost rights, while rook still had them */
7528                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7529                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7530                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7531                                    rights++; /* but at least one rook lost them */
7532                         }
7533                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7534                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7535                                 rights++;
7536                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7537                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7538                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7539                                    rights++;
7540                         }
7541                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7542                             && appData.drawRepeats > 1) {
7543                              /* adjudicate after user-specified nr of repeats */
7544                              int result = GameIsDrawn;
7545                              char *details = "XBoard adjudication: repetition draw";
7546                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7547                                 // [HGM] xiangqi: check for forbidden perpetuals
7548                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7549                                 for(m=forwardMostMove; m>k; m-=2) {
7550                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7551                                         ourPerpetual = 0; // the current mover did not always check
7552                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7553                                         hisPerpetual = 0; // the opponent did not always check
7554                                 }
7555                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7556                                                                         ourPerpetual, hisPerpetual);
7557                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7558                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7559                                     details = "Xboard adjudication: perpetual checking";
7560                                 } else
7561                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7562                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7563                                 } else
7564                                 // Now check for perpetual chases
7565                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7566                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7567                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7568                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7569                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7570                                         details = "Xboard adjudication: perpetual chasing";
7571                                     } else
7572                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7573                                         break; // Abort repetition-checking loop.
7574                                 }
7575                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7576                              }
7577                              if(engineOpponent) {
7578                                SendToProgram("force\n", engineOpponent); // suppress reply
7579                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7580                              }
7581                              GameEnds( result, details, GE_XBOARD );
7582                              return 1;
7583                         }
7584                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7585                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7586                     }
7587                 }
7588
7589                 /* Now we test for 50-move draws. Determine ply count */
7590                 count = forwardMostMove;
7591                 /* look for last irreversble move */
7592                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7593                     count--;
7594                 /* if we hit starting position, add initial plies */
7595                 if( count == backwardMostMove )
7596                     count -= initialRulePlies;
7597                 count = forwardMostMove - count;
7598                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7599                         // adjust reversible move counter for checks in Xiangqi
7600                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7601                         if(i < backwardMostMove) i = backwardMostMove;
7602                         while(i <= forwardMostMove) {
7603                                 lastCheck = inCheck; // check evasion does not count
7604                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7605                                 if(inCheck || lastCheck) count--; // check does not count
7606                                 i++;
7607                         }
7608                 }
7609                 if( count >= 100)
7610                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7611                          /* this is used to judge if draw claims are legal */
7612                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7613                          if(engineOpponent) {
7614                            SendToProgram("force\n", engineOpponent); // suppress reply
7615                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7616                          }
7617                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7618                          return 1;
7619                 }
7620
7621                 /* if draw offer is pending, treat it as a draw claim
7622                  * when draw condition present, to allow engines a way to
7623                  * claim draws before making their move to avoid a race
7624                  * condition occurring after their move
7625                  */
7626                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7627                          char *p = NULL;
7628                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7629                              p = "Draw claim: 50-move rule";
7630                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7631                              p = "Draw claim: 3-fold repetition";
7632                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7633                              p = "Draw claim: insufficient mating material";
7634                          if( p != NULL && canAdjudicate) {
7635                              if(engineOpponent) {
7636                                SendToProgram("force\n", engineOpponent); // suppress reply
7637                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7638                              }
7639                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7640                              return 1;
7641                          }
7642                 }
7643
7644                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7645                     if(engineOpponent) {
7646                       SendToProgram("force\n", engineOpponent); // suppress reply
7647                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7648                     }
7649                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7650                     return 1;
7651                 }
7652         return 0;
7653 }
7654
7655 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7656 {   // [HGM] book: this routine intercepts moves to simulate book replies
7657     char *bookHit = NULL;
7658
7659     //first determine if the incoming move brings opponent into his book
7660     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7661         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7662     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7663     if(bookHit != NULL && !cps->bookSuspend) {
7664         // make sure opponent is not going to reply after receiving move to book position
7665         SendToProgram("force\n", cps);
7666         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7667     }
7668     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7669     // now arrange restart after book miss
7670     if(bookHit) {
7671         // after a book hit we never send 'go', and the code after the call to this routine
7672         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7673         char buf[MSG_SIZ], *move = bookHit;
7674         if(cps->useSAN) {
7675             int fromX, fromY, toX, toY;
7676             char promoChar;
7677             ChessMove moveType;
7678             move = buf + 30;
7679             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7680                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7681                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7682                                     PosFlags(forwardMostMove),
7683                                     fromY, fromX, toY, toX, promoChar, move);
7684             } else {
7685                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7686                 bookHit = NULL;
7687             }
7688         }
7689         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7690         SendToProgram(buf, cps);
7691         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7692     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7693         SendToProgram("go\n", cps);
7694         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7695     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7696         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7697             SendToProgram("go\n", cps);
7698         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7699     }
7700     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7701 }
7702
7703 char *savedMessage;
7704 ChessProgramState *savedState;
7705 void DeferredBookMove(void)
7706 {
7707         if(savedState->lastPing != savedState->lastPong)
7708                     ScheduleDelayedEvent(DeferredBookMove, 10);
7709         else
7710         HandleMachineMove(savedMessage, savedState);
7711 }
7712
7713 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7714
7715 void
7716 HandleMachineMove(message, cps)
7717      char *message;
7718      ChessProgramState *cps;
7719 {
7720     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7721     char realname[MSG_SIZ];
7722     int fromX, fromY, toX, toY;
7723     ChessMove moveType;
7724     char promoChar;
7725     char *p, *pv=buf1;
7726     int machineWhite;
7727     char *bookHit;
7728
7729     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7730         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7731         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7732             DisplayError(_("Invalid pairing from pairing engine"), 0);
7733             return;
7734         }
7735         pairingReceived = 1;
7736         NextMatchGame();
7737         return; // Skim the pairing messages here.
7738     }
7739
7740     cps->userError = 0;
7741
7742 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7743     /*
7744      * Kludge to ignore BEL characters
7745      */
7746     while (*message == '\007') message++;
7747
7748     /*
7749      * [HGM] engine debug message: ignore lines starting with '#' character
7750      */
7751     if(cps->debug && *message == '#') return;
7752
7753     /*
7754      * Look for book output
7755      */
7756     if (cps == &first && bookRequested) {
7757         if (message[0] == '\t' || message[0] == ' ') {
7758             /* Part of the book output is here; append it */
7759             strcat(bookOutput, message);
7760             strcat(bookOutput, "  \n");
7761             return;
7762         } else if (bookOutput[0] != NULLCHAR) {
7763             /* All of book output has arrived; display it */
7764             char *p = bookOutput;
7765             while (*p != NULLCHAR) {
7766                 if (*p == '\t') *p = ' ';
7767                 p++;
7768             }
7769             DisplayInformation(bookOutput);
7770             bookRequested = FALSE;
7771             /* Fall through to parse the current output */
7772         }
7773     }
7774
7775     /*
7776      * Look for machine move.
7777      */
7778     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7779         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7780     {
7781         /* This method is only useful on engines that support ping */
7782         if (cps->lastPing != cps->lastPong) {
7783           if (gameMode == BeginningOfGame) {
7784             /* Extra move from before last new; ignore */
7785             if (appData.debugMode) {
7786                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7787             }
7788           } else {
7789             if (appData.debugMode) {
7790                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7791                         cps->which, gameMode);
7792             }
7793
7794             SendToProgram("undo\n", cps);
7795           }
7796           return;
7797         }
7798
7799         switch (gameMode) {
7800           case BeginningOfGame:
7801             /* Extra move from before last reset; ignore */
7802             if (appData.debugMode) {
7803                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7804             }
7805             return;
7806
7807           case EndOfGame:
7808           case IcsIdle:
7809           default:
7810             /* Extra move after we tried to stop.  The mode test is
7811                not a reliable way of detecting this problem, but it's
7812                the best we can do on engines that don't support ping.
7813             */
7814             if (appData.debugMode) {
7815                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7816                         cps->which, gameMode);
7817             }
7818             SendToProgram("undo\n", cps);
7819             return;
7820
7821           case MachinePlaysWhite:
7822           case IcsPlayingWhite:
7823             machineWhite = TRUE;
7824             break;
7825
7826           case MachinePlaysBlack:
7827           case IcsPlayingBlack:
7828             machineWhite = FALSE;
7829             break;
7830
7831           case TwoMachinesPlay:
7832             machineWhite = (cps->twoMachinesColor[0] == 'w');
7833             break;
7834         }
7835         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7836             if (appData.debugMode) {
7837                 fprintf(debugFP,
7838                         "Ignoring move out of turn by %s, gameMode %d"
7839                         ", forwardMost %d\n",
7840                         cps->which, gameMode, forwardMostMove);
7841             }
7842             return;
7843         }
7844
7845     if (appData.debugMode) { int f = forwardMostMove;
7846         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7847                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7848                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7849     }
7850         if(cps->alphaRank) AlphaRank(machineMove, 4);
7851         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7852                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7853             /* Machine move could not be parsed; ignore it. */
7854           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7855                     machineMove, _(cps->which));
7856             DisplayError(buf1, 0);
7857             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7858                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7859             if (gameMode == TwoMachinesPlay) {
7860               GameEnds(machineWhite ? BlackWins : WhiteWins,
7861                        buf1, GE_XBOARD);
7862             }
7863             return;
7864         }
7865
7866         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7867         /* So we have to redo legality test with true e.p. status here,  */
7868         /* to make sure an illegal e.p. capture does not slip through,   */
7869         /* to cause a forfeit on a justified illegal-move complaint      */
7870         /* of the opponent.                                              */
7871         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7872            ChessMove moveType;
7873            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7874                              fromY, fromX, toY, toX, promoChar);
7875             if (appData.debugMode) {
7876                 int i;
7877                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7878                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7879                 fprintf(debugFP, "castling rights\n");
7880             }
7881             if(moveType == IllegalMove) {
7882               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7883                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7884                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7885                            buf1, GE_XBOARD);
7886                 return;
7887            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7888            /* [HGM] Kludge to handle engines that send FRC-style castling
7889               when they shouldn't (like TSCP-Gothic) */
7890            switch(moveType) {
7891              case WhiteASideCastleFR:
7892              case BlackASideCastleFR:
7893                toX+=2;
7894                currentMoveString[2]++;
7895                break;
7896              case WhiteHSideCastleFR:
7897              case BlackHSideCastleFR:
7898                toX--;
7899                currentMoveString[2]--;
7900                break;
7901              default: ; // nothing to do, but suppresses warning of pedantic compilers
7902            }
7903         }
7904         hintRequested = FALSE;
7905         lastHint[0] = NULLCHAR;
7906         bookRequested = FALSE;
7907         /* Program may be pondering now */
7908         cps->maybeThinking = TRUE;
7909         if (cps->sendTime == 2) cps->sendTime = 1;
7910         if (cps->offeredDraw) cps->offeredDraw--;
7911
7912         /* [AS] Save move info*/
7913         pvInfoList[ forwardMostMove ].score = programStats.score;
7914         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7915         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7916
7917         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7918
7919         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7920         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7921             int count = 0;
7922
7923             while( count < adjudicateLossPlies ) {
7924                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7925
7926                 if( count & 1 ) {
7927                     score = -score; /* Flip score for winning side */
7928                 }
7929
7930                 if( score > adjudicateLossThreshold ) {
7931                     break;
7932                 }
7933
7934                 count++;
7935             }
7936
7937             if( count >= adjudicateLossPlies ) {
7938                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7939
7940                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7941                     "Xboard adjudication",
7942                     GE_XBOARD );
7943
7944                 return;
7945             }
7946         }
7947
7948         if(Adjudicate(cps)) {
7949             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7950             return; // [HGM] adjudicate: for all automatic game ends
7951         }
7952
7953 #if ZIPPY
7954         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7955             first.initDone) {
7956           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7957                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7958                 SendToICS("draw ");
7959                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7960           }
7961           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7962           ics_user_moved = 1;
7963           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7964                 char buf[3*MSG_SIZ];
7965
7966                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7967                         programStats.score / 100.,
7968                         programStats.depth,
7969                         programStats.time / 100.,
7970                         (unsigned int)programStats.nodes,
7971                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7972                         programStats.movelist);
7973                 SendToICS(buf);
7974 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7975           }
7976         }
7977 #endif
7978
7979         /* [AS] Clear stats for next move */
7980         ClearProgramStats();
7981         thinkOutput[0] = NULLCHAR;
7982         hiddenThinkOutputState = 0;
7983
7984         bookHit = NULL;
7985         if (gameMode == TwoMachinesPlay) {
7986             /* [HGM] relaying draw offers moved to after reception of move */
7987             /* and interpreting offer as claim if it brings draw condition */
7988             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7989                 SendToProgram("draw\n", cps->other);
7990             }
7991             if (cps->other->sendTime) {
7992                 SendTimeRemaining(cps->other,
7993                                   cps->other->twoMachinesColor[0] == 'w');
7994             }
7995             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7996             if (firstMove && !bookHit) {
7997                 firstMove = FALSE;
7998                 if (cps->other->useColors) {
7999                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8000                 }
8001                 SendToProgram("go\n", cps->other);
8002             }
8003             cps->other->maybeThinking = TRUE;
8004         }
8005
8006         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8007
8008         if (!pausing && appData.ringBellAfterMoves) {
8009             RingBell();
8010         }
8011
8012         /*
8013          * Reenable menu items that were disabled while
8014          * machine was thinking
8015          */
8016         if (gameMode != TwoMachinesPlay)
8017             SetUserThinkingEnables();
8018
8019         // [HGM] book: after book hit opponent has received move and is now in force mode
8020         // force the book reply into it, and then fake that it outputted this move by jumping
8021         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8022         if(bookHit) {
8023                 static char bookMove[MSG_SIZ]; // a bit generous?
8024
8025                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8026                 strcat(bookMove, bookHit);
8027                 message = bookMove;
8028                 cps = cps->other;
8029                 programStats.nodes = programStats.depth = programStats.time =
8030                 programStats.score = programStats.got_only_move = 0;
8031                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8032
8033                 if(cps->lastPing != cps->lastPong) {
8034                     savedMessage = message; // args for deferred call
8035                     savedState = cps;
8036                     ScheduleDelayedEvent(DeferredBookMove, 10);
8037                     return;
8038                 }
8039                 goto FakeBookMove;
8040         }
8041
8042         return;
8043     }
8044
8045     /* Set special modes for chess engines.  Later something general
8046      *  could be added here; for now there is just one kludge feature,
8047      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8048      *  when "xboard" is given as an interactive command.
8049      */
8050     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8051         cps->useSigint = FALSE;
8052         cps->useSigterm = FALSE;
8053     }
8054     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8055       ParseFeatures(message+8, cps);
8056       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8057     }
8058
8059     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8060       int dummy, s=6; char buf[MSG_SIZ];
8061       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8062       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8063       ParseFEN(boards[0], &dummy, message+s);
8064       DrawPosition(TRUE, boards[0]);
8065       startedFromSetupPosition = TRUE;
8066       return;
8067     }
8068     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8069      * want this, I was asked to put it in, and obliged.
8070      */
8071     if (!strncmp(message, "setboard ", 9)) {
8072         Board initial_position;
8073
8074         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8075
8076         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8077             DisplayError(_("Bad FEN received from engine"), 0);
8078             return ;
8079         } else {
8080            Reset(TRUE, FALSE);
8081            CopyBoard(boards[0], initial_position);
8082            initialRulePlies = FENrulePlies;
8083            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8084            else gameMode = MachinePlaysBlack;
8085            DrawPosition(FALSE, boards[currentMove]);
8086         }
8087         return;
8088     }
8089
8090     /*
8091      * Look for communication commands
8092      */
8093     if (!strncmp(message, "telluser ", 9)) {
8094         if(message[9] == '\\' && message[10] == '\\')
8095             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8096         PlayTellSound();
8097         DisplayNote(message + 9);
8098         return;
8099     }
8100     if (!strncmp(message, "tellusererror ", 14)) {
8101         cps->userError = 1;
8102         if(message[14] == '\\' && message[15] == '\\')
8103             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8104         PlayTellSound();
8105         DisplayError(message + 14, 0);
8106         return;
8107     }
8108     if (!strncmp(message, "tellopponent ", 13)) {
8109       if (appData.icsActive) {
8110         if (loggedOn) {
8111           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8112           SendToICS(buf1);
8113         }
8114       } else {
8115         DisplayNote(message + 13);
8116       }
8117       return;
8118     }
8119     if (!strncmp(message, "tellothers ", 11)) {
8120       if (appData.icsActive) {
8121         if (loggedOn) {
8122           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8123           SendToICS(buf1);
8124         }
8125       }
8126       return;
8127     }
8128     if (!strncmp(message, "tellall ", 8)) {
8129       if (appData.icsActive) {
8130         if (loggedOn) {
8131           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8132           SendToICS(buf1);
8133         }
8134       } else {
8135         DisplayNote(message + 8);
8136       }
8137       return;
8138     }
8139     if (strncmp(message, "warning", 7) == 0) {
8140         /* Undocumented feature, use tellusererror in new code */
8141         DisplayError(message, 0);
8142         return;
8143     }
8144     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8145         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8146         strcat(realname, " query");
8147         AskQuestion(realname, buf2, buf1, cps->pr);
8148         return;
8149     }
8150     /* Commands from the engine directly to ICS.  We don't allow these to be
8151      *  sent until we are logged on. Crafty kibitzes have been known to
8152      *  interfere with the login process.
8153      */
8154     if (loggedOn) {
8155         if (!strncmp(message, "tellics ", 8)) {
8156             SendToICS(message + 8);
8157             SendToICS("\n");
8158             return;
8159         }
8160         if (!strncmp(message, "tellicsnoalias ", 15)) {
8161             SendToICS(ics_prefix);
8162             SendToICS(message + 15);
8163             SendToICS("\n");
8164             return;
8165         }
8166         /* The following are for backward compatibility only */
8167         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8168             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8169             SendToICS(ics_prefix);
8170             SendToICS(message);
8171             SendToICS("\n");
8172             return;
8173         }
8174     }
8175     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8176         return;
8177     }
8178     /*
8179      * If the move is illegal, cancel it and redraw the board.
8180      * Also deal with other error cases.  Matching is rather loose
8181      * here to accommodate engines written before the spec.
8182      */
8183     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8184         strncmp(message, "Error", 5) == 0) {
8185         if (StrStr(message, "name") ||
8186             StrStr(message, "rating") || StrStr(message, "?") ||
8187             StrStr(message, "result") || StrStr(message, "board") ||
8188             StrStr(message, "bk") || StrStr(message, "computer") ||
8189             StrStr(message, "variant") || StrStr(message, "hint") ||
8190             StrStr(message, "random") || StrStr(message, "depth") ||
8191             StrStr(message, "accepted")) {
8192             return;
8193         }
8194         if (StrStr(message, "protover")) {
8195           /* Program is responding to input, so it's apparently done
8196              initializing, and this error message indicates it is
8197              protocol version 1.  So we don't need to wait any longer
8198              for it to initialize and send feature commands. */
8199           FeatureDone(cps, 1);
8200           cps->protocolVersion = 1;
8201           return;
8202         }
8203         cps->maybeThinking = FALSE;
8204
8205         if (StrStr(message, "draw")) {
8206             /* Program doesn't have "draw" command */
8207             cps->sendDrawOffers = 0;
8208             return;
8209         }
8210         if (cps->sendTime != 1 &&
8211             (StrStr(message, "time") || StrStr(message, "otim"))) {
8212           /* Program apparently doesn't have "time" or "otim" command */
8213           cps->sendTime = 0;
8214           return;
8215         }
8216         if (StrStr(message, "analyze")) {
8217             cps->analysisSupport = FALSE;
8218             cps->analyzing = FALSE;
8219             Reset(FALSE, TRUE);
8220             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8221             DisplayError(buf2, 0);
8222             return;
8223         }
8224         if (StrStr(message, "(no matching move)st")) {
8225           /* Special kludge for GNU Chess 4 only */
8226           cps->stKludge = TRUE;
8227           SendTimeControl(cps, movesPerSession, timeControl,
8228                           timeIncrement, appData.searchDepth,
8229                           searchTime);
8230           return;
8231         }
8232         if (StrStr(message, "(no matching move)sd")) {
8233           /* Special kludge for GNU Chess 4 only */
8234           cps->sdKludge = TRUE;
8235           SendTimeControl(cps, movesPerSession, timeControl,
8236                           timeIncrement, appData.searchDepth,
8237                           searchTime);
8238           return;
8239         }
8240         if (!StrStr(message, "llegal")) {
8241             return;
8242         }
8243         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8244             gameMode == IcsIdle) return;
8245         if (forwardMostMove <= backwardMostMove) return;
8246         if (pausing) PauseEvent();
8247       if(appData.forceIllegal) {
8248             // [HGM] illegal: machine refused move; force position after move into it
8249           SendToProgram("force\n", cps);
8250           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8251                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8252                 // when black is to move, while there might be nothing on a2 or black
8253                 // might already have the move. So send the board as if white has the move.
8254                 // But first we must change the stm of the engine, as it refused the last move
8255                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8256                 if(WhiteOnMove(forwardMostMove)) {
8257                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8258                     SendBoard(cps, forwardMostMove); // kludgeless board
8259                 } else {
8260                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8261                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8262                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8263                 }
8264           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8265             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8266                  gameMode == TwoMachinesPlay)
8267               SendToProgram("go\n", cps);
8268             return;
8269       } else
8270         if (gameMode == PlayFromGameFile) {
8271             /* Stop reading this game file */
8272             gameMode = EditGame;
8273             ModeHighlight();
8274         }
8275         /* [HGM] illegal-move claim should forfeit game when Xboard */
8276         /* only passes fully legal moves                            */
8277         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8278             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8279                                 "False illegal-move claim", GE_XBOARD );
8280             return; // do not take back move we tested as valid
8281         }
8282         currentMove = forwardMostMove-1;
8283         DisplayMove(currentMove-1); /* before DisplayMoveError */
8284         SwitchClocks(forwardMostMove-1); // [HGM] race
8285         DisplayBothClocks();
8286         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8287                 parseList[currentMove], _(cps->which));
8288         DisplayMoveError(buf1);
8289         DrawPosition(FALSE, boards[currentMove]);
8290         return;
8291     }
8292     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8293         /* Program has a broken "time" command that
8294            outputs a string not ending in newline.
8295            Don't use it. */
8296         cps->sendTime = 0;
8297     }
8298
8299     /*
8300      * If chess program startup fails, exit with an error message.
8301      * Attempts to recover here are futile.
8302      */
8303     if ((StrStr(message, "unknown host") != NULL)
8304         || (StrStr(message, "No remote directory") != NULL)
8305         || (StrStr(message, "not found") != NULL)
8306         || (StrStr(message, "No such file") != NULL)
8307         || (StrStr(message, "can't alloc") != NULL)
8308         || (StrStr(message, "Permission denied") != NULL)) {
8309
8310         cps->maybeThinking = FALSE;
8311         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8312                 _(cps->which), cps->program, cps->host, message);
8313         RemoveInputSource(cps->isr);
8314         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8315             if(cps == &first) appData.noChessProgram = TRUE;
8316             DisplayError(buf1, 0);
8317         }
8318         return;
8319     }
8320
8321     /*
8322      * Look for hint output
8323      */
8324     if (sscanf(message, "Hint: %s", buf1) == 1) {
8325         if (cps == &first && hintRequested) {
8326             hintRequested = FALSE;
8327             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8328                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8329                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8330                                     PosFlags(forwardMostMove),
8331                                     fromY, fromX, toY, toX, promoChar, buf1);
8332                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8333                 DisplayInformation(buf2);
8334             } else {
8335                 /* Hint move could not be parsed!? */
8336               snprintf(buf2, sizeof(buf2),
8337                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8338                         buf1, _(cps->which));
8339                 DisplayError(buf2, 0);
8340             }
8341         } else {
8342           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8343         }
8344         return;
8345     }
8346
8347     /*
8348      * Ignore other messages if game is not in progress
8349      */
8350     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8351         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8352
8353     /*
8354      * look for win, lose, draw, or draw offer
8355      */
8356     if (strncmp(message, "1-0", 3) == 0) {
8357         char *p, *q, *r = "";
8358         p = strchr(message, '{');
8359         if (p) {
8360             q = strchr(p, '}');
8361             if (q) {
8362                 *q = NULLCHAR;
8363                 r = p + 1;
8364             }
8365         }
8366         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8367         return;
8368     } else if (strncmp(message, "0-1", 3) == 0) {
8369         char *p, *q, *r = "";
8370         p = strchr(message, '{');
8371         if (p) {
8372             q = strchr(p, '}');
8373             if (q) {
8374                 *q = NULLCHAR;
8375                 r = p + 1;
8376             }
8377         }
8378         /* Kludge for Arasan 4.1 bug */
8379         if (strcmp(r, "Black resigns") == 0) {
8380             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8381             return;
8382         }
8383         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8384         return;
8385     } else if (strncmp(message, "1/2", 3) == 0) {
8386         char *p, *q, *r = "";
8387         p = strchr(message, '{');
8388         if (p) {
8389             q = strchr(p, '}');
8390             if (q) {
8391                 *q = NULLCHAR;
8392                 r = p + 1;
8393             }
8394         }
8395
8396         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8397         return;
8398
8399     } else if (strncmp(message, "White resign", 12) == 0) {
8400         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8401         return;
8402     } else if (strncmp(message, "Black resign", 12) == 0) {
8403         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8404         return;
8405     } else if (strncmp(message, "White matches", 13) == 0 ||
8406                strncmp(message, "Black matches", 13) == 0   ) {
8407         /* [HGM] ignore GNUShogi noises */
8408         return;
8409     } else if (strncmp(message, "White", 5) == 0 &&
8410                message[5] != '(' &&
8411                StrStr(message, "Black") == NULL) {
8412         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8413         return;
8414     } else if (strncmp(message, "Black", 5) == 0 &&
8415                message[5] != '(') {
8416         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8417         return;
8418     } else if (strcmp(message, "resign") == 0 ||
8419                strcmp(message, "computer resigns") == 0) {
8420         switch (gameMode) {
8421           case MachinePlaysBlack:
8422           case IcsPlayingBlack:
8423             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8424             break;
8425           case MachinePlaysWhite:
8426           case IcsPlayingWhite:
8427             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8428             break;
8429           case TwoMachinesPlay:
8430             if (cps->twoMachinesColor[0] == 'w')
8431               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8432             else
8433               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8434             break;
8435           default:
8436             /* can't happen */
8437             break;
8438         }
8439         return;
8440     } else if (strncmp(message, "opponent mates", 14) == 0) {
8441         switch (gameMode) {
8442           case MachinePlaysBlack:
8443           case IcsPlayingBlack:
8444             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8445             break;
8446           case MachinePlaysWhite:
8447           case IcsPlayingWhite:
8448             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8449             break;
8450           case TwoMachinesPlay:
8451             if (cps->twoMachinesColor[0] == 'w')
8452               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8453             else
8454               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8455             break;
8456           default:
8457             /* can't happen */
8458             break;
8459         }
8460         return;
8461     } else if (strncmp(message, "computer mates", 14) == 0) {
8462         switch (gameMode) {
8463           case MachinePlaysBlack:
8464           case IcsPlayingBlack:
8465             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8466             break;
8467           case MachinePlaysWhite:
8468           case IcsPlayingWhite:
8469             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8470             break;
8471           case TwoMachinesPlay:
8472             if (cps->twoMachinesColor[0] == 'w')
8473               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8474             else
8475               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8476             break;
8477           default:
8478             /* can't happen */
8479             break;
8480         }
8481         return;
8482     } else if (strncmp(message, "checkmate", 9) == 0) {
8483         if (WhiteOnMove(forwardMostMove)) {
8484             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8485         } else {
8486             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8487         }
8488         return;
8489     } else if (strstr(message, "Draw") != NULL ||
8490                strstr(message, "game is a draw") != NULL) {
8491         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8492         return;
8493     } else if (strstr(message, "offer") != NULL &&
8494                strstr(message, "draw") != NULL) {
8495 #if ZIPPY
8496         if (appData.zippyPlay && first.initDone) {
8497             /* Relay offer to ICS */
8498             SendToICS(ics_prefix);
8499             SendToICS("draw\n");
8500         }
8501 #endif
8502         cps->offeredDraw = 2; /* valid until this engine moves twice */
8503         if (gameMode == TwoMachinesPlay) {
8504             if (cps->other->offeredDraw) {
8505                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8506             /* [HGM] in two-machine mode we delay relaying draw offer      */
8507             /* until after we also have move, to see if it is really claim */
8508             }
8509         } else if (gameMode == MachinePlaysWhite ||
8510                    gameMode == MachinePlaysBlack) {
8511           if (userOfferedDraw) {
8512             DisplayInformation(_("Machine accepts your draw offer"));
8513             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8514           } else {
8515             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8516           }
8517         }
8518     }
8519
8520
8521     /*
8522      * Look for thinking output
8523      */
8524     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8525           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8526                                 ) {
8527         int plylev, mvleft, mvtot, curscore, time;
8528         char mvname[MOVE_LEN];
8529         u64 nodes; // [DM]
8530         char plyext;
8531         int ignore = FALSE;
8532         int prefixHint = FALSE;
8533         mvname[0] = NULLCHAR;
8534
8535         switch (gameMode) {
8536           case MachinePlaysBlack:
8537           case IcsPlayingBlack:
8538             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8539             break;
8540           case MachinePlaysWhite:
8541           case IcsPlayingWhite:
8542             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8543             break;
8544           case AnalyzeMode:
8545           case AnalyzeFile:
8546             break;
8547           case IcsObserving: /* [DM] icsEngineAnalyze */
8548             if (!appData.icsEngineAnalyze) ignore = TRUE;
8549             break;
8550           case TwoMachinesPlay:
8551             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8552                 ignore = TRUE;
8553             }
8554             break;
8555           default:
8556             ignore = TRUE;
8557             break;
8558         }
8559
8560         if (!ignore) {
8561             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8562             buf1[0] = NULLCHAR;
8563             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8564                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8565
8566                 if (plyext != ' ' && plyext != '\t') {
8567                     time *= 100;
8568                 }
8569
8570                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8571                 if( cps->scoreIsAbsolute &&
8572                     ( gameMode == MachinePlaysBlack ||
8573                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8574                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8575                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8576                      !WhiteOnMove(currentMove)
8577                     ) )
8578                 {
8579                     curscore = -curscore;
8580                 }
8581
8582                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8583
8584                 tempStats.depth = plylev;
8585                 tempStats.nodes = nodes;
8586                 tempStats.time = time;
8587                 tempStats.score = curscore;
8588                 tempStats.got_only_move = 0;
8589
8590                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8591                         int ticklen;
8592
8593                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8594                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8595                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8596                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8597                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8598                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8599                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8600                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8601                 }
8602
8603                 /* Buffer overflow protection */
8604                 if (pv[0] != NULLCHAR) {
8605                     if (strlen(pv) >= sizeof(tempStats.movelist)
8606                         && appData.debugMode) {
8607                         fprintf(debugFP,
8608                                 "PV is too long; using the first %u bytes.\n",
8609                                 (unsigned) sizeof(tempStats.movelist) - 1);
8610                     }
8611
8612                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8613                 } else {
8614                     sprintf(tempStats.movelist, " no PV\n");
8615                 }
8616
8617                 if (tempStats.seen_stat) {
8618                     tempStats.ok_to_send = 1;
8619                 }
8620
8621                 if (strchr(tempStats.movelist, '(') != NULL) {
8622                     tempStats.line_is_book = 1;
8623                     tempStats.nr_moves = 0;
8624                     tempStats.moves_left = 0;
8625                 } else {
8626                     tempStats.line_is_book = 0;
8627                 }
8628
8629                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8630                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8631
8632                 SendProgramStatsToFrontend( cps, &tempStats );
8633
8634                 /*
8635                     [AS] Protect the thinkOutput buffer from overflow... this
8636                     is only useful if buf1 hasn't overflowed first!
8637                 */
8638                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8639                          plylev,
8640                          (gameMode == TwoMachinesPlay ?
8641                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8642                          ((double) curscore) / 100.0,
8643                          prefixHint ? lastHint : "",
8644                          prefixHint ? " " : "" );
8645
8646                 if( buf1[0] != NULLCHAR ) {
8647                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8648
8649                     if( strlen(pv) > max_len ) {
8650                         if( appData.debugMode) {
8651                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8652                         }
8653                         pv[max_len+1] = '\0';
8654                     }
8655
8656                     strcat( thinkOutput, pv);
8657                 }
8658
8659                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8660                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8661                     DisplayMove(currentMove - 1);
8662                 }
8663                 return;
8664
8665             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8666                 /* crafty (9.25+) says "(only move) <move>"
8667                  * if there is only 1 legal move
8668                  */
8669                 sscanf(p, "(only move) %s", buf1);
8670                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8671                 sprintf(programStats.movelist, "%s (only move)", buf1);
8672                 programStats.depth = 1;
8673                 programStats.nr_moves = 1;
8674                 programStats.moves_left = 1;
8675                 programStats.nodes = 1;
8676                 programStats.time = 1;
8677                 programStats.got_only_move = 1;
8678
8679                 /* Not really, but we also use this member to
8680                    mean "line isn't going to change" (Crafty
8681                    isn't searching, so stats won't change) */
8682                 programStats.line_is_book = 1;
8683
8684                 SendProgramStatsToFrontend( cps, &programStats );
8685
8686                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8687                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8688                     DisplayMove(currentMove - 1);
8689                 }
8690                 return;
8691             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8692                               &time, &nodes, &plylev, &mvleft,
8693                               &mvtot, mvname) >= 5) {
8694                 /* The stat01: line is from Crafty (9.29+) in response
8695                    to the "." command */
8696                 programStats.seen_stat = 1;
8697                 cps->maybeThinking = TRUE;
8698
8699                 if (programStats.got_only_move || !appData.periodicUpdates)
8700                   return;
8701
8702                 programStats.depth = plylev;
8703                 programStats.time = time;
8704                 programStats.nodes = nodes;
8705                 programStats.moves_left = mvleft;
8706                 programStats.nr_moves = mvtot;
8707                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8708                 programStats.ok_to_send = 1;
8709                 programStats.movelist[0] = '\0';
8710
8711                 SendProgramStatsToFrontend( cps, &programStats );
8712
8713                 return;
8714
8715             } else if (strncmp(message,"++",2) == 0) {
8716                 /* Crafty 9.29+ outputs this */
8717                 programStats.got_fail = 2;
8718                 return;
8719
8720             } else if (strncmp(message,"--",2) == 0) {
8721                 /* Crafty 9.29+ outputs this */
8722                 programStats.got_fail = 1;
8723                 return;
8724
8725             } else if (thinkOutput[0] != NULLCHAR &&
8726                        strncmp(message, "    ", 4) == 0) {
8727                 unsigned message_len;
8728
8729                 p = message;
8730                 while (*p && *p == ' ') p++;
8731
8732                 message_len = strlen( p );
8733
8734                 /* [AS] Avoid buffer overflow */
8735                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8736                     strcat(thinkOutput, " ");
8737                     strcat(thinkOutput, p);
8738                 }
8739
8740                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8741                     strcat(programStats.movelist, " ");
8742                     strcat(programStats.movelist, p);
8743                 }
8744
8745                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8746                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8747                     DisplayMove(currentMove - 1);
8748                 }
8749                 return;
8750             }
8751         }
8752         else {
8753             buf1[0] = NULLCHAR;
8754
8755             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8756                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8757             {
8758                 ChessProgramStats cpstats;
8759
8760                 if (plyext != ' ' && plyext != '\t') {
8761                     time *= 100;
8762                 }
8763
8764                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8765                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8766                     curscore = -curscore;
8767                 }
8768
8769                 cpstats.depth = plylev;
8770                 cpstats.nodes = nodes;
8771                 cpstats.time = time;
8772                 cpstats.score = curscore;
8773                 cpstats.got_only_move = 0;
8774                 cpstats.movelist[0] = '\0';
8775
8776                 if (buf1[0] != NULLCHAR) {
8777                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8778                 }
8779
8780                 cpstats.ok_to_send = 0;
8781                 cpstats.line_is_book = 0;
8782                 cpstats.nr_moves = 0;
8783                 cpstats.moves_left = 0;
8784
8785                 SendProgramStatsToFrontend( cps, &cpstats );
8786             }
8787         }
8788     }
8789 }
8790
8791
8792 /* Parse a game score from the character string "game", and
8793    record it as the history of the current game.  The game
8794    score is NOT assumed to start from the standard position.
8795    The display is not updated in any way.
8796    */
8797 void
8798 ParseGameHistory(game)
8799      char *game;
8800 {
8801     ChessMove moveType;
8802     int fromX, fromY, toX, toY, boardIndex;
8803     char promoChar;
8804     char *p, *q;
8805     char buf[MSG_SIZ];
8806
8807     if (appData.debugMode)
8808       fprintf(debugFP, "Parsing game history: %s\n", game);
8809
8810     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8811     gameInfo.site = StrSave(appData.icsHost);
8812     gameInfo.date = PGNDate();
8813     gameInfo.round = StrSave("-");
8814
8815     /* Parse out names of players */
8816     while (*game == ' ') game++;
8817     p = buf;
8818     while (*game != ' ') *p++ = *game++;
8819     *p = NULLCHAR;
8820     gameInfo.white = StrSave(buf);
8821     while (*game == ' ') game++;
8822     p = buf;
8823     while (*game != ' ' && *game != '\n') *p++ = *game++;
8824     *p = NULLCHAR;
8825     gameInfo.black = StrSave(buf);
8826
8827     /* Parse moves */
8828     boardIndex = blackPlaysFirst ? 1 : 0;
8829     yynewstr(game);
8830     for (;;) {
8831         yyboardindex = boardIndex;
8832         moveType = (ChessMove) Myylex();
8833         switch (moveType) {
8834           case IllegalMove:             /* maybe suicide chess, etc. */
8835   if (appData.debugMode) {
8836     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8837     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8838     setbuf(debugFP, NULL);
8839   }
8840           case WhitePromotion:
8841           case BlackPromotion:
8842           case WhiteNonPromotion:
8843           case BlackNonPromotion:
8844           case NormalMove:
8845           case WhiteCapturesEnPassant:
8846           case BlackCapturesEnPassant:
8847           case WhiteKingSideCastle:
8848           case WhiteQueenSideCastle:
8849           case BlackKingSideCastle:
8850           case BlackQueenSideCastle:
8851           case WhiteKingSideCastleWild:
8852           case WhiteQueenSideCastleWild:
8853           case BlackKingSideCastleWild:
8854           case BlackQueenSideCastleWild:
8855           /* PUSH Fabien */
8856           case WhiteHSideCastleFR:
8857           case WhiteASideCastleFR:
8858           case BlackHSideCastleFR:
8859           case BlackASideCastleFR:
8860           /* POP Fabien */
8861             fromX = currentMoveString[0] - AAA;
8862             fromY = currentMoveString[1] - ONE;
8863             toX = currentMoveString[2] - AAA;
8864             toY = currentMoveString[3] - ONE;
8865             promoChar = currentMoveString[4];
8866             break;
8867           case WhiteDrop:
8868           case BlackDrop:
8869             fromX = moveType == WhiteDrop ?
8870               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8871             (int) CharToPiece(ToLower(currentMoveString[0]));
8872             fromY = DROP_RANK;
8873             toX = currentMoveString[2] - AAA;
8874             toY = currentMoveString[3] - ONE;
8875             promoChar = NULLCHAR;
8876             break;
8877           case AmbiguousMove:
8878             /* bug? */
8879             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8880   if (appData.debugMode) {
8881     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8882     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8883     setbuf(debugFP, NULL);
8884   }
8885             DisplayError(buf, 0);
8886             return;
8887           case ImpossibleMove:
8888             /* bug? */
8889             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8890   if (appData.debugMode) {
8891     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8892     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8893     setbuf(debugFP, NULL);
8894   }
8895             DisplayError(buf, 0);
8896             return;
8897           case EndOfFile:
8898             if (boardIndex < backwardMostMove) {
8899                 /* Oops, gap.  How did that happen? */
8900                 DisplayError(_("Gap in move list"), 0);
8901                 return;
8902             }
8903             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8904             if (boardIndex > forwardMostMove) {
8905                 forwardMostMove = boardIndex;
8906             }
8907             return;
8908           case ElapsedTime:
8909             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8910                 strcat(parseList[boardIndex-1], " ");
8911                 strcat(parseList[boardIndex-1], yy_text);
8912             }
8913             continue;
8914           case Comment:
8915           case PGNTag:
8916           case NAG:
8917           default:
8918             /* ignore */
8919             continue;
8920           case WhiteWins:
8921           case BlackWins:
8922           case GameIsDrawn:
8923           case GameUnfinished:
8924             if (gameMode == IcsExamining) {
8925                 if (boardIndex < backwardMostMove) {
8926                     /* Oops, gap.  How did that happen? */
8927                     return;
8928                 }
8929                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8930                 return;
8931             }
8932             gameInfo.result = moveType;
8933             p = strchr(yy_text, '{');
8934             if (p == NULL) p = strchr(yy_text, '(');
8935             if (p == NULL) {
8936                 p = yy_text;
8937                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8938             } else {
8939                 q = strchr(p, *p == '{' ? '}' : ')');
8940                 if (q != NULL) *q = NULLCHAR;
8941                 p++;
8942             }
8943             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8944             gameInfo.resultDetails = StrSave(p);
8945             continue;
8946         }
8947         if (boardIndex >= forwardMostMove &&
8948             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8949             backwardMostMove = blackPlaysFirst ? 1 : 0;
8950             return;
8951         }
8952         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8953                                  fromY, fromX, toY, toX, promoChar,
8954                                  parseList[boardIndex]);
8955         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8956         /* currentMoveString is set as a side-effect of yylex */
8957         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8958         strcat(moveList[boardIndex], "\n");
8959         boardIndex++;
8960         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8961         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8962           case MT_NONE:
8963           case MT_STALEMATE:
8964           default:
8965             break;
8966           case MT_CHECK:
8967             if(gameInfo.variant != VariantShogi)
8968                 strcat(parseList[boardIndex - 1], "+");
8969             break;
8970           case MT_CHECKMATE:
8971           case MT_STAINMATE:
8972             strcat(parseList[boardIndex - 1], "#");
8973             break;
8974         }
8975     }
8976 }
8977
8978
8979 /* Apply a move to the given board  */
8980 void
8981 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8982      int fromX, fromY, toX, toY;
8983      int promoChar;
8984      Board board;
8985 {
8986   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8987   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
8988
8989     /* [HGM] compute & store e.p. status and castling rights for new position */
8990     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8991
8992       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8993       oldEP = (signed char)board[EP_STATUS];
8994       board[EP_STATUS] = EP_NONE;
8995
8996       if( board[toY][toX] != EmptySquare )
8997            board[EP_STATUS] = EP_CAPTURE;
8998
8999   if (fromY == DROP_RANK) {
9000         /* must be first */
9001         piece = board[toY][toX] = (ChessSquare) fromX;
9002   } else {
9003       int i;
9004
9005       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9006            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9007                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9008       } else
9009       if( board[fromY][fromX] == WhitePawn ) {
9010            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9011                board[EP_STATUS] = EP_PAWN_MOVE;
9012            if( toY-fromY==2) {
9013                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9014                         gameInfo.variant != VariantBerolina || toX < fromX)
9015                       board[EP_STATUS] = toX | berolina;
9016                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9017                         gameInfo.variant != VariantBerolina || toX > fromX)
9018                       board[EP_STATUS] = toX;
9019            }
9020       } else
9021       if( board[fromY][fromX] == BlackPawn ) {
9022            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9023                board[EP_STATUS] = EP_PAWN_MOVE;
9024            if( toY-fromY== -2) {
9025                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9026                         gameInfo.variant != VariantBerolina || toX < fromX)
9027                       board[EP_STATUS] = toX | berolina;
9028                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9029                         gameInfo.variant != VariantBerolina || toX > fromX)
9030                       board[EP_STATUS] = toX;
9031            }
9032        }
9033
9034        for(i=0; i<nrCastlingRights; i++) {
9035            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9036               board[CASTLING][i] == toX   && castlingRank[i] == toY
9037              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9038        }
9039
9040      if (fromX == toX && fromY == toY) return;
9041
9042      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9043      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9044      if(gameInfo.variant == VariantKnightmate)
9045          king += (int) WhiteUnicorn - (int) WhiteKing;
9046
9047     /* Code added by Tord: */
9048     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9049     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9050         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9051       board[fromY][fromX] = EmptySquare;
9052       board[toY][toX] = EmptySquare;
9053       if((toX > fromX) != (piece == WhiteRook)) {
9054         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9055       } else {
9056         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9057       }
9058     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9059                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9060       board[fromY][fromX] = EmptySquare;
9061       board[toY][toX] = EmptySquare;
9062       if((toX > fromX) != (piece == BlackRook)) {
9063         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9064       } else {
9065         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9066       }
9067     /* End of code added by Tord */
9068
9069     } else if (board[fromY][fromX] == king
9070         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9071         && toY == fromY && toX > fromX+1) {
9072         board[fromY][fromX] = EmptySquare;
9073         board[toY][toX] = king;
9074         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9075         board[fromY][BOARD_RGHT-1] = EmptySquare;
9076     } else if (board[fromY][fromX] == king
9077         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9078                && toY == fromY && toX < fromX-1) {
9079         board[fromY][fromX] = EmptySquare;
9080         board[toY][toX] = king;
9081         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9082         board[fromY][BOARD_LEFT] = EmptySquare;
9083     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9084                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9085                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9086                ) {
9087         /* white pawn promotion */
9088         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9089         if(gameInfo.variant==VariantBughouse ||
9090            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9091             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9092         board[fromY][fromX] = EmptySquare;
9093     } else if ((fromY >= BOARD_HEIGHT>>1)
9094                && (toX != fromX)
9095                && gameInfo.variant != VariantXiangqi
9096                && gameInfo.variant != VariantBerolina
9097                && (board[fromY][fromX] == WhitePawn)
9098                && (board[toY][toX] == EmptySquare)) {
9099         board[fromY][fromX] = EmptySquare;
9100         board[toY][toX] = WhitePawn;
9101         captured = board[toY - 1][toX];
9102         board[toY - 1][toX] = EmptySquare;
9103     } else if ((fromY == BOARD_HEIGHT-4)
9104                && (toX == fromX)
9105                && gameInfo.variant == VariantBerolina
9106                && (board[fromY][fromX] == WhitePawn)
9107                && (board[toY][toX] == EmptySquare)) {
9108         board[fromY][fromX] = EmptySquare;
9109         board[toY][toX] = WhitePawn;
9110         if(oldEP & EP_BEROLIN_A) {
9111                 captured = board[fromY][fromX-1];
9112                 board[fromY][fromX-1] = EmptySquare;
9113         }else{  captured = board[fromY][fromX+1];
9114                 board[fromY][fromX+1] = EmptySquare;
9115         }
9116     } else if (board[fromY][fromX] == king
9117         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9118                && toY == fromY && toX > fromX+1) {
9119         board[fromY][fromX] = EmptySquare;
9120         board[toY][toX] = king;
9121         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9122         board[fromY][BOARD_RGHT-1] = EmptySquare;
9123     } else if (board[fromY][fromX] == king
9124         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9125                && toY == fromY && toX < fromX-1) {
9126         board[fromY][fromX] = EmptySquare;
9127         board[toY][toX] = king;
9128         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9129         board[fromY][BOARD_LEFT] = EmptySquare;
9130     } else if (fromY == 7 && fromX == 3
9131                && board[fromY][fromX] == BlackKing
9132                && toY == 7 && toX == 5) {
9133         board[fromY][fromX] = EmptySquare;
9134         board[toY][toX] = BlackKing;
9135         board[fromY][7] = EmptySquare;
9136         board[toY][4] = BlackRook;
9137     } else if (fromY == 7 && fromX == 3
9138                && board[fromY][fromX] == BlackKing
9139                && toY == 7 && toX == 1) {
9140         board[fromY][fromX] = EmptySquare;
9141         board[toY][toX] = BlackKing;
9142         board[fromY][0] = EmptySquare;
9143         board[toY][2] = BlackRook;
9144     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9145                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9146                && toY < promoRank && promoChar
9147                ) {
9148         /* black pawn promotion */
9149         board[toY][toX] = CharToPiece(ToLower(promoChar));
9150         if(gameInfo.variant==VariantBughouse ||
9151            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9152             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9153         board[fromY][fromX] = EmptySquare;
9154     } else if ((fromY < BOARD_HEIGHT>>1)
9155                && (toX != fromX)
9156                && gameInfo.variant != VariantXiangqi
9157                && gameInfo.variant != VariantBerolina
9158                && (board[fromY][fromX] == BlackPawn)
9159                && (board[toY][toX] == EmptySquare)) {
9160         board[fromY][fromX] = EmptySquare;
9161         board[toY][toX] = BlackPawn;
9162         captured = board[toY + 1][toX];
9163         board[toY + 1][toX] = EmptySquare;
9164     } else if ((fromY == 3)
9165                && (toX == fromX)
9166                && gameInfo.variant == VariantBerolina
9167                && (board[fromY][fromX] == BlackPawn)
9168                && (board[toY][toX] == EmptySquare)) {
9169         board[fromY][fromX] = EmptySquare;
9170         board[toY][toX] = BlackPawn;
9171         if(oldEP & EP_BEROLIN_A) {
9172                 captured = board[fromY][fromX-1];
9173                 board[fromY][fromX-1] = EmptySquare;
9174         }else{  captured = board[fromY][fromX+1];
9175                 board[fromY][fromX+1] = EmptySquare;
9176         }
9177     } else {
9178         board[toY][toX] = board[fromY][fromX];
9179         board[fromY][fromX] = EmptySquare;
9180     }
9181   }
9182
9183     if (gameInfo.holdingsWidth != 0) {
9184
9185       /* !!A lot more code needs to be written to support holdings  */
9186       /* [HGM] OK, so I have written it. Holdings are stored in the */
9187       /* penultimate board files, so they are automaticlly stored   */
9188       /* in the game history.                                       */
9189       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9190                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9191         /* Delete from holdings, by decreasing count */
9192         /* and erasing image if necessary            */
9193         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9194         if(p < (int) BlackPawn) { /* white drop */
9195              p -= (int)WhitePawn;
9196                  p = PieceToNumber((ChessSquare)p);
9197              if(p >= gameInfo.holdingsSize) p = 0;
9198              if(--board[p][BOARD_WIDTH-2] <= 0)
9199                   board[p][BOARD_WIDTH-1] = EmptySquare;
9200              if((int)board[p][BOARD_WIDTH-2] < 0)
9201                         board[p][BOARD_WIDTH-2] = 0;
9202         } else {                  /* black drop */
9203              p -= (int)BlackPawn;
9204                  p = PieceToNumber((ChessSquare)p);
9205              if(p >= gameInfo.holdingsSize) p = 0;
9206              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9207                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9208              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9209                         board[BOARD_HEIGHT-1-p][1] = 0;
9210         }
9211       }
9212       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9213           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9214         /* [HGM] holdings: Add to holdings, if holdings exist */
9215         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9216                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9217                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9218         }
9219         p = (int) captured;
9220         if (p >= (int) BlackPawn) {
9221           p -= (int)BlackPawn;
9222           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9223                   /* in Shogi restore piece to its original  first */
9224                   captured = (ChessSquare) (DEMOTED captured);
9225                   p = DEMOTED p;
9226           }
9227           p = PieceToNumber((ChessSquare)p);
9228           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9229           board[p][BOARD_WIDTH-2]++;
9230           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9231         } else {
9232           p -= (int)WhitePawn;
9233           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9234                   captured = (ChessSquare) (DEMOTED captured);
9235                   p = DEMOTED p;
9236           }
9237           p = PieceToNumber((ChessSquare)p);
9238           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9239           board[BOARD_HEIGHT-1-p][1]++;
9240           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9241         }
9242       }
9243     } else if (gameInfo.variant == VariantAtomic) {
9244       if (captured != EmptySquare) {
9245         int y, x;
9246         for (y = toY-1; y <= toY+1; y++) {
9247           for (x = toX-1; x <= toX+1; x++) {
9248             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9249                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9250               board[y][x] = EmptySquare;
9251             }
9252           }
9253         }
9254         board[toY][toX] = EmptySquare;
9255       }
9256     }
9257     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9258         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9259     } else
9260     if(promoChar == '+') {
9261         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9262         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9263     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9264         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9265     }
9266     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9267                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9268         // [HGM] superchess: take promotion piece out of holdings
9269         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9270         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9271             if(!--board[k][BOARD_WIDTH-2])
9272                 board[k][BOARD_WIDTH-1] = EmptySquare;
9273         } else {
9274             if(!--board[BOARD_HEIGHT-1-k][1])
9275                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9276         }
9277     }
9278
9279 }
9280
9281 /* Updates forwardMostMove */
9282 void
9283 MakeMove(fromX, fromY, toX, toY, promoChar)
9284      int fromX, fromY, toX, toY;
9285      int promoChar;
9286 {
9287 //    forwardMostMove++; // [HGM] bare: moved downstream
9288
9289     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9290         int timeLeft; static int lastLoadFlag=0; int king, piece;
9291         piece = boards[forwardMostMove][fromY][fromX];
9292         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9293         if(gameInfo.variant == VariantKnightmate)
9294             king += (int) WhiteUnicorn - (int) WhiteKing;
9295         if(forwardMostMove == 0) {
9296             if(blackPlaysFirst)
9297                 fprintf(serverMoves, "%s;", second.tidy);
9298             fprintf(serverMoves, "%s;", first.tidy);
9299             if(!blackPlaysFirst)
9300                 fprintf(serverMoves, "%s;", second.tidy);
9301         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9302         lastLoadFlag = loadFlag;
9303         // print base move
9304         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9305         // print castling suffix
9306         if( toY == fromY && piece == king ) {
9307             if(toX-fromX > 1)
9308                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9309             if(fromX-toX >1)
9310                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9311         }
9312         // e.p. suffix
9313         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9314              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9315              boards[forwardMostMove][toY][toX] == EmptySquare
9316              && fromX != toX && fromY != toY)
9317                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9318         // promotion suffix
9319         if(promoChar != NULLCHAR)
9320                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9321         if(!loadFlag) {
9322             fprintf(serverMoves, "/%d/%d",
9323                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9324             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9325             else                      timeLeft = blackTimeRemaining/1000;
9326             fprintf(serverMoves, "/%d", timeLeft);
9327         }
9328         fflush(serverMoves);
9329     }
9330
9331     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9332       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9333                         0, 1);
9334       return;
9335     }
9336     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9337     if (commentList[forwardMostMove+1] != NULL) {
9338         free(commentList[forwardMostMove+1]);
9339         commentList[forwardMostMove+1] = NULL;
9340     }
9341     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9342     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9343     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9344     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9345     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9346     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9347     gameInfo.result = GameUnfinished;
9348     if (gameInfo.resultDetails != NULL) {
9349         free(gameInfo.resultDetails);
9350         gameInfo.resultDetails = NULL;
9351     }
9352     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9353                               moveList[forwardMostMove - 1]);
9354     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9355                              PosFlags(forwardMostMove - 1),
9356                              fromY, fromX, toY, toX, promoChar,
9357                              parseList[forwardMostMove - 1]);
9358     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9359       case MT_NONE:
9360       case MT_STALEMATE:
9361       default:
9362         break;
9363       case MT_CHECK:
9364         if(gameInfo.variant != VariantShogi)
9365             strcat(parseList[forwardMostMove - 1], "+");
9366         break;
9367       case MT_CHECKMATE:
9368       case MT_STAINMATE:
9369         strcat(parseList[forwardMostMove - 1], "#");
9370         break;
9371     }
9372     if (appData.debugMode) {
9373         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9374     }
9375
9376 }
9377
9378 /* Updates currentMove if not pausing */
9379 void
9380 ShowMove(fromX, fromY, toX, toY)
9381 {
9382     int instant = (gameMode == PlayFromGameFile) ?
9383         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9384     if(appData.noGUI) return;
9385     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9386         if (!instant) {
9387             if (forwardMostMove == currentMove + 1) {
9388                 AnimateMove(boards[forwardMostMove - 1],
9389                             fromX, fromY, toX, toY);
9390             }
9391             if (appData.highlightLastMove) {
9392                 SetHighlights(fromX, fromY, toX, toY);
9393             }
9394         }
9395         currentMove = forwardMostMove;
9396     }
9397
9398     if (instant) return;
9399
9400     DisplayMove(currentMove - 1);
9401     DrawPosition(FALSE, boards[currentMove]);
9402     DisplayBothClocks();
9403     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9404     DisplayBook(currentMove);
9405 }
9406
9407 void SendEgtPath(ChessProgramState *cps)
9408 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9409         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9410
9411         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9412
9413         while(*p) {
9414             char c, *q = name+1, *r, *s;
9415
9416             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9417             while(*p && *p != ',') *q++ = *p++;
9418             *q++ = ':'; *q = 0;
9419             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9420                 strcmp(name, ",nalimov:") == 0 ) {
9421                 // take nalimov path from the menu-changeable option first, if it is defined
9422               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9423                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9424             } else
9425             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9426                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9427                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9428                 s = r = StrStr(s, ":") + 1; // beginning of path info
9429                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9430                 c = *r; *r = 0;             // temporarily null-terminate path info
9431                     *--q = 0;               // strip of trailig ':' from name
9432                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9433                 *r = c;
9434                 SendToProgram(buf,cps);     // send egtbpath command for this format
9435             }
9436             if(*p == ',') p++; // read away comma to position for next format name
9437         }
9438 }
9439
9440 void
9441 InitChessProgram(cps, setup)
9442      ChessProgramState *cps;
9443      int setup; /* [HGM] needed to setup FRC opening position */
9444 {
9445     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9446     if (appData.noChessProgram) return;
9447     hintRequested = FALSE;
9448     bookRequested = FALSE;
9449
9450     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9451     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9452     if(cps->memSize) { /* [HGM] memory */
9453       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9454         SendToProgram(buf, cps);
9455     }
9456     SendEgtPath(cps); /* [HGM] EGT */
9457     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9458       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9459         SendToProgram(buf, cps);
9460     }
9461
9462     SendToProgram(cps->initString, cps);
9463     if (gameInfo.variant != VariantNormal &&
9464         gameInfo.variant != VariantLoadable
9465         /* [HGM] also send variant if board size non-standard */
9466         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9467                                             ) {
9468       char *v = VariantName(gameInfo.variant);
9469       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9470         /* [HGM] in protocol 1 we have to assume all variants valid */
9471         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9472         DisplayFatalError(buf, 0, 1);
9473         return;
9474       }
9475
9476       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9477       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9478       if( gameInfo.variant == VariantXiangqi )
9479            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9480       if( gameInfo.variant == VariantShogi )
9481            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9482       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9483            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9484       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9485           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9486            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9487       if( gameInfo.variant == VariantCourier )
9488            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9489       if( gameInfo.variant == VariantSuper )
9490            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9491       if( gameInfo.variant == VariantGreat )
9492            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9493       if( gameInfo.variant == VariantSChess )
9494            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9495       if( gameInfo.variant == VariantGrand )
9496            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9497
9498       if(overruled) {
9499         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9500                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9501            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9502            if(StrStr(cps->variants, b) == NULL) {
9503                // specific sized variant not known, check if general sizing allowed
9504                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9505                    if(StrStr(cps->variants, "boardsize") == NULL) {
9506                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9507                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9508                        DisplayFatalError(buf, 0, 1);
9509                        return;
9510                    }
9511                    /* [HGM] here we really should compare with the maximum supported board size */
9512                }
9513            }
9514       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9515       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9516       SendToProgram(buf, cps);
9517     }
9518     currentlyInitializedVariant = gameInfo.variant;
9519
9520     /* [HGM] send opening position in FRC to first engine */
9521     if(setup) {
9522           SendToProgram("force\n", cps);
9523           SendBoard(cps, 0);
9524           /* engine is now in force mode! Set flag to wake it up after first move. */
9525           setboardSpoiledMachineBlack = 1;
9526     }
9527
9528     if (cps->sendICS) {
9529       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9530       SendToProgram(buf, cps);
9531     }
9532     cps->maybeThinking = FALSE;
9533     cps->offeredDraw = 0;
9534     if (!appData.icsActive) {
9535         SendTimeControl(cps, movesPerSession, timeControl,
9536                         timeIncrement, appData.searchDepth,
9537                         searchTime);
9538     }
9539     if (appData.showThinking
9540         // [HGM] thinking: four options require thinking output to be sent
9541         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9542                                 ) {
9543         SendToProgram("post\n", cps);
9544     }
9545     SendToProgram("hard\n", cps);
9546     if (!appData.ponderNextMove) {
9547         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9548            it without being sure what state we are in first.  "hard"
9549            is not a toggle, so that one is OK.
9550          */
9551         SendToProgram("easy\n", cps);
9552     }
9553     if (cps->usePing) {
9554       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9555       SendToProgram(buf, cps);
9556     }
9557     cps->initDone = TRUE;
9558     ClearEngineOutputPane(cps == &second);
9559 }
9560
9561
9562 void
9563 StartChessProgram(cps)
9564      ChessProgramState *cps;
9565 {
9566     char buf[MSG_SIZ];
9567     int err;
9568
9569     if (appData.noChessProgram) return;
9570     cps->initDone = FALSE;
9571
9572     if (strcmp(cps->host, "localhost") == 0) {
9573         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9574     } else if (*appData.remoteShell == NULLCHAR) {
9575         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9576     } else {
9577         if (*appData.remoteUser == NULLCHAR) {
9578           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9579                     cps->program);
9580         } else {
9581           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9582                     cps->host, appData.remoteUser, cps->program);
9583         }
9584         err = StartChildProcess(buf, "", &cps->pr);
9585     }
9586
9587     if (err != 0) {
9588       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9589         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9590         if(cps != &first) return;
9591         appData.noChessProgram = TRUE;
9592         ThawUI();
9593         SetNCPMode();
9594 //      DisplayFatalError(buf, err, 1);
9595 //      cps->pr = NoProc;
9596 //      cps->isr = NULL;
9597         return;
9598     }
9599
9600     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9601     if (cps->protocolVersion > 1) {
9602       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9603       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9604       cps->comboCnt = 0;  //                and values of combo boxes
9605       SendToProgram(buf, cps);
9606     } else {
9607       SendToProgram("xboard\n", cps);
9608     }
9609 }
9610
9611 void
9612 TwoMachinesEventIfReady P((void))
9613 {
9614   static int curMess = 0;
9615   if (first.lastPing != first.lastPong) {
9616     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9617     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9618     return;
9619   }
9620   if (second.lastPing != second.lastPong) {
9621     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9622     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9623     return;
9624   }
9625   DisplayMessage("", ""); curMess = 0;
9626   ThawUI();
9627   TwoMachinesEvent();
9628 }
9629
9630 char *
9631 MakeName(char *template)
9632 {
9633     time_t clock;
9634     struct tm *tm;
9635     static char buf[MSG_SIZ];
9636     char *p = buf;
9637     int i;
9638
9639     clock = time((time_t *)NULL);
9640     tm = localtime(&clock);
9641
9642     while(*p++ = *template++) if(p[-1] == '%') {
9643         switch(*template++) {
9644           case 0:   *p = 0; return buf;
9645           case 'Y': i = tm->tm_year+1900; break;
9646           case 'y': i = tm->tm_year-100; break;
9647           case 'M': i = tm->tm_mon+1; break;
9648           case 'd': i = tm->tm_mday; break;
9649           case 'h': i = tm->tm_hour; break;
9650           case 'm': i = tm->tm_min; break;
9651           case 's': i = tm->tm_sec; break;
9652           default:  i = 0;
9653         }
9654         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9655     }
9656     return buf;
9657 }
9658
9659 int
9660 CountPlayers(char *p)
9661 {
9662     int n = 0;
9663     while(p = strchr(p, '\n')) p++, n++; // count participants
9664     return n;
9665 }
9666
9667 FILE *
9668 WriteTourneyFile(char *results)
9669 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9670     FILE *f = fopen(appData.tourneyFile, "w");
9671     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9672         // create a file with tournament description
9673         fprintf(f, "-participants {%s}\n", appData.participants);
9674         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9675         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9676         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9677         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9678         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9679         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9680         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9681         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9682         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9683         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9684         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9685         if(searchTime > 0)
9686                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9687         else {
9688                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9689                 fprintf(f, "-tc %s\n", appData.timeControl);
9690                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9691         }
9692         fprintf(f, "-results \"%s\"\n", results);
9693     }
9694     return f;
9695 }
9696
9697 int
9698 CreateTourney(char *name)
9699 {
9700         FILE *f;
9701         if(name[0] == NULLCHAR) {
9702             if(appData.participants[0])
9703                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9704             return 0;
9705         }
9706         f = fopen(name, "r");
9707         if(f) { // file exists
9708             ASSIGN(appData.tourneyFile, name);
9709             ParseArgsFromFile(f); // parse it
9710         } else {
9711             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9712             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9713                 DisplayError(_("Not enough participants"), 0);
9714                 return 0;
9715             }
9716             ASSIGN(appData.tourneyFile, name);
9717             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9718             if((f = WriteTourneyFile("")) == NULL) return 0;
9719         }
9720         fclose(f);
9721         appData.noChessProgram = FALSE;
9722         appData.clockMode = TRUE;
9723         SetGNUMode();
9724         return 1;
9725 }
9726
9727 #define MAXENGINES 1000
9728 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9729
9730 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9731 {
9732     char buf[MSG_SIZ], *p, *q;
9733     int i=1;
9734     while(*names) {
9735         p = names; q = buf;
9736         while(*p && *p != '\n') *q++ = *p++;
9737         *q = 0;
9738         if(engineList[i]) free(engineList[i]);
9739         engineList[i] = strdup(buf);
9740         if(*p == '\n') p++;
9741         TidyProgramName(engineList[i], "localhost", buf);
9742         if(engineMnemonic[i]) free(engineMnemonic[i]);
9743         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9744             strcat(buf, " (");
9745             sscanf(q + 8, "%s", buf + strlen(buf));
9746             strcat(buf, ")");
9747         }
9748         engineMnemonic[i] = strdup(buf);
9749         names = p; i++;
9750       if(i > MAXENGINES - 2) break;
9751     }
9752     engineList[i] = NULL;
9753 }
9754
9755 // following implemented as macro to avoid type limitations
9756 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9757
9758 void SwapEngines(int n)
9759 {   // swap settings for first engine and other engine (so far only some selected options)
9760     int h;
9761     char *p;
9762     if(n == 0) return;
9763     SWAP(directory, p)
9764     SWAP(chessProgram, p)
9765     SWAP(isUCI, h)
9766     SWAP(hasOwnBookUCI, h)
9767     SWAP(protocolVersion, h)
9768     SWAP(reuse, h)
9769     SWAP(scoreIsAbsolute, h)
9770     SWAP(timeOdds, h)
9771     SWAP(logo, p)
9772     SWAP(pgnName, p)
9773     SWAP(pvSAN, h)
9774 }
9775
9776 void
9777 SetPlayer(int player)
9778 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9779     int i;
9780     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9781     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9782     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9783     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9784     if(mnemonic[i]) {
9785         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9786         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9787         ParseArgsFromString(buf);
9788     }
9789     free(engineName);
9790 }
9791
9792 int
9793 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9794 {   // determine players from game number
9795     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9796
9797     if(appData.tourneyType == 0) {
9798         roundsPerCycle = (nPlayers - 1) | 1;
9799         pairingsPerRound = nPlayers / 2;
9800     } else if(appData.tourneyType > 0) {
9801         roundsPerCycle = nPlayers - appData.tourneyType;
9802         pairingsPerRound = appData.tourneyType;
9803     }
9804     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9805     gamesPerCycle = gamesPerRound * roundsPerCycle;
9806     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9807     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9808     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9809     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9810     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9811     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9812
9813     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9814     if(appData.roundSync) *syncInterval = gamesPerRound;
9815
9816     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9817
9818     if(appData.tourneyType == 0) {
9819         if(curPairing == (nPlayers-1)/2 ) {
9820             *whitePlayer = curRound;
9821             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9822         } else {
9823             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9824             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9825             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9826             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9827         }
9828     } else if(appData.tourneyType > 0) {
9829         *whitePlayer = curPairing;
9830         *blackPlayer = curRound + appData.tourneyType;
9831     }
9832
9833     // take care of white/black alternation per round. 
9834     // For cycles and games this is already taken care of by default, derived from matchGame!
9835     return curRound & 1;
9836 }
9837
9838 int
9839 NextTourneyGame(int nr, int *swapColors)
9840 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9841     char *p, *q;
9842     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9843     FILE *tf;
9844     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9845     tf = fopen(appData.tourneyFile, "r");
9846     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9847     ParseArgsFromFile(tf); fclose(tf);
9848     InitTimeControls(); // TC might be altered from tourney file
9849
9850     nPlayers = CountPlayers(appData.participants); // count participants
9851     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9852     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9853
9854     if(syncInterval) {
9855         p = q = appData.results;
9856         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9857         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9858             DisplayMessage(_("Waiting for other game(s)"),"");
9859             waitingForGame = TRUE;
9860             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9861             return 0;
9862         }
9863         waitingForGame = FALSE;
9864     }
9865
9866     if(appData.tourneyType < 0) {
9867         if(nr>=0 && !pairingReceived) {
9868             char buf[1<<16];
9869             if(pairing.pr == NoProc) {
9870                 if(!appData.pairingEngine[0]) {
9871                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9872                     return 0;
9873                 }
9874                 StartChessProgram(&pairing); // starts the pairing engine
9875             }
9876             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9877             SendToProgram(buf, &pairing);
9878             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9879             SendToProgram(buf, &pairing);
9880             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9881         }
9882         pairingReceived = 0;                              // ... so we continue here 
9883         *swapColors = 0;
9884         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9885         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9886         matchGame = 1; roundNr = nr / syncInterval + 1;
9887     }
9888
9889     if(first.pr != NoProc) return 1; // engines already loaded
9890
9891     // redefine engines, engine dir, etc.
9892     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9893     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9894     SwapEngines(1);
9895     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9896     SwapEngines(1);         // and make that valid for second engine by swapping
9897     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9898     InitEngine(&second, 1);
9899     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9900     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9901     return 1;
9902 }
9903
9904 void
9905 NextMatchGame()
9906 {   // performs game initialization that does not invoke engines, and then tries to start the game
9907     int firstWhite, swapColors = 0;
9908     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9909     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9910     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9911     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9912     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9913     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9914     Reset(FALSE, first.pr != NoProc);
9915     appData.noChessProgram = FALSE;
9916     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9917     TwoMachinesEvent();
9918 }
9919
9920 void UserAdjudicationEvent( int result )
9921 {
9922     ChessMove gameResult = GameIsDrawn;
9923
9924     if( result > 0 ) {
9925         gameResult = WhiteWins;
9926     }
9927     else if( result < 0 ) {
9928         gameResult = BlackWins;
9929     }
9930
9931     if( gameMode == TwoMachinesPlay ) {
9932         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9933     }
9934 }
9935
9936
9937 // [HGM] save: calculate checksum of game to make games easily identifiable
9938 int StringCheckSum(char *s)
9939 {
9940         int i = 0;
9941         if(s==NULL) return 0;
9942         while(*s) i = i*259 + *s++;
9943         return i;
9944 }
9945
9946 int GameCheckSum()
9947 {
9948         int i, sum=0;
9949         for(i=backwardMostMove; i<forwardMostMove; i++) {
9950                 sum += pvInfoList[i].depth;
9951                 sum += StringCheckSum(parseList[i]);
9952                 sum += StringCheckSum(commentList[i]);
9953                 sum *= 261;
9954         }
9955         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9956         return sum + StringCheckSum(commentList[i]);
9957 } // end of save patch
9958
9959 void
9960 GameEnds(result, resultDetails, whosays)
9961      ChessMove result;
9962      char *resultDetails;
9963      int whosays;
9964 {
9965     GameMode nextGameMode;
9966     int isIcsGame;
9967     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9968
9969     if(endingGame) return; /* [HGM] crash: forbid recursion */
9970     endingGame = 1;
9971     if(twoBoards) { // [HGM] dual: switch back to one board
9972         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9973         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9974     }
9975     if (appData.debugMode) {
9976       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9977               result, resultDetails ? resultDetails : "(null)", whosays);
9978     }
9979
9980     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9981
9982     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9983         /* If we are playing on ICS, the server decides when the
9984            game is over, but the engine can offer to draw, claim
9985            a draw, or resign.
9986          */
9987 #if ZIPPY
9988         if (appData.zippyPlay && first.initDone) {
9989             if (result == GameIsDrawn) {
9990                 /* In case draw still needs to be claimed */
9991                 SendToICS(ics_prefix);
9992                 SendToICS("draw\n");
9993             } else if (StrCaseStr(resultDetails, "resign")) {
9994                 SendToICS(ics_prefix);
9995                 SendToICS("resign\n");
9996             }
9997         }
9998 #endif
9999         endingGame = 0; /* [HGM] crash */
10000         return;
10001     }
10002
10003     /* If we're loading the game from a file, stop */
10004     if (whosays == GE_FILE) {
10005       (void) StopLoadGameTimer();
10006       gameFileFP = NULL;
10007     }
10008
10009     /* Cancel draw offers */
10010     first.offeredDraw = second.offeredDraw = 0;
10011
10012     /* If this is an ICS game, only ICS can really say it's done;
10013        if not, anyone can. */
10014     isIcsGame = (gameMode == IcsPlayingWhite ||
10015                  gameMode == IcsPlayingBlack ||
10016                  gameMode == IcsObserving    ||
10017                  gameMode == IcsExamining);
10018
10019     if (!isIcsGame || whosays == GE_ICS) {
10020         /* OK -- not an ICS game, or ICS said it was done */
10021         StopClocks();
10022         if (!isIcsGame && !appData.noChessProgram)
10023           SetUserThinkingEnables();
10024
10025         /* [HGM] if a machine claims the game end we verify this claim */
10026         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10027             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10028                 char claimer;
10029                 ChessMove trueResult = (ChessMove) -1;
10030
10031                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10032                                             first.twoMachinesColor[0] :
10033                                             second.twoMachinesColor[0] ;
10034
10035                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10036                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10037                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10038                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10039                 } else
10040                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10041                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10042                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10043                 } else
10044                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10045                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10046                 }
10047
10048                 // now verify win claims, but not in drop games, as we don't understand those yet
10049                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10050                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10051                     (result == WhiteWins && claimer == 'w' ||
10052                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10053                       if (appData.debugMode) {
10054                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10055                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10056                       }
10057                       if(result != trueResult) {
10058                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10059                               result = claimer == 'w' ? BlackWins : WhiteWins;
10060                               resultDetails = buf;
10061                       }
10062                 } else
10063                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10064                     && (forwardMostMove <= backwardMostMove ||
10065                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10066                         (claimer=='b')==(forwardMostMove&1))
10067                                                                                   ) {
10068                       /* [HGM] verify: draws that were not flagged are false claims */
10069                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10070                       result = claimer == 'w' ? BlackWins : WhiteWins;
10071                       resultDetails = buf;
10072                 }
10073                 /* (Claiming a loss is accepted no questions asked!) */
10074             }
10075             /* [HGM] bare: don't allow bare King to win */
10076             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10077                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10078                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10079                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10080                && result != GameIsDrawn)
10081             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10082                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10083                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10084                         if(p >= 0 && p <= (int)WhiteKing) k++;
10085                 }
10086                 if (appData.debugMode) {
10087                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10088                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10089                 }
10090                 if(k <= 1) {
10091                         result = GameIsDrawn;
10092                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10093                         resultDetails = buf;
10094                 }
10095             }
10096         }
10097
10098
10099         if(serverMoves != NULL && !loadFlag) { char c = '=';
10100             if(result==WhiteWins) c = '+';
10101             if(result==BlackWins) c = '-';
10102             if(resultDetails != NULL)
10103                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10104         }
10105         if (resultDetails != NULL) {
10106             gameInfo.result = result;
10107             gameInfo.resultDetails = StrSave(resultDetails);
10108
10109             /* display last move only if game was not loaded from file */
10110             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10111                 DisplayMove(currentMove - 1);
10112
10113             if (forwardMostMove != 0) {
10114                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10115                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10116                                                                 ) {
10117                     if (*appData.saveGameFile != NULLCHAR) {
10118                         SaveGameToFile(appData.saveGameFile, TRUE);
10119                     } else if (appData.autoSaveGames) {
10120                         AutoSaveGame();
10121                     }
10122                     if (*appData.savePositionFile != NULLCHAR) {
10123                         SavePositionToFile(appData.savePositionFile);
10124                     }
10125                 }
10126             }
10127
10128             /* Tell program how game ended in case it is learning */
10129             /* [HGM] Moved this to after saving the PGN, just in case */
10130             /* engine died and we got here through time loss. In that */
10131             /* case we will get a fatal error writing the pipe, which */
10132             /* would otherwise lose us the PGN.                       */
10133             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10134             /* output during GameEnds should never be fatal anymore   */
10135             if (gameMode == MachinePlaysWhite ||
10136                 gameMode == MachinePlaysBlack ||
10137                 gameMode == TwoMachinesPlay ||
10138                 gameMode == IcsPlayingWhite ||
10139                 gameMode == IcsPlayingBlack ||
10140                 gameMode == BeginningOfGame) {
10141                 char buf[MSG_SIZ];
10142                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10143                         resultDetails);
10144                 if (first.pr != NoProc) {
10145                     SendToProgram(buf, &first);
10146                 }
10147                 if (second.pr != NoProc &&
10148                     gameMode == TwoMachinesPlay) {
10149                     SendToProgram(buf, &second);
10150                 }
10151             }
10152         }
10153
10154         if (appData.icsActive) {
10155             if (appData.quietPlay &&
10156                 (gameMode == IcsPlayingWhite ||
10157                  gameMode == IcsPlayingBlack)) {
10158                 SendToICS(ics_prefix);
10159                 SendToICS("set shout 1\n");
10160             }
10161             nextGameMode = IcsIdle;
10162             ics_user_moved = FALSE;
10163             /* clean up premove.  It's ugly when the game has ended and the
10164              * premove highlights are still on the board.
10165              */
10166             if (gotPremove) {
10167               gotPremove = FALSE;
10168               ClearPremoveHighlights();
10169               DrawPosition(FALSE, boards[currentMove]);
10170             }
10171             if (whosays == GE_ICS) {
10172                 switch (result) {
10173                 case WhiteWins:
10174                     if (gameMode == IcsPlayingWhite)
10175                         PlayIcsWinSound();
10176                     else if(gameMode == IcsPlayingBlack)
10177                         PlayIcsLossSound();
10178                     break;
10179                 case BlackWins:
10180                     if (gameMode == IcsPlayingBlack)
10181                         PlayIcsWinSound();
10182                     else if(gameMode == IcsPlayingWhite)
10183                         PlayIcsLossSound();
10184                     break;
10185                 case GameIsDrawn:
10186                     PlayIcsDrawSound();
10187                     break;
10188                 default:
10189                     PlayIcsUnfinishedSound();
10190                 }
10191             }
10192         } else if (gameMode == EditGame ||
10193                    gameMode == PlayFromGameFile ||
10194                    gameMode == AnalyzeMode ||
10195                    gameMode == AnalyzeFile) {
10196             nextGameMode = gameMode;
10197         } else {
10198             nextGameMode = EndOfGame;
10199         }
10200         pausing = FALSE;
10201         ModeHighlight();
10202     } else {
10203         nextGameMode = gameMode;
10204     }
10205
10206     if (appData.noChessProgram) {
10207         gameMode = nextGameMode;
10208         ModeHighlight();
10209         endingGame = 0; /* [HGM] crash */
10210         return;
10211     }
10212
10213     if (first.reuse) {
10214         /* Put first chess program into idle state */
10215         if (first.pr != NoProc &&
10216             (gameMode == MachinePlaysWhite ||
10217              gameMode == MachinePlaysBlack ||
10218              gameMode == TwoMachinesPlay ||
10219              gameMode == IcsPlayingWhite ||
10220              gameMode == IcsPlayingBlack ||
10221              gameMode == BeginningOfGame)) {
10222             SendToProgram("force\n", &first);
10223             if (first.usePing) {
10224               char buf[MSG_SIZ];
10225               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10226               SendToProgram(buf, &first);
10227             }
10228         }
10229     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10230         /* Kill off first chess program */
10231         if (first.isr != NULL)
10232           RemoveInputSource(first.isr);
10233         first.isr = NULL;
10234
10235         if (first.pr != NoProc) {
10236             ExitAnalyzeMode();
10237             DoSleep( appData.delayBeforeQuit );
10238             SendToProgram("quit\n", &first);
10239             DoSleep( appData.delayAfterQuit );
10240             DestroyChildProcess(first.pr, first.useSigterm);
10241         }
10242         first.pr = NoProc;
10243     }
10244     if (second.reuse) {
10245         /* Put second chess program into idle state */
10246         if (second.pr != NoProc &&
10247             gameMode == TwoMachinesPlay) {
10248             SendToProgram("force\n", &second);
10249             if (second.usePing) {
10250               char buf[MSG_SIZ];
10251               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10252               SendToProgram(buf, &second);
10253             }
10254         }
10255     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10256         /* Kill off second chess program */
10257         if (second.isr != NULL)
10258           RemoveInputSource(second.isr);
10259         second.isr = NULL;
10260
10261         if (second.pr != NoProc) {
10262             DoSleep( appData.delayBeforeQuit );
10263             SendToProgram("quit\n", &second);
10264             DoSleep( appData.delayAfterQuit );
10265             DestroyChildProcess(second.pr, second.useSigterm);
10266         }
10267         second.pr = NoProc;
10268     }
10269
10270     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10271         char resChar = '=';
10272         switch (result) {
10273         case WhiteWins:
10274           resChar = '+';
10275           if (first.twoMachinesColor[0] == 'w') {
10276             first.matchWins++;
10277           } else {
10278             second.matchWins++;
10279           }
10280           break;
10281         case BlackWins:
10282           resChar = '-';
10283           if (first.twoMachinesColor[0] == 'b') {
10284             first.matchWins++;
10285           } else {
10286             second.matchWins++;
10287           }
10288           break;
10289         case GameUnfinished:
10290           resChar = ' ';
10291         default:
10292           break;
10293         }
10294
10295         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10296         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10297             ReserveGame(nextGame, resChar); // sets nextGame
10298             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10299             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10300         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10301
10302         if (nextGame <= appData.matchGames && !abortMatch) {
10303             gameMode = nextGameMode;
10304             matchGame = nextGame; // this will be overruled in tourney mode!
10305             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10306             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10307             endingGame = 0; /* [HGM] crash */
10308             return;
10309         } else {
10310             gameMode = nextGameMode;
10311             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10312                      first.tidy, second.tidy,
10313                      first.matchWins, second.matchWins,
10314                      appData.matchGames - (first.matchWins + second.matchWins));
10315             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10316             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10317             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10318                 first.twoMachinesColor = "black\n";
10319                 second.twoMachinesColor = "white\n";
10320             } else {
10321                 first.twoMachinesColor = "white\n";
10322                 second.twoMachinesColor = "black\n";
10323             }
10324         }
10325     }
10326     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10327         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10328       ExitAnalyzeMode();
10329     gameMode = nextGameMode;
10330     ModeHighlight();
10331     endingGame = 0;  /* [HGM] crash */
10332     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10333         if(matchMode == TRUE) { // match through command line: exit with or without popup
10334             if(ranking) {
10335                 ToNrEvent(forwardMostMove);
10336                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10337                 else ExitEvent(0);
10338             } else DisplayFatalError(buf, 0, 0);
10339         } else { // match through menu; just stop, with or without popup
10340             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10341             ModeHighlight();
10342             if(ranking){
10343                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10344             } else DisplayNote(buf);
10345       }
10346       if(ranking) free(ranking);
10347     }
10348 }
10349
10350 /* Assumes program was just initialized (initString sent).
10351    Leaves program in force mode. */
10352 void
10353 FeedMovesToProgram(cps, upto)
10354      ChessProgramState *cps;
10355      int upto;
10356 {
10357     int i;
10358
10359     if (appData.debugMode)
10360       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10361               startedFromSetupPosition ? "position and " : "",
10362               backwardMostMove, upto, cps->which);
10363     if(currentlyInitializedVariant != gameInfo.variant) {
10364       char buf[MSG_SIZ];
10365         // [HGM] variantswitch: make engine aware of new variant
10366         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10367                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10368         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10369         SendToProgram(buf, cps);
10370         currentlyInitializedVariant = gameInfo.variant;
10371     }
10372     SendToProgram("force\n", cps);
10373     if (startedFromSetupPosition) {
10374         SendBoard(cps, backwardMostMove);
10375     if (appData.debugMode) {
10376         fprintf(debugFP, "feedMoves\n");
10377     }
10378     }
10379     for (i = backwardMostMove; i < upto; i++) {
10380         SendMoveToProgram(i, cps);
10381     }
10382 }
10383
10384
10385 int
10386 ResurrectChessProgram()
10387 {
10388      /* The chess program may have exited.
10389         If so, restart it and feed it all the moves made so far. */
10390     static int doInit = 0;
10391
10392     if (appData.noChessProgram) return 1;
10393
10394     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10395         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10396         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10397         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10398     } else {
10399         if (first.pr != NoProc) return 1;
10400         StartChessProgram(&first);
10401     }
10402     InitChessProgram(&first, FALSE);
10403     FeedMovesToProgram(&first, currentMove);
10404
10405     if (!first.sendTime) {
10406         /* can't tell gnuchess what its clock should read,
10407            so we bow to its notion. */
10408         ResetClocks();
10409         timeRemaining[0][currentMove] = whiteTimeRemaining;
10410         timeRemaining[1][currentMove] = blackTimeRemaining;
10411     }
10412
10413     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10414                 appData.icsEngineAnalyze) && first.analysisSupport) {
10415       SendToProgram("analyze\n", &first);
10416       first.analyzing = TRUE;
10417     }
10418     return 1;
10419 }
10420
10421 /*
10422  * Button procedures
10423  */
10424 void
10425 Reset(redraw, init)
10426      int redraw, init;
10427 {
10428     int i;
10429
10430     if (appData.debugMode) {
10431         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10432                 redraw, init, gameMode);
10433     }
10434     CleanupTail(); // [HGM] vari: delete any stored variations
10435     pausing = pauseExamInvalid = FALSE;
10436     startedFromSetupPosition = blackPlaysFirst = FALSE;
10437     firstMove = TRUE;
10438     whiteFlag = blackFlag = FALSE;
10439     userOfferedDraw = FALSE;
10440     hintRequested = bookRequested = FALSE;
10441     first.maybeThinking = FALSE;
10442     second.maybeThinking = FALSE;
10443     first.bookSuspend = FALSE; // [HGM] book
10444     second.bookSuspend = FALSE;
10445     thinkOutput[0] = NULLCHAR;
10446     lastHint[0] = NULLCHAR;
10447     ClearGameInfo(&gameInfo);
10448     gameInfo.variant = StringToVariant(appData.variant);
10449     ics_user_moved = ics_clock_paused = FALSE;
10450     ics_getting_history = H_FALSE;
10451     ics_gamenum = -1;
10452     white_holding[0] = black_holding[0] = NULLCHAR;
10453     ClearProgramStats();
10454     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10455
10456     ResetFrontEnd();
10457     ClearHighlights();
10458     flipView = appData.flipView;
10459     ClearPremoveHighlights();
10460     gotPremove = FALSE;
10461     alarmSounded = FALSE;
10462
10463     GameEnds(EndOfFile, NULL, GE_PLAYER);
10464     if(appData.serverMovesName != NULL) {
10465         /* [HGM] prepare to make moves file for broadcasting */
10466         clock_t t = clock();
10467         if(serverMoves != NULL) fclose(serverMoves);
10468         serverMoves = fopen(appData.serverMovesName, "r");
10469         if(serverMoves != NULL) {
10470             fclose(serverMoves);
10471             /* delay 15 sec before overwriting, so all clients can see end */
10472             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10473         }
10474         serverMoves = fopen(appData.serverMovesName, "w");
10475     }
10476
10477     ExitAnalyzeMode();
10478     gameMode = BeginningOfGame;
10479     ModeHighlight();
10480     if(appData.icsActive) gameInfo.variant = VariantNormal;
10481     currentMove = forwardMostMove = backwardMostMove = 0;
10482     InitPosition(redraw);
10483     for (i = 0; i < MAX_MOVES; i++) {
10484         if (commentList[i] != NULL) {
10485             free(commentList[i]);
10486             commentList[i] = NULL;
10487         }
10488     }
10489     ResetClocks();
10490     timeRemaining[0][0] = whiteTimeRemaining;
10491     timeRemaining[1][0] = blackTimeRemaining;
10492
10493     if (first.pr == NULL) {
10494         StartChessProgram(&first);
10495     }
10496     if (init) {
10497             InitChessProgram(&first, startedFromSetupPosition);
10498     }
10499     DisplayTitle("");
10500     DisplayMessage("", "");
10501     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10502     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10503 }
10504
10505 void
10506 AutoPlayGameLoop()
10507 {
10508     for (;;) {
10509         if (!AutoPlayOneMove())
10510           return;
10511         if (matchMode || appData.timeDelay == 0)
10512           continue;
10513         if (appData.timeDelay < 0)
10514           return;
10515         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10516         break;
10517     }
10518 }
10519
10520
10521 int
10522 AutoPlayOneMove()
10523 {
10524     int fromX, fromY, toX, toY;
10525
10526     if (appData.debugMode) {
10527       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10528     }
10529
10530     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10531       return FALSE;
10532
10533     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10534       pvInfoList[currentMove].depth = programStats.depth;
10535       pvInfoList[currentMove].score = programStats.score;
10536       pvInfoList[currentMove].time  = 0;
10537       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10538     }
10539
10540     if (currentMove >= forwardMostMove) {
10541       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10542       gameMode = EditGame;
10543       ModeHighlight();
10544
10545       /* [AS] Clear current move marker at the end of a game */
10546       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10547
10548       return FALSE;
10549     }
10550
10551     toX = moveList[currentMove][2] - AAA;
10552     toY = moveList[currentMove][3] - ONE;
10553
10554     if (moveList[currentMove][1] == '@') {
10555         if (appData.highlightLastMove) {
10556             SetHighlights(-1, -1, toX, toY);
10557         }
10558     } else {
10559         fromX = moveList[currentMove][0] - AAA;
10560         fromY = moveList[currentMove][1] - ONE;
10561
10562         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10563
10564         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10565
10566         if (appData.highlightLastMove) {
10567             SetHighlights(fromX, fromY, toX, toY);
10568         }
10569     }
10570     DisplayMove(currentMove);
10571     SendMoveToProgram(currentMove++, &first);
10572     DisplayBothClocks();
10573     DrawPosition(FALSE, boards[currentMove]);
10574     // [HGM] PV info: always display, routine tests if empty
10575     DisplayComment(currentMove - 1, commentList[currentMove]);
10576     return TRUE;
10577 }
10578
10579
10580 int
10581 LoadGameOneMove(readAhead)
10582      ChessMove readAhead;
10583 {
10584     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10585     char promoChar = NULLCHAR;
10586     ChessMove moveType;
10587     char move[MSG_SIZ];
10588     char *p, *q;
10589
10590     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10591         gameMode != AnalyzeMode && gameMode != Training) {
10592         gameFileFP = NULL;
10593         return FALSE;
10594     }
10595
10596     yyboardindex = forwardMostMove;
10597     if (readAhead != EndOfFile) {
10598       moveType = readAhead;
10599     } else {
10600       if (gameFileFP == NULL)
10601           return FALSE;
10602       moveType = (ChessMove) Myylex();
10603     }
10604
10605     done = FALSE;
10606     switch (moveType) {
10607       case Comment:
10608         if (appData.debugMode)
10609           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10610         p = yy_text;
10611
10612         /* append the comment but don't display it */
10613         AppendComment(currentMove, p, FALSE);
10614         return TRUE;
10615
10616       case WhiteCapturesEnPassant:
10617       case BlackCapturesEnPassant:
10618       case WhitePromotion:
10619       case BlackPromotion:
10620       case WhiteNonPromotion:
10621       case BlackNonPromotion:
10622       case NormalMove:
10623       case WhiteKingSideCastle:
10624       case WhiteQueenSideCastle:
10625       case BlackKingSideCastle:
10626       case BlackQueenSideCastle:
10627       case WhiteKingSideCastleWild:
10628       case WhiteQueenSideCastleWild:
10629       case BlackKingSideCastleWild:
10630       case BlackQueenSideCastleWild:
10631       /* PUSH Fabien */
10632       case WhiteHSideCastleFR:
10633       case WhiteASideCastleFR:
10634       case BlackHSideCastleFR:
10635       case BlackASideCastleFR:
10636       /* POP Fabien */
10637         if (appData.debugMode)
10638           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10639         fromX = currentMoveString[0] - AAA;
10640         fromY = currentMoveString[1] - ONE;
10641         toX = currentMoveString[2] - AAA;
10642         toY = currentMoveString[3] - ONE;
10643         promoChar = currentMoveString[4];
10644         break;
10645
10646       case WhiteDrop:
10647       case BlackDrop:
10648         if (appData.debugMode)
10649           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10650         fromX = moveType == WhiteDrop ?
10651           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10652         (int) CharToPiece(ToLower(currentMoveString[0]));
10653         fromY = DROP_RANK;
10654         toX = currentMoveString[2] - AAA;
10655         toY = currentMoveString[3] - ONE;
10656         break;
10657
10658       case WhiteWins:
10659       case BlackWins:
10660       case GameIsDrawn:
10661       case GameUnfinished:
10662         if (appData.debugMode)
10663           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10664         p = strchr(yy_text, '{');
10665         if (p == NULL) p = strchr(yy_text, '(');
10666         if (p == NULL) {
10667             p = yy_text;
10668             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10669         } else {
10670             q = strchr(p, *p == '{' ? '}' : ')');
10671             if (q != NULL) *q = NULLCHAR;
10672             p++;
10673         }
10674         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10675         GameEnds(moveType, p, GE_FILE);
10676         done = TRUE;
10677         if (cmailMsgLoaded) {
10678             ClearHighlights();
10679             flipView = WhiteOnMove(currentMove);
10680             if (moveType == GameUnfinished) flipView = !flipView;
10681             if (appData.debugMode)
10682               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10683         }
10684         break;
10685
10686       case EndOfFile:
10687         if (appData.debugMode)
10688           fprintf(debugFP, "Parser hit end of file\n");
10689         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10690           case MT_NONE:
10691           case MT_CHECK:
10692             break;
10693           case MT_CHECKMATE:
10694           case MT_STAINMATE:
10695             if (WhiteOnMove(currentMove)) {
10696                 GameEnds(BlackWins, "Black mates", GE_FILE);
10697             } else {
10698                 GameEnds(WhiteWins, "White mates", GE_FILE);
10699             }
10700             break;
10701           case MT_STALEMATE:
10702             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10703             break;
10704         }
10705         done = TRUE;
10706         break;
10707
10708       case MoveNumberOne:
10709         if (lastLoadGameStart == GNUChessGame) {
10710             /* GNUChessGames have numbers, but they aren't move numbers */
10711             if (appData.debugMode)
10712               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10713                       yy_text, (int) moveType);
10714             return LoadGameOneMove(EndOfFile); /* tail recursion */
10715         }
10716         /* else fall thru */
10717
10718       case XBoardGame:
10719       case GNUChessGame:
10720       case PGNTag:
10721         /* Reached start of next game in file */
10722         if (appData.debugMode)
10723           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10724         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10725           case MT_NONE:
10726           case MT_CHECK:
10727             break;
10728           case MT_CHECKMATE:
10729           case MT_STAINMATE:
10730             if (WhiteOnMove(currentMove)) {
10731                 GameEnds(BlackWins, "Black mates", GE_FILE);
10732             } else {
10733                 GameEnds(WhiteWins, "White mates", GE_FILE);
10734             }
10735             break;
10736           case MT_STALEMATE:
10737             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10738             break;
10739         }
10740         done = TRUE;
10741         break;
10742
10743       case PositionDiagram:     /* should not happen; ignore */
10744       case ElapsedTime:         /* ignore */
10745       case NAG:                 /* ignore */
10746         if (appData.debugMode)
10747           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10748                   yy_text, (int) moveType);
10749         return LoadGameOneMove(EndOfFile); /* tail recursion */
10750
10751       case IllegalMove:
10752         if (appData.testLegality) {
10753             if (appData.debugMode)
10754               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10755             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10756                     (forwardMostMove / 2) + 1,
10757                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10758             DisplayError(move, 0);
10759             done = TRUE;
10760         } else {
10761             if (appData.debugMode)
10762               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10763                       yy_text, currentMoveString);
10764             fromX = currentMoveString[0] - AAA;
10765             fromY = currentMoveString[1] - ONE;
10766             toX = currentMoveString[2] - AAA;
10767             toY = currentMoveString[3] - ONE;
10768             promoChar = currentMoveString[4];
10769         }
10770         break;
10771
10772       case AmbiguousMove:
10773         if (appData.debugMode)
10774           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10775         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10776                 (forwardMostMove / 2) + 1,
10777                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10778         DisplayError(move, 0);
10779         done = TRUE;
10780         break;
10781
10782       default:
10783       case ImpossibleMove:
10784         if (appData.debugMode)
10785           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10786         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10787                 (forwardMostMove / 2) + 1,
10788                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10789         DisplayError(move, 0);
10790         done = TRUE;
10791         break;
10792     }
10793
10794     if (done) {
10795         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10796             DrawPosition(FALSE, boards[currentMove]);
10797             DisplayBothClocks();
10798             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10799               DisplayComment(currentMove - 1, commentList[currentMove]);
10800         }
10801         (void) StopLoadGameTimer();
10802         gameFileFP = NULL;
10803         cmailOldMove = forwardMostMove;
10804         return FALSE;
10805     } else {
10806         /* currentMoveString is set as a side-effect of yylex */
10807
10808         thinkOutput[0] = NULLCHAR;
10809         MakeMove(fromX, fromY, toX, toY, promoChar);
10810         currentMove = forwardMostMove;
10811         return TRUE;
10812     }
10813 }
10814
10815 /* Load the nth game from the given file */
10816 int
10817 LoadGameFromFile(filename, n, title, useList)
10818      char *filename;
10819      int n;
10820      char *title;
10821      /*Boolean*/ int useList;
10822 {
10823     FILE *f;
10824     char buf[MSG_SIZ];
10825
10826     if (strcmp(filename, "-") == 0) {
10827         f = stdin;
10828         title = "stdin";
10829     } else {
10830         f = fopen(filename, "rb");
10831         if (f == NULL) {
10832           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10833             DisplayError(buf, errno);
10834             return FALSE;
10835         }
10836     }
10837     if (fseek(f, 0, 0) == -1) {
10838         /* f is not seekable; probably a pipe */
10839         useList = FALSE;
10840     }
10841     if (useList && n == 0) {
10842         int error = GameListBuild(f);
10843         if (error) {
10844             DisplayError(_("Cannot build game list"), error);
10845         } else if (!ListEmpty(&gameList) &&
10846                    ((ListGame *) gameList.tailPred)->number > 1) {
10847             GameListPopUp(f, title);
10848             return TRUE;
10849         }
10850         GameListDestroy();
10851         n = 1;
10852     }
10853     if (n == 0) n = 1;
10854     return LoadGame(f, n, title, FALSE);
10855 }
10856
10857
10858 void
10859 MakeRegisteredMove()
10860 {
10861     int fromX, fromY, toX, toY;
10862     char promoChar;
10863     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10864         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10865           case CMAIL_MOVE:
10866           case CMAIL_DRAW:
10867             if (appData.debugMode)
10868               fprintf(debugFP, "Restoring %s for game %d\n",
10869                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10870
10871             thinkOutput[0] = NULLCHAR;
10872             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10873             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10874             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10875             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10876             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10877             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10878             MakeMove(fromX, fromY, toX, toY, promoChar);
10879             ShowMove(fromX, fromY, toX, toY);
10880
10881             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10882               case MT_NONE:
10883               case MT_CHECK:
10884                 break;
10885
10886               case MT_CHECKMATE:
10887               case MT_STAINMATE:
10888                 if (WhiteOnMove(currentMove)) {
10889                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10890                 } else {
10891                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10892                 }
10893                 break;
10894
10895               case MT_STALEMATE:
10896                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10897                 break;
10898             }
10899
10900             break;
10901
10902           case CMAIL_RESIGN:
10903             if (WhiteOnMove(currentMove)) {
10904                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10905             } else {
10906                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10907             }
10908             break;
10909
10910           case CMAIL_ACCEPT:
10911             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10912             break;
10913
10914           default:
10915             break;
10916         }
10917     }
10918
10919     return;
10920 }
10921
10922 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10923 int
10924 CmailLoadGame(f, gameNumber, title, useList)
10925      FILE *f;
10926      int gameNumber;
10927      char *title;
10928      int useList;
10929 {
10930     int retVal;
10931
10932     if (gameNumber > nCmailGames) {
10933         DisplayError(_("No more games in this message"), 0);
10934         return FALSE;
10935     }
10936     if (f == lastLoadGameFP) {
10937         int offset = gameNumber - lastLoadGameNumber;
10938         if (offset == 0) {
10939             cmailMsg[0] = NULLCHAR;
10940             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10941                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10942                 nCmailMovesRegistered--;
10943             }
10944             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10945             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10946                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10947             }
10948         } else {
10949             if (! RegisterMove()) return FALSE;
10950         }
10951     }
10952
10953     retVal = LoadGame(f, gameNumber, title, useList);
10954
10955     /* Make move registered during previous look at this game, if any */
10956     MakeRegisteredMove();
10957
10958     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10959         commentList[currentMove]
10960           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10961         DisplayComment(currentMove - 1, commentList[currentMove]);
10962     }
10963
10964     return retVal;
10965 }
10966
10967 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10968 int
10969 ReloadGame(offset)
10970      int offset;
10971 {
10972     int gameNumber = lastLoadGameNumber + offset;
10973     if (lastLoadGameFP == NULL) {
10974         DisplayError(_("No game has been loaded yet"), 0);
10975         return FALSE;
10976     }
10977     if (gameNumber <= 0) {
10978         DisplayError(_("Can't back up any further"), 0);
10979         return FALSE;
10980     }
10981     if (cmailMsgLoaded) {
10982         return CmailLoadGame(lastLoadGameFP, gameNumber,
10983                              lastLoadGameTitle, lastLoadGameUseList);
10984     } else {
10985         return LoadGame(lastLoadGameFP, gameNumber,
10986                         lastLoadGameTitle, lastLoadGameUseList);
10987     }
10988 }
10989
10990
10991
10992 /* Load the nth game from open file f */
10993 int
10994 LoadGame(f, gameNumber, title, useList)
10995      FILE *f;
10996      int gameNumber;
10997      char *title;
10998      int useList;
10999 {
11000     ChessMove cm;
11001     char buf[MSG_SIZ];
11002     int gn = gameNumber;
11003     ListGame *lg = NULL;
11004     int numPGNTags = 0;
11005     int err;
11006     GameMode oldGameMode;
11007     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11008
11009     if (appData.debugMode)
11010         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11011
11012     if (gameMode == Training )
11013         SetTrainingModeOff();
11014
11015     oldGameMode = gameMode;
11016     if (gameMode != BeginningOfGame) {
11017       Reset(FALSE, TRUE);
11018     }
11019
11020     gameFileFP = f;
11021     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11022         fclose(lastLoadGameFP);
11023     }
11024
11025     if (useList) {
11026         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11027
11028         if (lg) {
11029             fseek(f, lg->offset, 0);
11030             GameListHighlight(gameNumber);
11031             gn = 1;
11032         }
11033         else {
11034             DisplayError(_("Game number out of range"), 0);
11035             return FALSE;
11036         }
11037     } else {
11038         GameListDestroy();
11039         if (fseek(f, 0, 0) == -1) {
11040             if (f == lastLoadGameFP ?
11041                 gameNumber == lastLoadGameNumber + 1 :
11042                 gameNumber == 1) {
11043                 gn = 1;
11044             } else {
11045                 DisplayError(_("Can't seek on game file"), 0);
11046                 return FALSE;
11047             }
11048         }
11049     }
11050     lastLoadGameFP = f;
11051     lastLoadGameNumber = gameNumber;
11052     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11053     lastLoadGameUseList = useList;
11054
11055     yynewfile(f);
11056
11057     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11058       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11059                 lg->gameInfo.black);
11060             DisplayTitle(buf);
11061     } else if (*title != NULLCHAR) {
11062         if (gameNumber > 1) {
11063           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11064             DisplayTitle(buf);
11065         } else {
11066             DisplayTitle(title);
11067         }
11068     }
11069
11070     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11071         gameMode = PlayFromGameFile;
11072         ModeHighlight();
11073     }
11074
11075     currentMove = forwardMostMove = backwardMostMove = 0;
11076     CopyBoard(boards[0], initialPosition);
11077     StopClocks();
11078
11079     /*
11080      * Skip the first gn-1 games in the file.
11081      * Also skip over anything that precedes an identifiable
11082      * start of game marker, to avoid being confused by
11083      * garbage at the start of the file.  Currently
11084      * recognized start of game markers are the move number "1",
11085      * the pattern "gnuchess .* game", the pattern
11086      * "^[#;%] [^ ]* game file", and a PGN tag block.
11087      * A game that starts with one of the latter two patterns
11088      * will also have a move number 1, possibly
11089      * following a position diagram.
11090      * 5-4-02: Let's try being more lenient and allowing a game to
11091      * start with an unnumbered move.  Does that break anything?
11092      */
11093     cm = lastLoadGameStart = EndOfFile;
11094     while (gn > 0) {
11095         yyboardindex = forwardMostMove;
11096         cm = (ChessMove) Myylex();
11097         switch (cm) {
11098           case EndOfFile:
11099             if (cmailMsgLoaded) {
11100                 nCmailGames = CMAIL_MAX_GAMES - gn;
11101             } else {
11102                 Reset(TRUE, TRUE);
11103                 DisplayError(_("Game not found in file"), 0);
11104             }
11105             return FALSE;
11106
11107           case GNUChessGame:
11108           case XBoardGame:
11109             gn--;
11110             lastLoadGameStart = cm;
11111             break;
11112
11113           case MoveNumberOne:
11114             switch (lastLoadGameStart) {
11115               case GNUChessGame:
11116               case XBoardGame:
11117               case PGNTag:
11118                 break;
11119               case MoveNumberOne:
11120               case EndOfFile:
11121                 gn--;           /* count this game */
11122                 lastLoadGameStart = cm;
11123                 break;
11124               default:
11125                 /* impossible */
11126                 break;
11127             }
11128             break;
11129
11130           case PGNTag:
11131             switch (lastLoadGameStart) {
11132               case GNUChessGame:
11133               case PGNTag:
11134               case MoveNumberOne:
11135               case EndOfFile:
11136                 gn--;           /* count this game */
11137                 lastLoadGameStart = cm;
11138                 break;
11139               case XBoardGame:
11140                 lastLoadGameStart = cm; /* game counted already */
11141                 break;
11142               default:
11143                 /* impossible */
11144                 break;
11145             }
11146             if (gn > 0) {
11147                 do {
11148                     yyboardindex = forwardMostMove;
11149                     cm = (ChessMove) Myylex();
11150                 } while (cm == PGNTag || cm == Comment);
11151             }
11152             break;
11153
11154           case WhiteWins:
11155           case BlackWins:
11156           case GameIsDrawn:
11157             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11158                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11159                     != CMAIL_OLD_RESULT) {
11160                     nCmailResults ++ ;
11161                     cmailResult[  CMAIL_MAX_GAMES
11162                                 - gn - 1] = CMAIL_OLD_RESULT;
11163                 }
11164             }
11165             break;
11166
11167           case NormalMove:
11168             /* Only a NormalMove can be at the start of a game
11169              * without a position diagram. */
11170             if (lastLoadGameStart == EndOfFile ) {
11171               gn--;
11172               lastLoadGameStart = MoveNumberOne;
11173             }
11174             break;
11175
11176           default:
11177             break;
11178         }
11179     }
11180
11181     if (appData.debugMode)
11182       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11183
11184     if (cm == XBoardGame) {
11185         /* Skip any header junk before position diagram and/or move 1 */
11186         for (;;) {
11187             yyboardindex = forwardMostMove;
11188             cm = (ChessMove) Myylex();
11189
11190             if (cm == EndOfFile ||
11191                 cm == GNUChessGame || cm == XBoardGame) {
11192                 /* Empty game; pretend end-of-file and handle later */
11193                 cm = EndOfFile;
11194                 break;
11195             }
11196
11197             if (cm == MoveNumberOne || cm == PositionDiagram ||
11198                 cm == PGNTag || cm == Comment)
11199               break;
11200         }
11201     } else if (cm == GNUChessGame) {
11202         if (gameInfo.event != NULL) {
11203             free(gameInfo.event);
11204         }
11205         gameInfo.event = StrSave(yy_text);
11206     }
11207
11208     startedFromSetupPosition = FALSE;
11209     while (cm == PGNTag) {
11210         if (appData.debugMode)
11211           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11212         err = ParsePGNTag(yy_text, &gameInfo);
11213         if (!err) numPGNTags++;
11214
11215         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11216         if(gameInfo.variant != oldVariant) {
11217             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11218             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11219             InitPosition(TRUE);
11220             oldVariant = gameInfo.variant;
11221             if (appData.debugMode)
11222               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11223         }
11224
11225
11226         if (gameInfo.fen != NULL) {
11227           Board initial_position;
11228           startedFromSetupPosition = TRUE;
11229           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11230             Reset(TRUE, TRUE);
11231             DisplayError(_("Bad FEN position in file"), 0);
11232             return FALSE;
11233           }
11234           CopyBoard(boards[0], initial_position);
11235           if (blackPlaysFirst) {
11236             currentMove = forwardMostMove = backwardMostMove = 1;
11237             CopyBoard(boards[1], initial_position);
11238             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11239             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11240             timeRemaining[0][1] = whiteTimeRemaining;
11241             timeRemaining[1][1] = blackTimeRemaining;
11242             if (commentList[0] != NULL) {
11243               commentList[1] = commentList[0];
11244               commentList[0] = NULL;
11245             }
11246           } else {
11247             currentMove = forwardMostMove = backwardMostMove = 0;
11248           }
11249           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11250           {   int i;
11251               initialRulePlies = FENrulePlies;
11252               for( i=0; i< nrCastlingRights; i++ )
11253                   initialRights[i] = initial_position[CASTLING][i];
11254           }
11255           yyboardindex = forwardMostMove;
11256           free(gameInfo.fen);
11257           gameInfo.fen = NULL;
11258         }
11259
11260         yyboardindex = forwardMostMove;
11261         cm = (ChessMove) Myylex();
11262
11263         /* Handle comments interspersed among the tags */
11264         while (cm == Comment) {
11265             char *p;
11266             if (appData.debugMode)
11267               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11268             p = yy_text;
11269             AppendComment(currentMove, p, FALSE);
11270             yyboardindex = forwardMostMove;
11271             cm = (ChessMove) Myylex();
11272         }
11273     }
11274
11275     /* don't rely on existence of Event tag since if game was
11276      * pasted from clipboard the Event tag may not exist
11277      */
11278     if (numPGNTags > 0){
11279         char *tags;
11280         if (gameInfo.variant == VariantNormal) {
11281           VariantClass v = StringToVariant(gameInfo.event);
11282           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11283           if(v < VariantShogi) gameInfo.variant = v;
11284         }
11285         if (!matchMode) {
11286           if( appData.autoDisplayTags ) {
11287             tags = PGNTags(&gameInfo);
11288             TagsPopUp(tags, CmailMsg());
11289             free(tags);
11290           }
11291         }
11292     } else {
11293         /* Make something up, but don't display it now */
11294         SetGameInfo();
11295         TagsPopDown();
11296     }
11297
11298     if (cm == PositionDiagram) {
11299         int i, j;
11300         char *p;
11301         Board initial_position;
11302
11303         if (appData.debugMode)
11304           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11305
11306         if (!startedFromSetupPosition) {
11307             p = yy_text;
11308             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11309               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11310                 switch (*p) {
11311                   case '{':
11312                   case '[':
11313                   case '-':
11314                   case ' ':
11315                   case '\t':
11316                   case '\n':
11317                   case '\r':
11318                     break;
11319                   default:
11320                     initial_position[i][j++] = CharToPiece(*p);
11321                     break;
11322                 }
11323             while (*p == ' ' || *p == '\t' ||
11324                    *p == '\n' || *p == '\r') p++;
11325
11326             if (strncmp(p, "black", strlen("black"))==0)
11327               blackPlaysFirst = TRUE;
11328             else
11329               blackPlaysFirst = FALSE;
11330             startedFromSetupPosition = TRUE;
11331
11332             CopyBoard(boards[0], initial_position);
11333             if (blackPlaysFirst) {
11334                 currentMove = forwardMostMove = backwardMostMove = 1;
11335                 CopyBoard(boards[1], initial_position);
11336                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11337                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11338                 timeRemaining[0][1] = whiteTimeRemaining;
11339                 timeRemaining[1][1] = blackTimeRemaining;
11340                 if (commentList[0] != NULL) {
11341                     commentList[1] = commentList[0];
11342                     commentList[0] = NULL;
11343                 }
11344             } else {
11345                 currentMove = forwardMostMove = backwardMostMove = 0;
11346             }
11347         }
11348         yyboardindex = forwardMostMove;
11349         cm = (ChessMove) Myylex();
11350     }
11351
11352     if (first.pr == NoProc) {
11353         StartChessProgram(&first);
11354     }
11355     InitChessProgram(&first, FALSE);
11356     SendToProgram("force\n", &first);
11357     if (startedFromSetupPosition) {
11358         SendBoard(&first, forwardMostMove);
11359     if (appData.debugMode) {
11360         fprintf(debugFP, "Load Game\n");
11361     }
11362         DisplayBothClocks();
11363     }
11364
11365     /* [HGM] server: flag to write setup moves in broadcast file as one */
11366     loadFlag = appData.suppressLoadMoves;
11367
11368     while (cm == Comment) {
11369         char *p;
11370         if (appData.debugMode)
11371           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11372         p = yy_text;
11373         AppendComment(currentMove, p, FALSE);
11374         yyboardindex = forwardMostMove;
11375         cm = (ChessMove) Myylex();
11376     }
11377
11378     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11379         cm == WhiteWins || cm == BlackWins ||
11380         cm == GameIsDrawn || cm == GameUnfinished) {
11381         DisplayMessage("", _("No moves in game"));
11382         if (cmailMsgLoaded) {
11383             if (appData.debugMode)
11384               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11385             ClearHighlights();
11386             flipView = FALSE;
11387         }
11388         DrawPosition(FALSE, boards[currentMove]);
11389         DisplayBothClocks();
11390         gameMode = EditGame;
11391         ModeHighlight();
11392         gameFileFP = NULL;
11393         cmailOldMove = 0;
11394         return TRUE;
11395     }
11396
11397     // [HGM] PV info: routine tests if comment empty
11398     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11399         DisplayComment(currentMove - 1, commentList[currentMove]);
11400     }
11401     if (!matchMode && appData.timeDelay != 0)
11402       DrawPosition(FALSE, boards[currentMove]);
11403
11404     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11405       programStats.ok_to_send = 1;
11406     }
11407
11408     /* if the first token after the PGN tags is a move
11409      * and not move number 1, retrieve it from the parser
11410      */
11411     if (cm != MoveNumberOne)
11412         LoadGameOneMove(cm);
11413
11414     /* load the remaining moves from the file */
11415     while (LoadGameOneMove(EndOfFile)) {
11416       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11417       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11418     }
11419
11420     /* rewind to the start of the game */
11421     currentMove = backwardMostMove;
11422
11423     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11424
11425     if (oldGameMode == AnalyzeFile ||
11426         oldGameMode == AnalyzeMode) {
11427       AnalyzeFileEvent();
11428     }
11429
11430     if (matchMode || appData.timeDelay == 0) {
11431       ToEndEvent();
11432       gameMode = EditGame;
11433       ModeHighlight();
11434     } else if (appData.timeDelay > 0) {
11435       AutoPlayGameLoop();
11436     }
11437
11438     if (appData.debugMode)
11439         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11440
11441     loadFlag = 0; /* [HGM] true game starts */
11442     return TRUE;
11443 }
11444
11445 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11446 int
11447 ReloadPosition(offset)
11448      int offset;
11449 {
11450     int positionNumber = lastLoadPositionNumber + offset;
11451     if (lastLoadPositionFP == NULL) {
11452         DisplayError(_("No position has been loaded yet"), 0);
11453         return FALSE;
11454     }
11455     if (positionNumber <= 0) {
11456         DisplayError(_("Can't back up any further"), 0);
11457         return FALSE;
11458     }
11459     return LoadPosition(lastLoadPositionFP, positionNumber,
11460                         lastLoadPositionTitle);
11461 }
11462
11463 /* Load the nth position from the given file */
11464 int
11465 LoadPositionFromFile(filename, n, title)
11466      char *filename;
11467      int n;
11468      char *title;
11469 {
11470     FILE *f;
11471     char buf[MSG_SIZ];
11472
11473     if (strcmp(filename, "-") == 0) {
11474         return LoadPosition(stdin, n, "stdin");
11475     } else {
11476         f = fopen(filename, "rb");
11477         if (f == NULL) {
11478             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11479             DisplayError(buf, errno);
11480             return FALSE;
11481         } else {
11482             return LoadPosition(f, n, title);
11483         }
11484     }
11485 }
11486
11487 /* Load the nth position from the given open file, and close it */
11488 int
11489 LoadPosition(f, positionNumber, title)
11490      FILE *f;
11491      int positionNumber;
11492      char *title;
11493 {
11494     char *p, line[MSG_SIZ];
11495     Board initial_position;
11496     int i, j, fenMode, pn;
11497
11498     if (gameMode == Training )
11499         SetTrainingModeOff();
11500
11501     if (gameMode != BeginningOfGame) {
11502         Reset(FALSE, TRUE);
11503     }
11504     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11505         fclose(lastLoadPositionFP);
11506     }
11507     if (positionNumber == 0) positionNumber = 1;
11508     lastLoadPositionFP = f;
11509     lastLoadPositionNumber = positionNumber;
11510     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11511     if (first.pr == NoProc) {
11512       StartChessProgram(&first);
11513       InitChessProgram(&first, FALSE);
11514     }
11515     pn = positionNumber;
11516     if (positionNumber < 0) {
11517         /* Negative position number means to seek to that byte offset */
11518         if (fseek(f, -positionNumber, 0) == -1) {
11519             DisplayError(_("Can't seek on position file"), 0);
11520             return FALSE;
11521         };
11522         pn = 1;
11523     } else {
11524         if (fseek(f, 0, 0) == -1) {
11525             if (f == lastLoadPositionFP ?
11526                 positionNumber == lastLoadPositionNumber + 1 :
11527                 positionNumber == 1) {
11528                 pn = 1;
11529             } else {
11530                 DisplayError(_("Can't seek on position file"), 0);
11531                 return FALSE;
11532             }
11533         }
11534     }
11535     /* See if this file is FEN or old-style xboard */
11536     if (fgets(line, MSG_SIZ, f) == NULL) {
11537         DisplayError(_("Position not found in file"), 0);
11538         return FALSE;
11539     }
11540     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11541     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11542
11543     if (pn >= 2) {
11544         if (fenMode || line[0] == '#') pn--;
11545         while (pn > 0) {
11546             /* skip positions before number pn */
11547             if (fgets(line, MSG_SIZ, f) == NULL) {
11548                 Reset(TRUE, TRUE);
11549                 DisplayError(_("Position not found in file"), 0);
11550                 return FALSE;
11551             }
11552             if (fenMode || line[0] == '#') pn--;
11553         }
11554     }
11555
11556     if (fenMode) {
11557         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11558             DisplayError(_("Bad FEN position in file"), 0);
11559             return FALSE;
11560         }
11561     } else {
11562         (void) fgets(line, MSG_SIZ, f);
11563         (void) fgets(line, MSG_SIZ, f);
11564
11565         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11566             (void) fgets(line, MSG_SIZ, f);
11567             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11568                 if (*p == ' ')
11569                   continue;
11570                 initial_position[i][j++] = CharToPiece(*p);
11571             }
11572         }
11573
11574         blackPlaysFirst = FALSE;
11575         if (!feof(f)) {
11576             (void) fgets(line, MSG_SIZ, f);
11577             if (strncmp(line, "black", strlen("black"))==0)
11578               blackPlaysFirst = TRUE;
11579         }
11580     }
11581     startedFromSetupPosition = TRUE;
11582
11583     SendToProgram("force\n", &first);
11584     CopyBoard(boards[0], initial_position);
11585     if (blackPlaysFirst) {
11586         currentMove = forwardMostMove = backwardMostMove = 1;
11587         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11588         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11589         CopyBoard(boards[1], initial_position);
11590         DisplayMessage("", _("Black to play"));
11591     } else {
11592         currentMove = forwardMostMove = backwardMostMove = 0;
11593         DisplayMessage("", _("White to play"));
11594     }
11595     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11596     SendBoard(&first, forwardMostMove);
11597     if (appData.debugMode) {
11598 int i, j;
11599   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11600   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11601         fprintf(debugFP, "Load Position\n");
11602     }
11603
11604     if (positionNumber > 1) {
11605       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11606         DisplayTitle(line);
11607     } else {
11608         DisplayTitle(title);
11609     }
11610     gameMode = EditGame;
11611     ModeHighlight();
11612     ResetClocks();
11613     timeRemaining[0][1] = whiteTimeRemaining;
11614     timeRemaining[1][1] = blackTimeRemaining;
11615     DrawPosition(FALSE, boards[currentMove]);
11616
11617     return TRUE;
11618 }
11619
11620
11621 void
11622 CopyPlayerNameIntoFileName(dest, src)
11623      char **dest, *src;
11624 {
11625     while (*src != NULLCHAR && *src != ',') {
11626         if (*src == ' ') {
11627             *(*dest)++ = '_';
11628             src++;
11629         } else {
11630             *(*dest)++ = *src++;
11631         }
11632     }
11633 }
11634
11635 char *DefaultFileName(ext)
11636      char *ext;
11637 {
11638     static char def[MSG_SIZ];
11639     char *p;
11640
11641     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11642         p = def;
11643         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11644         *p++ = '-';
11645         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11646         *p++ = '.';
11647         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11648     } else {
11649         def[0] = NULLCHAR;
11650     }
11651     return def;
11652 }
11653
11654 /* Save the current game to the given file */
11655 int
11656 SaveGameToFile(filename, append)
11657      char *filename;
11658      int append;
11659 {
11660     FILE *f;
11661     char buf[MSG_SIZ];
11662     int result;
11663
11664     if (strcmp(filename, "-") == 0) {
11665         return SaveGame(stdout, 0, NULL);
11666     } else {
11667         f = fopen(filename, append ? "a" : "w");
11668         if (f == NULL) {
11669             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11670             DisplayError(buf, errno);
11671             return FALSE;
11672         } else {
11673             safeStrCpy(buf, lastMsg, MSG_SIZ);
11674             DisplayMessage(_("Waiting for access to save file"), "");
11675             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11676             DisplayMessage(_("Saving game"), "");
11677             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11678             result = SaveGame(f, 0, NULL);
11679             DisplayMessage(buf, "");
11680             return result;
11681         }
11682     }
11683 }
11684
11685 char *
11686 SavePart(str)
11687      char *str;
11688 {
11689     static char buf[MSG_SIZ];
11690     char *p;
11691
11692     p = strchr(str, ' ');
11693     if (p == NULL) return str;
11694     strncpy(buf, str, p - str);
11695     buf[p - str] = NULLCHAR;
11696     return buf;
11697 }
11698
11699 #define PGN_MAX_LINE 75
11700
11701 #define PGN_SIDE_WHITE  0
11702 #define PGN_SIDE_BLACK  1
11703
11704 /* [AS] */
11705 static int FindFirstMoveOutOfBook( int side )
11706 {
11707     int result = -1;
11708
11709     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11710         int index = backwardMostMove;
11711         int has_book_hit = 0;
11712
11713         if( (index % 2) != side ) {
11714             index++;
11715         }
11716
11717         while( index < forwardMostMove ) {
11718             /* Check to see if engine is in book */
11719             int depth = pvInfoList[index].depth;
11720             int score = pvInfoList[index].score;
11721             int in_book = 0;
11722
11723             if( depth <= 2 ) {
11724                 in_book = 1;
11725             }
11726             else if( score == 0 && depth == 63 ) {
11727                 in_book = 1; /* Zappa */
11728             }
11729             else if( score == 2 && depth == 99 ) {
11730                 in_book = 1; /* Abrok */
11731             }
11732
11733             has_book_hit += in_book;
11734
11735             if( ! in_book ) {
11736                 result = index;
11737
11738                 break;
11739             }
11740
11741             index += 2;
11742         }
11743     }
11744
11745     return result;
11746 }
11747
11748 /* [AS] */
11749 void GetOutOfBookInfo( char * buf )
11750 {
11751     int oob[2];
11752     int i;
11753     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11754
11755     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11756     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11757
11758     *buf = '\0';
11759
11760     if( oob[0] >= 0 || oob[1] >= 0 ) {
11761         for( i=0; i<2; i++ ) {
11762             int idx = oob[i];
11763
11764             if( idx >= 0 ) {
11765                 if( i > 0 && oob[0] >= 0 ) {
11766                     strcat( buf, "   " );
11767                 }
11768
11769                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11770                 sprintf( buf+strlen(buf), "%s%.2f",
11771                     pvInfoList[idx].score >= 0 ? "+" : "",
11772                     pvInfoList[idx].score / 100.0 );
11773             }
11774         }
11775     }
11776 }
11777
11778 /* Save game in PGN style and close the file */
11779 int
11780 SaveGamePGN(f)
11781      FILE *f;
11782 {
11783     int i, offset, linelen, newblock;
11784     time_t tm;
11785 //    char *movetext;
11786     char numtext[32];
11787     int movelen, numlen, blank;
11788     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11789
11790     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11791
11792     tm = time((time_t *) NULL);
11793
11794     PrintPGNTags(f, &gameInfo);
11795
11796     if (backwardMostMove > 0 || startedFromSetupPosition) {
11797         char *fen = PositionToFEN(backwardMostMove, NULL);
11798         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11799         fprintf(f, "\n{--------------\n");
11800         PrintPosition(f, backwardMostMove);
11801         fprintf(f, "--------------}\n");
11802         free(fen);
11803     }
11804     else {
11805         /* [AS] Out of book annotation */
11806         if( appData.saveOutOfBookInfo ) {
11807             char buf[64];
11808
11809             GetOutOfBookInfo( buf );
11810
11811             if( buf[0] != '\0' ) {
11812                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11813             }
11814         }
11815
11816         fprintf(f, "\n");
11817     }
11818
11819     i = backwardMostMove;
11820     linelen = 0;
11821     newblock = TRUE;
11822
11823     while (i < forwardMostMove) {
11824         /* Print comments preceding this move */
11825         if (commentList[i] != NULL) {
11826             if (linelen > 0) fprintf(f, "\n");
11827             fprintf(f, "%s", commentList[i]);
11828             linelen = 0;
11829             newblock = TRUE;
11830         }
11831
11832         /* Format move number */
11833         if ((i % 2) == 0)
11834           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11835         else
11836           if (newblock)
11837             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11838           else
11839             numtext[0] = NULLCHAR;
11840
11841         numlen = strlen(numtext);
11842         newblock = FALSE;
11843
11844         /* Print move number */
11845         blank = linelen > 0 && numlen > 0;
11846         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11847             fprintf(f, "\n");
11848             linelen = 0;
11849             blank = 0;
11850         }
11851         if (blank) {
11852             fprintf(f, " ");
11853             linelen++;
11854         }
11855         fprintf(f, "%s", numtext);
11856         linelen += numlen;
11857
11858         /* Get move */
11859         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11860         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11861
11862         /* Print move */
11863         blank = linelen > 0 && movelen > 0;
11864         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11865             fprintf(f, "\n");
11866             linelen = 0;
11867             blank = 0;
11868         }
11869         if (blank) {
11870             fprintf(f, " ");
11871             linelen++;
11872         }
11873         fprintf(f, "%s", move_buffer);
11874         linelen += movelen;
11875
11876         /* [AS] Add PV info if present */
11877         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11878             /* [HGM] add time */
11879             char buf[MSG_SIZ]; int seconds;
11880
11881             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11882
11883             if( seconds <= 0)
11884               buf[0] = 0;
11885             else
11886               if( seconds < 30 )
11887                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11888               else
11889                 {
11890                   seconds = (seconds + 4)/10; // round to full seconds
11891                   if( seconds < 60 )
11892                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11893                   else
11894                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11895                 }
11896
11897             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11898                       pvInfoList[i].score >= 0 ? "+" : "",
11899                       pvInfoList[i].score / 100.0,
11900                       pvInfoList[i].depth,
11901                       buf );
11902
11903             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11904
11905             /* Print score/depth */
11906             blank = linelen > 0 && movelen > 0;
11907             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11908                 fprintf(f, "\n");
11909                 linelen = 0;
11910                 blank = 0;
11911             }
11912             if (blank) {
11913                 fprintf(f, " ");
11914                 linelen++;
11915             }
11916             fprintf(f, "%s", move_buffer);
11917             linelen += movelen;
11918         }
11919
11920         i++;
11921     }
11922
11923     /* Start a new line */
11924     if (linelen > 0) fprintf(f, "\n");
11925
11926     /* Print comments after last move */
11927     if (commentList[i] != NULL) {
11928         fprintf(f, "%s\n", commentList[i]);
11929     }
11930
11931     /* Print result */
11932     if (gameInfo.resultDetails != NULL &&
11933         gameInfo.resultDetails[0] != NULLCHAR) {
11934         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11935                 PGNResult(gameInfo.result));
11936     } else {
11937         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11938     }
11939
11940     fclose(f);
11941     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11942     return TRUE;
11943 }
11944
11945 /* Save game in old style and close the file */
11946 int
11947 SaveGameOldStyle(f)
11948      FILE *f;
11949 {
11950     int i, offset;
11951     time_t tm;
11952
11953     tm = time((time_t *) NULL);
11954
11955     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11956     PrintOpponents(f);
11957
11958     if (backwardMostMove > 0 || startedFromSetupPosition) {
11959         fprintf(f, "\n[--------------\n");
11960         PrintPosition(f, backwardMostMove);
11961         fprintf(f, "--------------]\n");
11962     } else {
11963         fprintf(f, "\n");
11964     }
11965
11966     i = backwardMostMove;
11967     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11968
11969     while (i < forwardMostMove) {
11970         if (commentList[i] != NULL) {
11971             fprintf(f, "[%s]\n", commentList[i]);
11972         }
11973
11974         if ((i % 2) == 1) {
11975             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11976             i++;
11977         } else {
11978             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11979             i++;
11980             if (commentList[i] != NULL) {
11981                 fprintf(f, "\n");
11982                 continue;
11983             }
11984             if (i >= forwardMostMove) {
11985                 fprintf(f, "\n");
11986                 break;
11987             }
11988             fprintf(f, "%s\n", parseList[i]);
11989             i++;
11990         }
11991     }
11992
11993     if (commentList[i] != NULL) {
11994         fprintf(f, "[%s]\n", commentList[i]);
11995     }
11996
11997     /* This isn't really the old style, but it's close enough */
11998     if (gameInfo.resultDetails != NULL &&
11999         gameInfo.resultDetails[0] != NULLCHAR) {
12000         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12001                 gameInfo.resultDetails);
12002     } else {
12003         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12004     }
12005
12006     fclose(f);
12007     return TRUE;
12008 }
12009
12010 /* Save the current game to open file f and close the file */
12011 int
12012 SaveGame(f, dummy, dummy2)
12013      FILE *f;
12014      int dummy;
12015      char *dummy2;
12016 {
12017     if (gameMode == EditPosition) EditPositionDone(TRUE);
12018     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12019     if (appData.oldSaveStyle)
12020       return SaveGameOldStyle(f);
12021     else
12022       return SaveGamePGN(f);
12023 }
12024
12025 /* Save the current position to the given file */
12026 int
12027 SavePositionToFile(filename)
12028      char *filename;
12029 {
12030     FILE *f;
12031     char buf[MSG_SIZ];
12032
12033     if (strcmp(filename, "-") == 0) {
12034         return SavePosition(stdout, 0, NULL);
12035     } else {
12036         f = fopen(filename, "a");
12037         if (f == NULL) {
12038             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12039             DisplayError(buf, errno);
12040             return FALSE;
12041         } else {
12042             safeStrCpy(buf, lastMsg, MSG_SIZ);
12043             DisplayMessage(_("Waiting for access to save file"), "");
12044             flock(fileno(f), LOCK_EX); // [HGM] lock
12045             DisplayMessage(_("Saving position"), "");
12046             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12047             SavePosition(f, 0, NULL);
12048             DisplayMessage(buf, "");
12049             return TRUE;
12050         }
12051     }
12052 }
12053
12054 /* Save the current position to the given open file and close the file */
12055 int
12056 SavePosition(f, dummy, dummy2)
12057      FILE *f;
12058      int dummy;
12059      char *dummy2;
12060 {
12061     time_t tm;
12062     char *fen;
12063
12064     if (gameMode == EditPosition) EditPositionDone(TRUE);
12065     if (appData.oldSaveStyle) {
12066         tm = time((time_t *) NULL);
12067
12068         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12069         PrintOpponents(f);
12070         fprintf(f, "[--------------\n");
12071         PrintPosition(f, currentMove);
12072         fprintf(f, "--------------]\n");
12073     } else {
12074         fen = PositionToFEN(currentMove, NULL);
12075         fprintf(f, "%s\n", fen);
12076         free(fen);
12077     }
12078     fclose(f);
12079     return TRUE;
12080 }
12081
12082 void
12083 ReloadCmailMsgEvent(unregister)
12084      int unregister;
12085 {
12086 #if !WIN32
12087     static char *inFilename = NULL;
12088     static char *outFilename;
12089     int i;
12090     struct stat inbuf, outbuf;
12091     int status;
12092
12093     /* Any registered moves are unregistered if unregister is set, */
12094     /* i.e. invoked by the signal handler */
12095     if (unregister) {
12096         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12097             cmailMoveRegistered[i] = FALSE;
12098             if (cmailCommentList[i] != NULL) {
12099                 free(cmailCommentList[i]);
12100                 cmailCommentList[i] = NULL;
12101             }
12102         }
12103         nCmailMovesRegistered = 0;
12104     }
12105
12106     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12107         cmailResult[i] = CMAIL_NOT_RESULT;
12108     }
12109     nCmailResults = 0;
12110
12111     if (inFilename == NULL) {
12112         /* Because the filenames are static they only get malloced once  */
12113         /* and they never get freed                                      */
12114         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12115         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12116
12117         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12118         sprintf(outFilename, "%s.out", appData.cmailGameName);
12119     }
12120
12121     status = stat(outFilename, &outbuf);
12122     if (status < 0) {
12123         cmailMailedMove = FALSE;
12124     } else {
12125         status = stat(inFilename, &inbuf);
12126         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12127     }
12128
12129     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12130        counts the games, notes how each one terminated, etc.
12131
12132        It would be nice to remove this kludge and instead gather all
12133        the information while building the game list.  (And to keep it
12134        in the game list nodes instead of having a bunch of fixed-size
12135        parallel arrays.)  Note this will require getting each game's
12136        termination from the PGN tags, as the game list builder does
12137        not process the game moves.  --mann
12138        */
12139     cmailMsgLoaded = TRUE;
12140     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12141
12142     /* Load first game in the file or popup game menu */
12143     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12144
12145 #endif /* !WIN32 */
12146     return;
12147 }
12148
12149 int
12150 RegisterMove()
12151 {
12152     FILE *f;
12153     char string[MSG_SIZ];
12154
12155     if (   cmailMailedMove
12156         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12157         return TRUE;            /* Allow free viewing  */
12158     }
12159
12160     /* Unregister move to ensure that we don't leave RegisterMove        */
12161     /* with the move registered when the conditions for registering no   */
12162     /* longer hold                                                       */
12163     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12164         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12165         nCmailMovesRegistered --;
12166
12167         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12168           {
12169               free(cmailCommentList[lastLoadGameNumber - 1]);
12170               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12171           }
12172     }
12173
12174     if (cmailOldMove == -1) {
12175         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12176         return FALSE;
12177     }
12178
12179     if (currentMove > cmailOldMove + 1) {
12180         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12181         return FALSE;
12182     }
12183
12184     if (currentMove < cmailOldMove) {
12185         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12186         return FALSE;
12187     }
12188
12189     if (forwardMostMove > currentMove) {
12190         /* Silently truncate extra moves */
12191         TruncateGame();
12192     }
12193
12194     if (   (currentMove == cmailOldMove + 1)
12195         || (   (currentMove == cmailOldMove)
12196             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12197                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12198         if (gameInfo.result != GameUnfinished) {
12199             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12200         }
12201
12202         if (commentList[currentMove] != NULL) {
12203             cmailCommentList[lastLoadGameNumber - 1]
12204               = StrSave(commentList[currentMove]);
12205         }
12206         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12207
12208         if (appData.debugMode)
12209           fprintf(debugFP, "Saving %s for game %d\n",
12210                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12211
12212         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12213
12214         f = fopen(string, "w");
12215         if (appData.oldSaveStyle) {
12216             SaveGameOldStyle(f); /* also closes the file */
12217
12218             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12219             f = fopen(string, "w");
12220             SavePosition(f, 0, NULL); /* also closes the file */
12221         } else {
12222             fprintf(f, "{--------------\n");
12223             PrintPosition(f, currentMove);
12224             fprintf(f, "--------------}\n\n");
12225
12226             SaveGame(f, 0, NULL); /* also closes the file*/
12227         }
12228
12229         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12230         nCmailMovesRegistered ++;
12231     } else if (nCmailGames == 1) {
12232         DisplayError(_("You have not made a move yet"), 0);
12233         return FALSE;
12234     }
12235
12236     return TRUE;
12237 }
12238
12239 void
12240 MailMoveEvent()
12241 {
12242 #if !WIN32
12243     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12244     FILE *commandOutput;
12245     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12246     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12247     int nBuffers;
12248     int i;
12249     int archived;
12250     char *arcDir;
12251
12252     if (! cmailMsgLoaded) {
12253         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12254         return;
12255     }
12256
12257     if (nCmailGames == nCmailResults) {
12258         DisplayError(_("No unfinished games"), 0);
12259         return;
12260     }
12261
12262 #if CMAIL_PROHIBIT_REMAIL
12263     if (cmailMailedMove) {
12264       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);
12265         DisplayError(msg, 0);
12266         return;
12267     }
12268 #endif
12269
12270     if (! (cmailMailedMove || RegisterMove())) return;
12271
12272     if (   cmailMailedMove
12273         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12274       snprintf(string, MSG_SIZ, partCommandString,
12275                appData.debugMode ? " -v" : "", appData.cmailGameName);
12276         commandOutput = popen(string, "r");
12277
12278         if (commandOutput == NULL) {
12279             DisplayError(_("Failed to invoke cmail"), 0);
12280         } else {
12281             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12282                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12283             }
12284             if (nBuffers > 1) {
12285                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12286                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12287                 nBytes = MSG_SIZ - 1;
12288             } else {
12289                 (void) memcpy(msg, buffer, nBytes);
12290             }
12291             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12292
12293             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12294                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12295
12296                 archived = TRUE;
12297                 for (i = 0; i < nCmailGames; i ++) {
12298                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12299                         archived = FALSE;
12300                     }
12301                 }
12302                 if (   archived
12303                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12304                         != NULL)) {
12305                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12306                            arcDir,
12307                            appData.cmailGameName,
12308                            gameInfo.date);
12309                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12310                     cmailMsgLoaded = FALSE;
12311                 }
12312             }
12313
12314             DisplayInformation(msg);
12315             pclose(commandOutput);
12316         }
12317     } else {
12318         if ((*cmailMsg) != '\0') {
12319             DisplayInformation(cmailMsg);
12320         }
12321     }
12322
12323     return;
12324 #endif /* !WIN32 */
12325 }
12326
12327 char *
12328 CmailMsg()
12329 {
12330 #if WIN32
12331     return NULL;
12332 #else
12333     int  prependComma = 0;
12334     char number[5];
12335     char string[MSG_SIZ];       /* Space for game-list */
12336     int  i;
12337
12338     if (!cmailMsgLoaded) return "";
12339
12340     if (cmailMailedMove) {
12341       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12342     } else {
12343         /* Create a list of games left */
12344       snprintf(string, MSG_SIZ, "[");
12345         for (i = 0; i < nCmailGames; i ++) {
12346             if (! (   cmailMoveRegistered[i]
12347                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12348                 if (prependComma) {
12349                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12350                 } else {
12351                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12352                     prependComma = 1;
12353                 }
12354
12355                 strcat(string, number);
12356             }
12357         }
12358         strcat(string, "]");
12359
12360         if (nCmailMovesRegistered + nCmailResults == 0) {
12361             switch (nCmailGames) {
12362               case 1:
12363                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12364                 break;
12365
12366               case 2:
12367                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12368                 break;
12369
12370               default:
12371                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12372                          nCmailGames);
12373                 break;
12374             }
12375         } else {
12376             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12377               case 1:
12378                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12379                          string);
12380                 break;
12381
12382               case 0:
12383                 if (nCmailResults == nCmailGames) {
12384                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12385                 } else {
12386                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12387                 }
12388                 break;
12389
12390               default:
12391                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12392                          string);
12393             }
12394         }
12395     }
12396     return cmailMsg;
12397 #endif /* WIN32 */
12398 }
12399
12400 void
12401 ResetGameEvent()
12402 {
12403     if (gameMode == Training)
12404       SetTrainingModeOff();
12405
12406     Reset(TRUE, TRUE);
12407     cmailMsgLoaded = FALSE;
12408     if (appData.icsActive) {
12409       SendToICS(ics_prefix);
12410       SendToICS("refresh\n");
12411     }
12412 }
12413
12414 void
12415 ExitEvent(status)
12416      int status;
12417 {
12418     exiting++;
12419     if (exiting > 2) {
12420       /* Give up on clean exit */
12421       exit(status);
12422     }
12423     if (exiting > 1) {
12424       /* Keep trying for clean exit */
12425       return;
12426     }
12427
12428     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12429
12430     if (telnetISR != NULL) {
12431       RemoveInputSource(telnetISR);
12432     }
12433     if (icsPR != NoProc) {
12434       DestroyChildProcess(icsPR, TRUE);
12435     }
12436
12437     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12438     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12439
12440     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12441     /* make sure this other one finishes before killing it!                  */
12442     if(endingGame) { int count = 0;
12443         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12444         while(endingGame && count++ < 10) DoSleep(1);
12445         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12446     }
12447
12448     /* Kill off chess programs */
12449     if (first.pr != NoProc) {
12450         ExitAnalyzeMode();
12451
12452         DoSleep( appData.delayBeforeQuit );
12453         SendToProgram("quit\n", &first);
12454         DoSleep( appData.delayAfterQuit );
12455         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12456     }
12457     if (second.pr != NoProc) {
12458         DoSleep( appData.delayBeforeQuit );
12459         SendToProgram("quit\n", &second);
12460         DoSleep( appData.delayAfterQuit );
12461         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12462     }
12463     if (first.isr != NULL) {
12464         RemoveInputSource(first.isr);
12465     }
12466     if (second.isr != NULL) {
12467         RemoveInputSource(second.isr);
12468     }
12469
12470     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12471     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12472
12473     ShutDownFrontEnd();
12474     exit(status);
12475 }
12476
12477 void
12478 PauseEvent()
12479 {
12480     if (appData.debugMode)
12481         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12482     if (pausing) {
12483         pausing = FALSE;
12484         ModeHighlight();
12485         if (gameMode == MachinePlaysWhite ||
12486             gameMode == MachinePlaysBlack) {
12487             StartClocks();
12488         } else {
12489             DisplayBothClocks();
12490         }
12491         if (gameMode == PlayFromGameFile) {
12492             if (appData.timeDelay >= 0)
12493                 AutoPlayGameLoop();
12494         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12495             Reset(FALSE, TRUE);
12496             SendToICS(ics_prefix);
12497             SendToICS("refresh\n");
12498         } else if (currentMove < forwardMostMove) {
12499             ForwardInner(forwardMostMove);
12500         }
12501         pauseExamInvalid = FALSE;
12502     } else {
12503         switch (gameMode) {
12504           default:
12505             return;
12506           case IcsExamining:
12507             pauseExamForwardMostMove = forwardMostMove;
12508             pauseExamInvalid = FALSE;
12509             /* fall through */
12510           case IcsObserving:
12511           case IcsPlayingWhite:
12512           case IcsPlayingBlack:
12513             pausing = TRUE;
12514             ModeHighlight();
12515             return;
12516           case PlayFromGameFile:
12517             (void) StopLoadGameTimer();
12518             pausing = TRUE;
12519             ModeHighlight();
12520             break;
12521           case BeginningOfGame:
12522             if (appData.icsActive) return;
12523             /* else fall through */
12524           case MachinePlaysWhite:
12525           case MachinePlaysBlack:
12526           case TwoMachinesPlay:
12527             if (forwardMostMove == 0)
12528               return;           /* don't pause if no one has moved */
12529             if ((gameMode == MachinePlaysWhite &&
12530                  !WhiteOnMove(forwardMostMove)) ||
12531                 (gameMode == MachinePlaysBlack &&
12532                  WhiteOnMove(forwardMostMove))) {
12533                 StopClocks();
12534             }
12535             pausing = TRUE;
12536             ModeHighlight();
12537             break;
12538         }
12539     }
12540 }
12541
12542 void
12543 EditCommentEvent()
12544 {
12545     char title[MSG_SIZ];
12546
12547     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12548       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12549     } else {
12550       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12551                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12552                parseList[currentMove - 1]);
12553     }
12554
12555     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12556 }
12557
12558
12559 void
12560 EditTagsEvent()
12561 {
12562     char *tags = PGNTags(&gameInfo);
12563     bookUp = FALSE;
12564     EditTagsPopUp(tags, NULL);
12565     free(tags);
12566 }
12567
12568 void
12569 AnalyzeModeEvent()
12570 {
12571     if (appData.noChessProgram || gameMode == AnalyzeMode)
12572       return;
12573
12574     if (gameMode != AnalyzeFile) {
12575         if (!appData.icsEngineAnalyze) {
12576                EditGameEvent();
12577                if (gameMode != EditGame) return;
12578         }
12579         ResurrectChessProgram();
12580         SendToProgram("analyze\n", &first);
12581         first.analyzing = TRUE;
12582         /*first.maybeThinking = TRUE;*/
12583         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12584         EngineOutputPopUp();
12585     }
12586     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12587     pausing = FALSE;
12588     ModeHighlight();
12589     SetGameInfo();
12590
12591     StartAnalysisClock();
12592     GetTimeMark(&lastNodeCountTime);
12593     lastNodeCount = 0;
12594 }
12595
12596 void
12597 AnalyzeFileEvent()
12598 {
12599     if (appData.noChessProgram || gameMode == AnalyzeFile)
12600       return;
12601
12602     if (gameMode != AnalyzeMode) {
12603         EditGameEvent();
12604         if (gameMode != EditGame) return;
12605         ResurrectChessProgram();
12606         SendToProgram("analyze\n", &first);
12607         first.analyzing = TRUE;
12608         /*first.maybeThinking = TRUE;*/
12609         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12610         EngineOutputPopUp();
12611     }
12612     gameMode = AnalyzeFile;
12613     pausing = FALSE;
12614     ModeHighlight();
12615     SetGameInfo();
12616
12617     StartAnalysisClock();
12618     GetTimeMark(&lastNodeCountTime);
12619     lastNodeCount = 0;
12620 }
12621
12622 void
12623 MachineWhiteEvent()
12624 {
12625     char buf[MSG_SIZ];
12626     char *bookHit = NULL;
12627
12628     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12629       return;
12630
12631
12632     if (gameMode == PlayFromGameFile ||
12633         gameMode == TwoMachinesPlay  ||
12634         gameMode == Training         ||
12635         gameMode == AnalyzeMode      ||
12636         gameMode == EndOfGame)
12637         EditGameEvent();
12638
12639     if (gameMode == EditPosition)
12640         EditPositionDone(TRUE);
12641
12642     if (!WhiteOnMove(currentMove)) {
12643         DisplayError(_("It is not White's turn"), 0);
12644         return;
12645     }
12646
12647     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12648       ExitAnalyzeMode();
12649
12650     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12651         gameMode == AnalyzeFile)
12652         TruncateGame();
12653
12654     ResurrectChessProgram();    /* in case it isn't running */
12655     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12656         gameMode = MachinePlaysWhite;
12657         ResetClocks();
12658     } else
12659     gameMode = MachinePlaysWhite;
12660     pausing = FALSE;
12661     ModeHighlight();
12662     SetGameInfo();
12663     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12664     DisplayTitle(buf);
12665     if (first.sendName) {
12666       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12667       SendToProgram(buf, &first);
12668     }
12669     if (first.sendTime) {
12670       if (first.useColors) {
12671         SendToProgram("black\n", &first); /*gnu kludge*/
12672       }
12673       SendTimeRemaining(&first, TRUE);
12674     }
12675     if (first.useColors) {
12676       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12677     }
12678     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12679     SetMachineThinkingEnables();
12680     first.maybeThinking = TRUE;
12681     StartClocks();
12682     firstMove = FALSE;
12683
12684     if (appData.autoFlipView && !flipView) {
12685       flipView = !flipView;
12686       DrawPosition(FALSE, NULL);
12687       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12688     }
12689
12690     if(bookHit) { // [HGM] book: simulate book reply
12691         static char bookMove[MSG_SIZ]; // a bit generous?
12692
12693         programStats.nodes = programStats.depth = programStats.time =
12694         programStats.score = programStats.got_only_move = 0;
12695         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12696
12697         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12698         strcat(bookMove, bookHit);
12699         HandleMachineMove(bookMove, &first);
12700     }
12701 }
12702
12703 void
12704 MachineBlackEvent()
12705 {
12706   char buf[MSG_SIZ];
12707   char *bookHit = NULL;
12708
12709     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12710         return;
12711
12712
12713     if (gameMode == PlayFromGameFile ||
12714         gameMode == TwoMachinesPlay  ||
12715         gameMode == Training         ||
12716         gameMode == AnalyzeMode      ||
12717         gameMode == EndOfGame)
12718         EditGameEvent();
12719
12720     if (gameMode == EditPosition)
12721         EditPositionDone(TRUE);
12722
12723     if (WhiteOnMove(currentMove)) {
12724         DisplayError(_("It is not Black's turn"), 0);
12725         return;
12726     }
12727
12728     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12729       ExitAnalyzeMode();
12730
12731     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12732         gameMode == AnalyzeFile)
12733         TruncateGame();
12734
12735     ResurrectChessProgram();    /* in case it isn't running */
12736     gameMode = MachinePlaysBlack;
12737     pausing = FALSE;
12738     ModeHighlight();
12739     SetGameInfo();
12740     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12741     DisplayTitle(buf);
12742     if (first.sendName) {
12743       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12744       SendToProgram(buf, &first);
12745     }
12746     if (first.sendTime) {
12747       if (first.useColors) {
12748         SendToProgram("white\n", &first); /*gnu kludge*/
12749       }
12750       SendTimeRemaining(&first, FALSE);
12751     }
12752     if (first.useColors) {
12753       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12754     }
12755     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12756     SetMachineThinkingEnables();
12757     first.maybeThinking = TRUE;
12758     StartClocks();
12759
12760     if (appData.autoFlipView && flipView) {
12761       flipView = !flipView;
12762       DrawPosition(FALSE, NULL);
12763       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12764     }
12765     if(bookHit) { // [HGM] book: simulate book reply
12766         static char bookMove[MSG_SIZ]; // a bit generous?
12767
12768         programStats.nodes = programStats.depth = programStats.time =
12769         programStats.score = programStats.got_only_move = 0;
12770         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12771
12772         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12773         strcat(bookMove, bookHit);
12774         HandleMachineMove(bookMove, &first);
12775     }
12776 }
12777
12778
12779 void
12780 DisplayTwoMachinesTitle()
12781 {
12782     char buf[MSG_SIZ];
12783     if (appData.matchGames > 0) {
12784         if(appData.tourneyFile[0]) {
12785           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12786                    gameInfo.white, gameInfo.black,
12787                    nextGame+1, appData.matchGames+1,
12788                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12789         } else 
12790         if (first.twoMachinesColor[0] == 'w') {
12791           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12792                    gameInfo.white, gameInfo.black,
12793                    first.matchWins, second.matchWins,
12794                    matchGame - 1 - (first.matchWins + second.matchWins));
12795         } else {
12796           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12797                    gameInfo.white, gameInfo.black,
12798                    second.matchWins, first.matchWins,
12799                    matchGame - 1 - (first.matchWins + second.matchWins));
12800         }
12801     } else {
12802       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12803     }
12804     DisplayTitle(buf);
12805 }
12806
12807 void
12808 SettingsMenuIfReady()
12809 {
12810   if (second.lastPing != second.lastPong) {
12811     DisplayMessage("", _("Waiting for second chess program"));
12812     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12813     return;
12814   }
12815   ThawUI();
12816   DisplayMessage("", "");
12817   SettingsPopUp(&second);
12818 }
12819
12820 int
12821 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12822 {
12823     char buf[MSG_SIZ];
12824     if (cps->pr == NULL) {
12825         StartChessProgram(cps);
12826         if (cps->protocolVersion == 1) {
12827           retry();
12828         } else {
12829           /* kludge: allow timeout for initial "feature" command */
12830           FreezeUI();
12831           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12832           DisplayMessage("", buf);
12833           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12834         }
12835         return 1;
12836     }
12837     return 0;
12838 }
12839
12840 void
12841 TwoMachinesEvent P((void))
12842 {
12843     int i;
12844     char buf[MSG_SIZ];
12845     ChessProgramState *onmove;
12846     char *bookHit = NULL;
12847     static int stalling = 0;
12848     TimeMark now;
12849     long wait;
12850
12851     if (appData.noChessProgram) return;
12852
12853     switch (gameMode) {
12854       case TwoMachinesPlay:
12855         return;
12856       case MachinePlaysWhite:
12857       case MachinePlaysBlack:
12858         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12859             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12860             return;
12861         }
12862         /* fall through */
12863       case BeginningOfGame:
12864       case PlayFromGameFile:
12865       case EndOfGame:
12866         EditGameEvent();
12867         if (gameMode != EditGame) return;
12868         break;
12869       case EditPosition:
12870         EditPositionDone(TRUE);
12871         break;
12872       case AnalyzeMode:
12873       case AnalyzeFile:
12874         ExitAnalyzeMode();
12875         break;
12876       case EditGame:
12877       default:
12878         break;
12879     }
12880
12881 //    forwardMostMove = currentMove;
12882     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12883
12884     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12885
12886     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12887     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12888       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12889       return;
12890     }
12891     if(!stalling) {
12892       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12893       SendToProgram("force\n", &second);
12894       stalling = 1;
12895       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12896       return;
12897     }
12898     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12899     if(appData.matchPause>10000 || appData.matchPause<10)
12900                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12901     wait = SubtractTimeMarks(&now, &pauseStart);
12902     if(wait < appData.matchPause) {
12903         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12904         return;
12905     }
12906     stalling = 0;
12907     DisplayMessage("", "");
12908     if (startedFromSetupPosition) {
12909         SendBoard(&second, backwardMostMove);
12910     if (appData.debugMode) {
12911         fprintf(debugFP, "Two Machines\n");
12912     }
12913     }
12914     for (i = backwardMostMove; i < forwardMostMove; i++) {
12915         SendMoveToProgram(i, &second);
12916     }
12917
12918     gameMode = TwoMachinesPlay;
12919     pausing = FALSE;
12920     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12921     SetGameInfo();
12922     DisplayTwoMachinesTitle();
12923     firstMove = TRUE;
12924     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12925         onmove = &first;
12926     } else {
12927         onmove = &second;
12928     }
12929     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12930     SendToProgram(first.computerString, &first);
12931     if (first.sendName) {
12932       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12933       SendToProgram(buf, &first);
12934     }
12935     SendToProgram(second.computerString, &second);
12936     if (second.sendName) {
12937       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12938       SendToProgram(buf, &second);
12939     }
12940
12941     ResetClocks();
12942     if (!first.sendTime || !second.sendTime) {
12943         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12944         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12945     }
12946     if (onmove->sendTime) {
12947       if (onmove->useColors) {
12948         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12949       }
12950       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12951     }
12952     if (onmove->useColors) {
12953       SendToProgram(onmove->twoMachinesColor, onmove);
12954     }
12955     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12956 //    SendToProgram("go\n", onmove);
12957     onmove->maybeThinking = TRUE;
12958     SetMachineThinkingEnables();
12959
12960     StartClocks();
12961
12962     if(bookHit) { // [HGM] book: simulate book reply
12963         static char bookMove[MSG_SIZ]; // a bit generous?
12964
12965         programStats.nodes = programStats.depth = programStats.time =
12966         programStats.score = programStats.got_only_move = 0;
12967         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12968
12969         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12970         strcat(bookMove, bookHit);
12971         savedMessage = bookMove; // args for deferred call
12972         savedState = onmove;
12973         ScheduleDelayedEvent(DeferredBookMove, 1);
12974     }
12975 }
12976
12977 void
12978 TrainingEvent()
12979 {
12980     if (gameMode == Training) {
12981       SetTrainingModeOff();
12982       gameMode = PlayFromGameFile;
12983       DisplayMessage("", _("Training mode off"));
12984     } else {
12985       gameMode = Training;
12986       animateTraining = appData.animate;
12987
12988       /* make sure we are not already at the end of the game */
12989       if (currentMove < forwardMostMove) {
12990         SetTrainingModeOn();
12991         DisplayMessage("", _("Training mode on"));
12992       } else {
12993         gameMode = PlayFromGameFile;
12994         DisplayError(_("Already at end of game"), 0);
12995       }
12996     }
12997     ModeHighlight();
12998 }
12999
13000 void
13001 IcsClientEvent()
13002 {
13003     if (!appData.icsActive) return;
13004     switch (gameMode) {
13005       case IcsPlayingWhite:
13006       case IcsPlayingBlack:
13007       case IcsObserving:
13008       case IcsIdle:
13009       case BeginningOfGame:
13010       case IcsExamining:
13011         return;
13012
13013       case EditGame:
13014         break;
13015
13016       case EditPosition:
13017         EditPositionDone(TRUE);
13018         break;
13019
13020       case AnalyzeMode:
13021       case AnalyzeFile:
13022         ExitAnalyzeMode();
13023         break;
13024
13025       default:
13026         EditGameEvent();
13027         break;
13028     }
13029
13030     gameMode = IcsIdle;
13031     ModeHighlight();
13032     return;
13033 }
13034
13035
13036 void
13037 EditGameEvent()
13038 {
13039     int i;
13040
13041     switch (gameMode) {
13042       case Training:
13043         SetTrainingModeOff();
13044         break;
13045       case MachinePlaysWhite:
13046       case MachinePlaysBlack:
13047       case BeginningOfGame:
13048         SendToProgram("force\n", &first);
13049         SetUserThinkingEnables();
13050         break;
13051       case PlayFromGameFile:
13052         (void) StopLoadGameTimer();
13053         if (gameFileFP != NULL) {
13054             gameFileFP = NULL;
13055         }
13056         break;
13057       case EditPosition:
13058         EditPositionDone(TRUE);
13059         break;
13060       case AnalyzeMode:
13061       case AnalyzeFile:
13062         ExitAnalyzeMode();
13063         SendToProgram("force\n", &first);
13064         break;
13065       case TwoMachinesPlay:
13066         GameEnds(EndOfFile, NULL, GE_PLAYER);
13067         ResurrectChessProgram();
13068         SetUserThinkingEnables();
13069         break;
13070       case EndOfGame:
13071         ResurrectChessProgram();
13072         break;
13073       case IcsPlayingBlack:
13074       case IcsPlayingWhite:
13075         DisplayError(_("Warning: You are still playing a game"), 0);
13076         break;
13077       case IcsObserving:
13078         DisplayError(_("Warning: You are still observing a game"), 0);
13079         break;
13080       case IcsExamining:
13081         DisplayError(_("Warning: You are still examining a game"), 0);
13082         break;
13083       case IcsIdle:
13084         break;
13085       case EditGame:
13086       default:
13087         return;
13088     }
13089
13090     pausing = FALSE;
13091     StopClocks();
13092     first.offeredDraw = second.offeredDraw = 0;
13093
13094     if (gameMode == PlayFromGameFile) {
13095         whiteTimeRemaining = timeRemaining[0][currentMove];
13096         blackTimeRemaining = timeRemaining[1][currentMove];
13097         DisplayTitle("");
13098     }
13099
13100     if (gameMode == MachinePlaysWhite ||
13101         gameMode == MachinePlaysBlack ||
13102         gameMode == TwoMachinesPlay ||
13103         gameMode == EndOfGame) {
13104         i = forwardMostMove;
13105         while (i > currentMove) {
13106             SendToProgram("undo\n", &first);
13107             i--;
13108         }
13109         whiteTimeRemaining = timeRemaining[0][currentMove];
13110         blackTimeRemaining = timeRemaining[1][currentMove];
13111         DisplayBothClocks();
13112         if (whiteFlag || blackFlag) {
13113             whiteFlag = blackFlag = 0;
13114         }
13115         DisplayTitle("");
13116     }
13117
13118     gameMode = EditGame;
13119     ModeHighlight();
13120     SetGameInfo();
13121 }
13122
13123
13124 void
13125 EditPositionEvent()
13126 {
13127     if (gameMode == EditPosition) {
13128         EditGameEvent();
13129         return;
13130     }
13131
13132     EditGameEvent();
13133     if (gameMode != EditGame) return;
13134
13135     gameMode = EditPosition;
13136     ModeHighlight();
13137     SetGameInfo();
13138     if (currentMove > 0)
13139       CopyBoard(boards[0], boards[currentMove]);
13140
13141     blackPlaysFirst = !WhiteOnMove(currentMove);
13142     ResetClocks();
13143     currentMove = forwardMostMove = backwardMostMove = 0;
13144     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13145     DisplayMove(-1);
13146 }
13147
13148 void
13149 ExitAnalyzeMode()
13150 {
13151     /* [DM] icsEngineAnalyze - possible call from other functions */
13152     if (appData.icsEngineAnalyze) {
13153         appData.icsEngineAnalyze = FALSE;
13154
13155         DisplayMessage("",_("Close ICS engine analyze..."));
13156     }
13157     if (first.analysisSupport && first.analyzing) {
13158       SendToProgram("exit\n", &first);
13159       first.analyzing = FALSE;
13160     }
13161     thinkOutput[0] = NULLCHAR;
13162 }
13163
13164 void
13165 EditPositionDone(Boolean fakeRights)
13166 {
13167     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13168
13169     startedFromSetupPosition = TRUE;
13170     InitChessProgram(&first, FALSE);
13171     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13172       boards[0][EP_STATUS] = EP_NONE;
13173       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13174     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13175         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13176         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13177       } else boards[0][CASTLING][2] = NoRights;
13178     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13179         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13180         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13181       } else boards[0][CASTLING][5] = NoRights;
13182     }
13183     SendToProgram("force\n", &first);
13184     if (blackPlaysFirst) {
13185         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13186         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13187         currentMove = forwardMostMove = backwardMostMove = 1;
13188         CopyBoard(boards[1], boards[0]);
13189     } else {
13190         currentMove = forwardMostMove = backwardMostMove = 0;
13191     }
13192     SendBoard(&first, forwardMostMove);
13193     if (appData.debugMode) {
13194         fprintf(debugFP, "EditPosDone\n");
13195     }
13196     DisplayTitle("");
13197     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13198     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13199     gameMode = EditGame;
13200     ModeHighlight();
13201     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13202     ClearHighlights(); /* [AS] */
13203 }
13204
13205 /* Pause for `ms' milliseconds */
13206 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13207 void
13208 TimeDelay(ms)
13209      long ms;
13210 {
13211     TimeMark m1, m2;
13212
13213     GetTimeMark(&m1);
13214     do {
13215         GetTimeMark(&m2);
13216     } while (SubtractTimeMarks(&m2, &m1) < ms);
13217 }
13218
13219 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13220 void
13221 SendMultiLineToICS(buf)
13222      char *buf;
13223 {
13224     char temp[MSG_SIZ+1], *p;
13225     int len;
13226
13227     len = strlen(buf);
13228     if (len > MSG_SIZ)
13229       len = MSG_SIZ;
13230
13231     strncpy(temp, buf, len);
13232     temp[len] = 0;
13233
13234     p = temp;
13235     while (*p) {
13236         if (*p == '\n' || *p == '\r')
13237           *p = ' ';
13238         ++p;
13239     }
13240
13241     strcat(temp, "\n");
13242     SendToICS(temp);
13243     SendToPlayer(temp, strlen(temp));
13244 }
13245
13246 void
13247 SetWhiteToPlayEvent()
13248 {
13249     if (gameMode == EditPosition) {
13250         blackPlaysFirst = FALSE;
13251         DisplayBothClocks();    /* works because currentMove is 0 */
13252     } else if (gameMode == IcsExamining) {
13253         SendToICS(ics_prefix);
13254         SendToICS("tomove white\n");
13255     }
13256 }
13257
13258 void
13259 SetBlackToPlayEvent()
13260 {
13261     if (gameMode == EditPosition) {
13262         blackPlaysFirst = TRUE;
13263         currentMove = 1;        /* kludge */
13264         DisplayBothClocks();
13265         currentMove = 0;
13266     } else if (gameMode == IcsExamining) {
13267         SendToICS(ics_prefix);
13268         SendToICS("tomove black\n");
13269     }
13270 }
13271
13272 void
13273 EditPositionMenuEvent(selection, x, y)
13274      ChessSquare selection;
13275      int x, y;
13276 {
13277     char buf[MSG_SIZ];
13278     ChessSquare piece = boards[0][y][x];
13279
13280     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13281
13282     switch (selection) {
13283       case ClearBoard:
13284         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13285             SendToICS(ics_prefix);
13286             SendToICS("bsetup clear\n");
13287         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13288             SendToICS(ics_prefix);
13289             SendToICS("clearboard\n");
13290         } else {
13291             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13292                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13293                 for (y = 0; y < BOARD_HEIGHT; y++) {
13294                     if (gameMode == IcsExamining) {
13295                         if (boards[currentMove][y][x] != EmptySquare) {
13296                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13297                                     AAA + x, ONE + y);
13298                             SendToICS(buf);
13299                         }
13300                     } else {
13301                         boards[0][y][x] = p;
13302                     }
13303                 }
13304             }
13305         }
13306         if (gameMode == EditPosition) {
13307             DrawPosition(FALSE, boards[0]);
13308         }
13309         break;
13310
13311       case WhitePlay:
13312         SetWhiteToPlayEvent();
13313         break;
13314
13315       case BlackPlay:
13316         SetBlackToPlayEvent();
13317         break;
13318
13319       case EmptySquare:
13320         if (gameMode == IcsExamining) {
13321             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13322             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13323             SendToICS(buf);
13324         } else {
13325             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13326                 if(x == BOARD_LEFT-2) {
13327                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13328                     boards[0][y][1] = 0;
13329                 } else
13330                 if(x == BOARD_RGHT+1) {
13331                     if(y >= gameInfo.holdingsSize) break;
13332                     boards[0][y][BOARD_WIDTH-2] = 0;
13333                 } else break;
13334             }
13335             boards[0][y][x] = EmptySquare;
13336             DrawPosition(FALSE, boards[0]);
13337         }
13338         break;
13339
13340       case PromotePiece:
13341         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13342            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13343             selection = (ChessSquare) (PROMOTED piece);
13344         } else if(piece == EmptySquare) selection = WhiteSilver;
13345         else selection = (ChessSquare)((int)piece - 1);
13346         goto defaultlabel;
13347
13348       case DemotePiece:
13349         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13350            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13351             selection = (ChessSquare) (DEMOTED piece);
13352         } else if(piece == EmptySquare) selection = BlackSilver;
13353         else selection = (ChessSquare)((int)piece + 1);
13354         goto defaultlabel;
13355
13356       case WhiteQueen:
13357       case BlackQueen:
13358         if(gameInfo.variant == VariantShatranj ||
13359            gameInfo.variant == VariantXiangqi  ||
13360            gameInfo.variant == VariantCourier  ||
13361            gameInfo.variant == VariantMakruk     )
13362             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13363         goto defaultlabel;
13364
13365       case WhiteKing:
13366       case BlackKing:
13367         if(gameInfo.variant == VariantXiangqi)
13368             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13369         if(gameInfo.variant == VariantKnightmate)
13370             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13371       default:
13372         defaultlabel:
13373         if (gameMode == IcsExamining) {
13374             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13375             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13376                      PieceToChar(selection), AAA + x, ONE + y);
13377             SendToICS(buf);
13378         } else {
13379             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13380                 int n;
13381                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13382                     n = PieceToNumber(selection - BlackPawn);
13383                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13384                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13385                     boards[0][BOARD_HEIGHT-1-n][1]++;
13386                 } else
13387                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13388                     n = PieceToNumber(selection);
13389                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13390                     boards[0][n][BOARD_WIDTH-1] = selection;
13391                     boards[0][n][BOARD_WIDTH-2]++;
13392                 }
13393             } else
13394             boards[0][y][x] = selection;
13395             DrawPosition(TRUE, boards[0]);
13396         }
13397         break;
13398     }
13399 }
13400
13401
13402 void
13403 DropMenuEvent(selection, x, y)
13404      ChessSquare selection;
13405      int x, y;
13406 {
13407     ChessMove moveType;
13408
13409     switch (gameMode) {
13410       case IcsPlayingWhite:
13411       case MachinePlaysBlack:
13412         if (!WhiteOnMove(currentMove)) {
13413             DisplayMoveError(_("It is Black's turn"));
13414             return;
13415         }
13416         moveType = WhiteDrop;
13417         break;
13418       case IcsPlayingBlack:
13419       case MachinePlaysWhite:
13420         if (WhiteOnMove(currentMove)) {
13421             DisplayMoveError(_("It is White's turn"));
13422             return;
13423         }
13424         moveType = BlackDrop;
13425         break;
13426       case EditGame:
13427         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13428         break;
13429       default:
13430         return;
13431     }
13432
13433     if (moveType == BlackDrop && selection < BlackPawn) {
13434       selection = (ChessSquare) ((int) selection
13435                                  + (int) BlackPawn - (int) WhitePawn);
13436     }
13437     if (boards[currentMove][y][x] != EmptySquare) {
13438         DisplayMoveError(_("That square is occupied"));
13439         return;
13440     }
13441
13442     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13443 }
13444
13445 void
13446 AcceptEvent()
13447 {
13448     /* Accept a pending offer of any kind from opponent */
13449
13450     if (appData.icsActive) {
13451         SendToICS(ics_prefix);
13452         SendToICS("accept\n");
13453     } else if (cmailMsgLoaded) {
13454         if (currentMove == cmailOldMove &&
13455             commentList[cmailOldMove] != NULL &&
13456             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13457                    "Black offers a draw" : "White offers a draw")) {
13458             TruncateGame();
13459             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13460             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13461         } else {
13462             DisplayError(_("There is no pending offer on this move"), 0);
13463             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13464         }
13465     } else {
13466         /* Not used for offers from chess program */
13467     }
13468 }
13469
13470 void
13471 DeclineEvent()
13472 {
13473     /* Decline a pending offer of any kind from opponent */
13474
13475     if (appData.icsActive) {
13476         SendToICS(ics_prefix);
13477         SendToICS("decline\n");
13478     } else if (cmailMsgLoaded) {
13479         if (currentMove == cmailOldMove &&
13480             commentList[cmailOldMove] != NULL &&
13481             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13482                    "Black offers a draw" : "White offers a draw")) {
13483 #ifdef NOTDEF
13484             AppendComment(cmailOldMove, "Draw declined", TRUE);
13485             DisplayComment(cmailOldMove - 1, "Draw declined");
13486 #endif /*NOTDEF*/
13487         } else {
13488             DisplayError(_("There is no pending offer on this move"), 0);
13489         }
13490     } else {
13491         /* Not used for offers from chess program */
13492     }
13493 }
13494
13495 void
13496 RematchEvent()
13497 {
13498     /* Issue ICS rematch command */
13499     if (appData.icsActive) {
13500         SendToICS(ics_prefix);
13501         SendToICS("rematch\n");
13502     }
13503 }
13504
13505 void
13506 CallFlagEvent()
13507 {
13508     /* Call your opponent's flag (claim a win on time) */
13509     if (appData.icsActive) {
13510         SendToICS(ics_prefix);
13511         SendToICS("flag\n");
13512     } else {
13513         switch (gameMode) {
13514           default:
13515             return;
13516           case MachinePlaysWhite:
13517             if (whiteFlag) {
13518                 if (blackFlag)
13519                   GameEnds(GameIsDrawn, "Both players ran out of time",
13520                            GE_PLAYER);
13521                 else
13522                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13523             } else {
13524                 DisplayError(_("Your opponent is not out of time"), 0);
13525             }
13526             break;
13527           case MachinePlaysBlack:
13528             if (blackFlag) {
13529                 if (whiteFlag)
13530                   GameEnds(GameIsDrawn, "Both players ran out of time",
13531                            GE_PLAYER);
13532                 else
13533                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13534             } else {
13535                 DisplayError(_("Your opponent is not out of time"), 0);
13536             }
13537             break;
13538         }
13539     }
13540 }
13541
13542 void
13543 ClockClick(int which)
13544 {       // [HGM] code moved to back-end from winboard.c
13545         if(which) { // black clock
13546           if (gameMode == EditPosition || gameMode == IcsExamining) {
13547             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13548             SetBlackToPlayEvent();
13549           } else if (gameMode == EditGame || shiftKey) {
13550             AdjustClock(which, -1);
13551           } else if (gameMode == IcsPlayingWhite ||
13552                      gameMode == MachinePlaysBlack) {
13553             CallFlagEvent();
13554           }
13555         } else { // white clock
13556           if (gameMode == EditPosition || gameMode == IcsExamining) {
13557             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13558             SetWhiteToPlayEvent();
13559           } else if (gameMode == EditGame || shiftKey) {
13560             AdjustClock(which, -1);
13561           } else if (gameMode == IcsPlayingBlack ||
13562                    gameMode == MachinePlaysWhite) {
13563             CallFlagEvent();
13564           }
13565         }
13566 }
13567
13568 void
13569 DrawEvent()
13570 {
13571     /* Offer draw or accept pending draw offer from opponent */
13572
13573     if (appData.icsActive) {
13574         /* Note: tournament rules require draw offers to be
13575            made after you make your move but before you punch
13576            your clock.  Currently ICS doesn't let you do that;
13577            instead, you immediately punch your clock after making
13578            a move, but you can offer a draw at any time. */
13579
13580         SendToICS(ics_prefix);
13581         SendToICS("draw\n");
13582         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13583     } else if (cmailMsgLoaded) {
13584         if (currentMove == cmailOldMove &&
13585             commentList[cmailOldMove] != NULL &&
13586             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13587                    "Black offers a draw" : "White offers a draw")) {
13588             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13589             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13590         } else if (currentMove == cmailOldMove + 1) {
13591             char *offer = WhiteOnMove(cmailOldMove) ?
13592               "White offers a draw" : "Black offers a draw";
13593             AppendComment(currentMove, offer, TRUE);
13594             DisplayComment(currentMove - 1, offer);
13595             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13596         } else {
13597             DisplayError(_("You must make your move before offering a draw"), 0);
13598             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13599         }
13600     } else if (first.offeredDraw) {
13601         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13602     } else {
13603         if (first.sendDrawOffers) {
13604             SendToProgram("draw\n", &first);
13605             userOfferedDraw = TRUE;
13606         }
13607     }
13608 }
13609
13610 void
13611 AdjournEvent()
13612 {
13613     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13614
13615     if (appData.icsActive) {
13616         SendToICS(ics_prefix);
13617         SendToICS("adjourn\n");
13618     } else {
13619         /* Currently GNU Chess doesn't offer or accept Adjourns */
13620     }
13621 }
13622
13623
13624 void
13625 AbortEvent()
13626 {
13627     /* Offer Abort or accept pending Abort offer from opponent */
13628
13629     if (appData.icsActive) {
13630         SendToICS(ics_prefix);
13631         SendToICS("abort\n");
13632     } else {
13633         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13634     }
13635 }
13636
13637 void
13638 ResignEvent()
13639 {
13640     /* Resign.  You can do this even if it's not your turn. */
13641
13642     if (appData.icsActive) {
13643         SendToICS(ics_prefix);
13644         SendToICS("resign\n");
13645     } else {
13646         switch (gameMode) {
13647           case MachinePlaysWhite:
13648             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13649             break;
13650           case MachinePlaysBlack:
13651             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13652             break;
13653           case EditGame:
13654             if (cmailMsgLoaded) {
13655                 TruncateGame();
13656                 if (WhiteOnMove(cmailOldMove)) {
13657                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13658                 } else {
13659                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13660                 }
13661                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13662             }
13663             break;
13664           default:
13665             break;
13666         }
13667     }
13668 }
13669
13670
13671 void
13672 StopObservingEvent()
13673 {
13674     /* Stop observing current games */
13675     SendToICS(ics_prefix);
13676     SendToICS("unobserve\n");
13677 }
13678
13679 void
13680 StopExaminingEvent()
13681 {
13682     /* Stop observing current game */
13683     SendToICS(ics_prefix);
13684     SendToICS("unexamine\n");
13685 }
13686
13687 void
13688 ForwardInner(target)
13689      int target;
13690 {
13691     int limit;
13692
13693     if (appData.debugMode)
13694         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13695                 target, currentMove, forwardMostMove);
13696
13697     if (gameMode == EditPosition)
13698       return;
13699
13700     if (gameMode == PlayFromGameFile && !pausing)
13701       PauseEvent();
13702
13703     if (gameMode == IcsExamining && pausing)
13704       limit = pauseExamForwardMostMove;
13705     else
13706       limit = forwardMostMove;
13707
13708     if (target > limit) target = limit;
13709
13710     if (target > 0 && moveList[target - 1][0]) {
13711         int fromX, fromY, toX, toY;
13712         toX = moveList[target - 1][2] - AAA;
13713         toY = moveList[target - 1][3] - ONE;
13714         if (moveList[target - 1][1] == '@') {
13715             if (appData.highlightLastMove) {
13716                 SetHighlights(-1, -1, toX, toY);
13717             }
13718         } else {
13719             fromX = moveList[target - 1][0] - AAA;
13720             fromY = moveList[target - 1][1] - ONE;
13721             if (target == currentMove + 1) {
13722                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13723             }
13724             if (appData.highlightLastMove) {
13725                 SetHighlights(fromX, fromY, toX, toY);
13726             }
13727         }
13728     }
13729     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13730         gameMode == Training || gameMode == PlayFromGameFile ||
13731         gameMode == AnalyzeFile) {
13732         while (currentMove < target) {
13733             SendMoveToProgram(currentMove++, &first);
13734         }
13735     } else {
13736         currentMove = target;
13737     }
13738
13739     if (gameMode == EditGame || gameMode == EndOfGame) {
13740         whiteTimeRemaining = timeRemaining[0][currentMove];
13741         blackTimeRemaining = timeRemaining[1][currentMove];
13742     }
13743     DisplayBothClocks();
13744     DisplayMove(currentMove - 1);
13745     DrawPosition(FALSE, boards[currentMove]);
13746     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13747     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13748         DisplayComment(currentMove - 1, commentList[currentMove]);
13749     }
13750     DisplayBook(currentMove);
13751 }
13752
13753
13754 void
13755 ForwardEvent()
13756 {
13757     if (gameMode == IcsExamining && !pausing) {
13758         SendToICS(ics_prefix);
13759         SendToICS("forward\n");
13760     } else {
13761         ForwardInner(currentMove + 1);
13762     }
13763 }
13764
13765 void
13766 ToEndEvent()
13767 {
13768     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13769         /* to optimze, we temporarily turn off analysis mode while we feed
13770          * the remaining moves to the engine. Otherwise we get analysis output
13771          * after each move.
13772          */
13773         if (first.analysisSupport) {
13774           SendToProgram("exit\nforce\n", &first);
13775           first.analyzing = FALSE;
13776         }
13777     }
13778
13779     if (gameMode == IcsExamining && !pausing) {
13780         SendToICS(ics_prefix);
13781         SendToICS("forward 999999\n");
13782     } else {
13783         ForwardInner(forwardMostMove);
13784     }
13785
13786     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13787         /* we have fed all the moves, so reactivate analysis mode */
13788         SendToProgram("analyze\n", &first);
13789         first.analyzing = TRUE;
13790         /*first.maybeThinking = TRUE;*/
13791         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13792     }
13793 }
13794
13795 void
13796 BackwardInner(target)
13797      int target;
13798 {
13799     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13800
13801     if (appData.debugMode)
13802         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13803                 target, currentMove, forwardMostMove);
13804
13805     if (gameMode == EditPosition) return;
13806     if (currentMove <= backwardMostMove) {
13807         ClearHighlights();
13808         DrawPosition(full_redraw, boards[currentMove]);
13809         return;
13810     }
13811     if (gameMode == PlayFromGameFile && !pausing)
13812       PauseEvent();
13813
13814     if (moveList[target][0]) {
13815         int fromX, fromY, toX, toY;
13816         toX = moveList[target][2] - AAA;
13817         toY = moveList[target][3] - ONE;
13818         if (moveList[target][1] == '@') {
13819             if (appData.highlightLastMove) {
13820                 SetHighlights(-1, -1, toX, toY);
13821             }
13822         } else {
13823             fromX = moveList[target][0] - AAA;
13824             fromY = moveList[target][1] - ONE;
13825             if (target == currentMove - 1) {
13826                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13827             }
13828             if (appData.highlightLastMove) {
13829                 SetHighlights(fromX, fromY, toX, toY);
13830             }
13831         }
13832     }
13833     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13834         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13835         while (currentMove > target) {
13836             SendToProgram("undo\n", &first);
13837             currentMove--;
13838         }
13839     } else {
13840         currentMove = target;
13841     }
13842
13843     if (gameMode == EditGame || gameMode == EndOfGame) {
13844         whiteTimeRemaining = timeRemaining[0][currentMove];
13845         blackTimeRemaining = timeRemaining[1][currentMove];
13846     }
13847     DisplayBothClocks();
13848     DisplayMove(currentMove - 1);
13849     DrawPosition(full_redraw, boards[currentMove]);
13850     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13851     // [HGM] PV info: routine tests if comment empty
13852     DisplayComment(currentMove - 1, commentList[currentMove]);
13853     DisplayBook(currentMove);
13854 }
13855
13856 void
13857 BackwardEvent()
13858 {
13859     if (gameMode == IcsExamining && !pausing) {
13860         SendToICS(ics_prefix);
13861         SendToICS("backward\n");
13862     } else {
13863         BackwardInner(currentMove - 1);
13864     }
13865 }
13866
13867 void
13868 ToStartEvent()
13869 {
13870     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13871         /* to optimize, we temporarily turn off analysis mode while we undo
13872          * all the moves. Otherwise we get analysis output after each undo.
13873          */
13874         if (first.analysisSupport) {
13875           SendToProgram("exit\nforce\n", &first);
13876           first.analyzing = FALSE;
13877         }
13878     }
13879
13880     if (gameMode == IcsExamining && !pausing) {
13881         SendToICS(ics_prefix);
13882         SendToICS("backward 999999\n");
13883     } else {
13884         BackwardInner(backwardMostMove);
13885     }
13886
13887     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13888         /* we have fed all the moves, so reactivate analysis mode */
13889         SendToProgram("analyze\n", &first);
13890         first.analyzing = TRUE;
13891         /*first.maybeThinking = TRUE;*/
13892         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13893     }
13894 }
13895
13896 void
13897 ToNrEvent(int to)
13898 {
13899   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13900   if (to >= forwardMostMove) to = forwardMostMove;
13901   if (to <= backwardMostMove) to = backwardMostMove;
13902   if (to < currentMove) {
13903     BackwardInner(to);
13904   } else {
13905     ForwardInner(to);
13906   }
13907 }
13908
13909 void
13910 RevertEvent(Boolean annotate)
13911 {
13912     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13913         return;
13914     }
13915     if (gameMode != IcsExamining) {
13916         DisplayError(_("You are not examining a game"), 0);
13917         return;
13918     }
13919     if (pausing) {
13920         DisplayError(_("You can't revert while pausing"), 0);
13921         return;
13922     }
13923     SendToICS(ics_prefix);
13924     SendToICS("revert\n");
13925 }
13926
13927 void
13928 RetractMoveEvent()
13929 {
13930     switch (gameMode) {
13931       case MachinePlaysWhite:
13932       case MachinePlaysBlack:
13933         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13934             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13935             return;
13936         }
13937         if (forwardMostMove < 2) return;
13938         currentMove = forwardMostMove = forwardMostMove - 2;
13939         whiteTimeRemaining = timeRemaining[0][currentMove];
13940         blackTimeRemaining = timeRemaining[1][currentMove];
13941         DisplayBothClocks();
13942         DisplayMove(currentMove - 1);
13943         ClearHighlights();/*!! could figure this out*/
13944         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13945         SendToProgram("remove\n", &first);
13946         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13947         break;
13948
13949       case BeginningOfGame:
13950       default:
13951         break;
13952
13953       case IcsPlayingWhite:
13954       case IcsPlayingBlack:
13955         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13956             SendToICS(ics_prefix);
13957             SendToICS("takeback 2\n");
13958         } else {
13959             SendToICS(ics_prefix);
13960             SendToICS("takeback 1\n");
13961         }
13962         break;
13963     }
13964 }
13965
13966 void
13967 MoveNowEvent()
13968 {
13969     ChessProgramState *cps;
13970
13971     switch (gameMode) {
13972       case MachinePlaysWhite:
13973         if (!WhiteOnMove(forwardMostMove)) {
13974             DisplayError(_("It is your turn"), 0);
13975             return;
13976         }
13977         cps = &first;
13978         break;
13979       case MachinePlaysBlack:
13980         if (WhiteOnMove(forwardMostMove)) {
13981             DisplayError(_("It is your turn"), 0);
13982             return;
13983         }
13984         cps = &first;
13985         break;
13986       case TwoMachinesPlay:
13987         if (WhiteOnMove(forwardMostMove) ==
13988             (first.twoMachinesColor[0] == 'w')) {
13989             cps = &first;
13990         } else {
13991             cps = &second;
13992         }
13993         break;
13994       case BeginningOfGame:
13995       default:
13996         return;
13997     }
13998     SendToProgram("?\n", cps);
13999 }
14000
14001 void
14002 TruncateGameEvent()
14003 {
14004     EditGameEvent();
14005     if (gameMode != EditGame) return;
14006     TruncateGame();
14007 }
14008
14009 void
14010 TruncateGame()
14011 {
14012     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14013     if (forwardMostMove > currentMove) {
14014         if (gameInfo.resultDetails != NULL) {
14015             free(gameInfo.resultDetails);
14016             gameInfo.resultDetails = NULL;
14017             gameInfo.result = GameUnfinished;
14018         }
14019         forwardMostMove = currentMove;
14020         HistorySet(parseList, backwardMostMove, forwardMostMove,
14021                    currentMove-1);
14022     }
14023 }
14024
14025 void
14026 HintEvent()
14027 {
14028     if (appData.noChessProgram) return;
14029     switch (gameMode) {
14030       case MachinePlaysWhite:
14031         if (WhiteOnMove(forwardMostMove)) {
14032             DisplayError(_("Wait until your turn"), 0);
14033             return;
14034         }
14035         break;
14036       case BeginningOfGame:
14037       case MachinePlaysBlack:
14038         if (!WhiteOnMove(forwardMostMove)) {
14039             DisplayError(_("Wait until your turn"), 0);
14040             return;
14041         }
14042         break;
14043       default:
14044         DisplayError(_("No hint available"), 0);
14045         return;
14046     }
14047     SendToProgram("hint\n", &first);
14048     hintRequested = TRUE;
14049 }
14050
14051 void
14052 BookEvent()
14053 {
14054     if (appData.noChessProgram) return;
14055     switch (gameMode) {
14056       case MachinePlaysWhite:
14057         if (WhiteOnMove(forwardMostMove)) {
14058             DisplayError(_("Wait until your turn"), 0);
14059             return;
14060         }
14061         break;
14062       case BeginningOfGame:
14063       case MachinePlaysBlack:
14064         if (!WhiteOnMove(forwardMostMove)) {
14065             DisplayError(_("Wait until your turn"), 0);
14066             return;
14067         }
14068         break;
14069       case EditPosition:
14070         EditPositionDone(TRUE);
14071         break;
14072       case TwoMachinesPlay:
14073         return;
14074       default:
14075         break;
14076     }
14077     SendToProgram("bk\n", &first);
14078     bookOutput[0] = NULLCHAR;
14079     bookRequested = TRUE;
14080 }
14081
14082 void
14083 AboutGameEvent()
14084 {
14085     char *tags = PGNTags(&gameInfo);
14086     TagsPopUp(tags, CmailMsg());
14087     free(tags);
14088 }
14089
14090 /* end button procedures */
14091
14092 void
14093 PrintPosition(fp, move)
14094      FILE *fp;
14095      int move;
14096 {
14097     int i, j;
14098
14099     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14100         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14101             char c = PieceToChar(boards[move][i][j]);
14102             fputc(c == 'x' ? '.' : c, fp);
14103             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14104         }
14105     }
14106     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14107       fprintf(fp, "white to play\n");
14108     else
14109       fprintf(fp, "black to play\n");
14110 }
14111
14112 void
14113 PrintOpponents(fp)
14114      FILE *fp;
14115 {
14116     if (gameInfo.white != NULL) {
14117         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14118     } else {
14119         fprintf(fp, "\n");
14120     }
14121 }
14122
14123 /* Find last component of program's own name, using some heuristics */
14124 void
14125 TidyProgramName(prog, host, buf)
14126      char *prog, *host, buf[MSG_SIZ];
14127 {
14128     char *p, *q;
14129     int local = (strcmp(host, "localhost") == 0);
14130     while (!local && (p = strchr(prog, ';')) != NULL) {
14131         p++;
14132         while (*p == ' ') p++;
14133         prog = p;
14134     }
14135     if (*prog == '"' || *prog == '\'') {
14136         q = strchr(prog + 1, *prog);
14137     } else {
14138         q = strchr(prog, ' ');
14139     }
14140     if (q == NULL) q = prog + strlen(prog);
14141     p = q;
14142     while (p >= prog && *p != '/' && *p != '\\') p--;
14143     p++;
14144     if(p == prog && *p == '"') p++;
14145     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14146     memcpy(buf, p, q - p);
14147     buf[q - p] = NULLCHAR;
14148     if (!local) {
14149         strcat(buf, "@");
14150         strcat(buf, host);
14151     }
14152 }
14153
14154 char *
14155 TimeControlTagValue()
14156 {
14157     char buf[MSG_SIZ];
14158     if (!appData.clockMode) {
14159       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14160     } else if (movesPerSession > 0) {
14161       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14162     } else if (timeIncrement == 0) {
14163       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14164     } else {
14165       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14166     }
14167     return StrSave(buf);
14168 }
14169
14170 void
14171 SetGameInfo()
14172 {
14173     /* This routine is used only for certain modes */
14174     VariantClass v = gameInfo.variant;
14175     ChessMove r = GameUnfinished;
14176     char *p = NULL;
14177
14178     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14179         r = gameInfo.result;
14180         p = gameInfo.resultDetails;
14181         gameInfo.resultDetails = NULL;
14182     }
14183     ClearGameInfo(&gameInfo);
14184     gameInfo.variant = v;
14185
14186     switch (gameMode) {
14187       case MachinePlaysWhite:
14188         gameInfo.event = StrSave( appData.pgnEventHeader );
14189         gameInfo.site = StrSave(HostName());
14190         gameInfo.date = PGNDate();
14191         gameInfo.round = StrSave("-");
14192         gameInfo.white = StrSave(first.tidy);
14193         gameInfo.black = StrSave(UserName());
14194         gameInfo.timeControl = TimeControlTagValue();
14195         break;
14196
14197       case MachinePlaysBlack:
14198         gameInfo.event = StrSave( appData.pgnEventHeader );
14199         gameInfo.site = StrSave(HostName());
14200         gameInfo.date = PGNDate();
14201         gameInfo.round = StrSave("-");
14202         gameInfo.white = StrSave(UserName());
14203         gameInfo.black = StrSave(first.tidy);
14204         gameInfo.timeControl = TimeControlTagValue();
14205         break;
14206
14207       case TwoMachinesPlay:
14208         gameInfo.event = StrSave( appData.pgnEventHeader );
14209         gameInfo.site = StrSave(HostName());
14210         gameInfo.date = PGNDate();
14211         if (roundNr > 0) {
14212             char buf[MSG_SIZ];
14213             snprintf(buf, MSG_SIZ, "%d", roundNr);
14214             gameInfo.round = StrSave(buf);
14215         } else {
14216             gameInfo.round = StrSave("-");
14217         }
14218         if (first.twoMachinesColor[0] == 'w') {
14219             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14220             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14221         } else {
14222             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14223             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14224         }
14225         gameInfo.timeControl = TimeControlTagValue();
14226         break;
14227
14228       case EditGame:
14229         gameInfo.event = StrSave("Edited game");
14230         gameInfo.site = StrSave(HostName());
14231         gameInfo.date = PGNDate();
14232         gameInfo.round = StrSave("-");
14233         gameInfo.white = StrSave("-");
14234         gameInfo.black = StrSave("-");
14235         gameInfo.result = r;
14236         gameInfo.resultDetails = p;
14237         break;
14238
14239       case EditPosition:
14240         gameInfo.event = StrSave("Edited position");
14241         gameInfo.site = StrSave(HostName());
14242         gameInfo.date = PGNDate();
14243         gameInfo.round = StrSave("-");
14244         gameInfo.white = StrSave("-");
14245         gameInfo.black = StrSave("-");
14246         break;
14247
14248       case IcsPlayingWhite:
14249       case IcsPlayingBlack:
14250       case IcsObserving:
14251       case IcsExamining:
14252         break;
14253
14254       case PlayFromGameFile:
14255         gameInfo.event = StrSave("Game from non-PGN file");
14256         gameInfo.site = StrSave(HostName());
14257         gameInfo.date = PGNDate();
14258         gameInfo.round = StrSave("-");
14259         gameInfo.white = StrSave("?");
14260         gameInfo.black = StrSave("?");
14261         break;
14262
14263       default:
14264         break;
14265     }
14266 }
14267
14268 void
14269 ReplaceComment(index, text)
14270      int index;
14271      char *text;
14272 {
14273     int len;
14274     char *p;
14275     float score;
14276
14277     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14278        pvInfoList[index-1].depth == len &&
14279        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14280        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14281     while (*text == '\n') text++;
14282     len = strlen(text);
14283     while (len > 0 && text[len - 1] == '\n') len--;
14284
14285     if (commentList[index] != NULL)
14286       free(commentList[index]);
14287
14288     if (len == 0) {
14289         commentList[index] = NULL;
14290         return;
14291     }
14292   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14293       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14294       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14295     commentList[index] = (char *) malloc(len + 2);
14296     strncpy(commentList[index], text, len);
14297     commentList[index][len] = '\n';
14298     commentList[index][len + 1] = NULLCHAR;
14299   } else {
14300     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14301     char *p;
14302     commentList[index] = (char *) malloc(len + 7);
14303     safeStrCpy(commentList[index], "{\n", 3);
14304     safeStrCpy(commentList[index]+2, text, len+1);
14305     commentList[index][len+2] = NULLCHAR;
14306     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14307     strcat(commentList[index], "\n}\n");
14308   }
14309 }
14310
14311 void
14312 CrushCRs(text)
14313      char *text;
14314 {
14315   char *p = text;
14316   char *q = text;
14317   char ch;
14318
14319   do {
14320     ch = *p++;
14321     if (ch == '\r') continue;
14322     *q++ = ch;
14323   } while (ch != '\0');
14324 }
14325
14326 void
14327 AppendComment(index, text, addBraces)
14328      int index;
14329      char *text;
14330      Boolean addBraces; // [HGM] braces: tells if we should add {}
14331 {
14332     int oldlen, len;
14333     char *old;
14334
14335 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14336     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14337
14338     CrushCRs(text);
14339     while (*text == '\n') text++;
14340     len = strlen(text);
14341     while (len > 0 && text[len - 1] == '\n') len--;
14342
14343     if (len == 0) return;
14344
14345     if (commentList[index] != NULL) {
14346         old = commentList[index];
14347         oldlen = strlen(old);
14348         while(commentList[index][oldlen-1] ==  '\n')
14349           commentList[index][--oldlen] = NULLCHAR;
14350         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14351         safeStrCpy(commentList[index], old, oldlen + len + 6);
14352         free(old);
14353         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14354         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14355           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14356           while (*text == '\n') { text++; len--; }
14357           commentList[index][--oldlen] = NULLCHAR;
14358       }
14359         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14360         else          strcat(commentList[index], "\n");
14361         strcat(commentList[index], text);
14362         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14363         else          strcat(commentList[index], "\n");
14364     } else {
14365         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14366         if(addBraces)
14367           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14368         else commentList[index][0] = NULLCHAR;
14369         strcat(commentList[index], text);
14370         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14371         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14372     }
14373 }
14374
14375 static char * FindStr( char * text, char * sub_text )
14376 {
14377     char * result = strstr( text, sub_text );
14378
14379     if( result != NULL ) {
14380         result += strlen( sub_text );
14381     }
14382
14383     return result;
14384 }
14385
14386 /* [AS] Try to extract PV info from PGN comment */
14387 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14388 char *GetInfoFromComment( int index, char * text )
14389 {
14390     char * sep = text, *p;
14391
14392     if( text != NULL && index > 0 ) {
14393         int score = 0;
14394         int depth = 0;
14395         int time = -1, sec = 0, deci;
14396         char * s_eval = FindStr( text, "[%eval " );
14397         char * s_emt = FindStr( text, "[%emt " );
14398
14399         if( s_eval != NULL || s_emt != NULL ) {
14400             /* New style */
14401             char delim;
14402
14403             if( s_eval != NULL ) {
14404                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14405                     return text;
14406                 }
14407
14408                 if( delim != ']' ) {
14409                     return text;
14410                 }
14411             }
14412
14413             if( s_emt != NULL ) {
14414             }
14415                 return text;
14416         }
14417         else {
14418             /* We expect something like: [+|-]nnn.nn/dd */
14419             int score_lo = 0;
14420
14421             if(*text != '{') return text; // [HGM] braces: must be normal comment
14422
14423             sep = strchr( text, '/' );
14424             if( sep == NULL || sep < (text+4) ) {
14425                 return text;
14426             }
14427
14428             p = text;
14429             if(p[1] == '(') { // comment starts with PV
14430                p = strchr(p, ')'); // locate end of PV
14431                if(p == NULL || sep < p+5) return text;
14432                // at this point we have something like "{(.*) +0.23/6 ..."
14433                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14434                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14435                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14436             }
14437             time = -1; sec = -1; deci = -1;
14438             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14439                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14440                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14441                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14442                 return text;
14443             }
14444
14445             if( score_lo < 0 || score_lo >= 100 ) {
14446                 return text;
14447             }
14448
14449             if(sec >= 0) time = 600*time + 10*sec; else
14450             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14451
14452             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14453
14454             /* [HGM] PV time: now locate end of PV info */
14455             while( *++sep >= '0' && *sep <= '9'); // strip depth
14456             if(time >= 0)
14457             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14458             if(sec >= 0)
14459             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14460             if(deci >= 0)
14461             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14462             while(*sep == ' ') sep++;
14463         }
14464
14465         if( depth <= 0 ) {
14466             return text;
14467         }
14468
14469         if( time < 0 ) {
14470             time = -1;
14471         }
14472
14473         pvInfoList[index-1].depth = depth;
14474         pvInfoList[index-1].score = score;
14475         pvInfoList[index-1].time  = 10*time; // centi-sec
14476         if(*sep == '}') *sep = 0; else *--sep = '{';
14477         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14478     }
14479     return sep;
14480 }
14481
14482 void
14483 SendToProgram(message, cps)
14484      char *message;
14485      ChessProgramState *cps;
14486 {
14487     int count, outCount, error;
14488     char buf[MSG_SIZ];
14489
14490     if (cps->pr == NULL) return;
14491     Attention(cps);
14492
14493     if (appData.debugMode) {
14494         TimeMark now;
14495         GetTimeMark(&now);
14496         fprintf(debugFP, "%ld >%-6s: %s",
14497                 SubtractTimeMarks(&now, &programStartTime),
14498                 cps->which, message);
14499     }
14500
14501     count = strlen(message);
14502     outCount = OutputToProcess(cps->pr, message, count, &error);
14503     if (outCount < count && !exiting
14504                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14505       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14506       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14507         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14508             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14509                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14510                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14511                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14512             } else {
14513                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14514                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14515                 gameInfo.result = res;
14516             }
14517             gameInfo.resultDetails = StrSave(buf);
14518         }
14519         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14520         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14521     }
14522 }
14523
14524 void
14525 ReceiveFromProgram(isr, closure, message, count, error)
14526      InputSourceRef isr;
14527      VOIDSTAR closure;
14528      char *message;
14529      int count;
14530      int error;
14531 {
14532     char *end_str;
14533     char buf[MSG_SIZ];
14534     ChessProgramState *cps = (ChessProgramState *)closure;
14535
14536     if (isr != cps->isr) return; /* Killed intentionally */
14537     if (count <= 0) {
14538         if (count == 0) {
14539             RemoveInputSource(cps->isr);
14540             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14541             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14542                     _(cps->which), cps->program);
14543         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14544                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14545                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14546                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14547                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14548                 } else {
14549                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14550                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14551                     gameInfo.result = res;
14552                 }
14553                 gameInfo.resultDetails = StrSave(buf);
14554             }
14555             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14556             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14557         } else {
14558             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14559                     _(cps->which), cps->program);
14560             RemoveInputSource(cps->isr);
14561
14562             /* [AS] Program is misbehaving badly... kill it */
14563             if( count == -2 ) {
14564                 DestroyChildProcess( cps->pr, 9 );
14565                 cps->pr = NoProc;
14566             }
14567
14568             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14569         }
14570         return;
14571     }
14572
14573     if ((end_str = strchr(message, '\r')) != NULL)
14574       *end_str = NULLCHAR;
14575     if ((end_str = strchr(message, '\n')) != NULL)
14576       *end_str = NULLCHAR;
14577
14578     if (appData.debugMode) {
14579         TimeMark now; int print = 1;
14580         char *quote = ""; char c; int i;
14581
14582         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14583                 char start = message[0];
14584                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14585                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14586                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14587                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14588                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14589                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14590                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14591                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14592                    sscanf(message, "hint: %c", &c)!=1 && 
14593                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14594                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14595                     print = (appData.engineComments >= 2);
14596                 }
14597                 message[0] = start; // restore original message
14598         }
14599         if(print) {
14600                 GetTimeMark(&now);
14601                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14602                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14603                         quote,
14604                         message);
14605         }
14606     }
14607
14608     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14609     if (appData.icsEngineAnalyze) {
14610         if (strstr(message, "whisper") != NULL ||
14611              strstr(message, "kibitz") != NULL ||
14612             strstr(message, "tellics") != NULL) return;
14613     }
14614
14615     HandleMachineMove(message, cps);
14616 }
14617
14618
14619 void
14620 SendTimeControl(cps, mps, tc, inc, sd, st)
14621      ChessProgramState *cps;
14622      int mps, inc, sd, st;
14623      long tc;
14624 {
14625     char buf[MSG_SIZ];
14626     int seconds;
14627
14628     if( timeControl_2 > 0 ) {
14629         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14630             tc = timeControl_2;
14631         }
14632     }
14633     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14634     inc /= cps->timeOdds;
14635     st  /= cps->timeOdds;
14636
14637     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14638
14639     if (st > 0) {
14640       /* Set exact time per move, normally using st command */
14641       if (cps->stKludge) {
14642         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14643         seconds = st % 60;
14644         if (seconds == 0) {
14645           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14646         } else {
14647           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14648         }
14649       } else {
14650         snprintf(buf, MSG_SIZ, "st %d\n", st);
14651       }
14652     } else {
14653       /* Set conventional or incremental time control, using level command */
14654       if (seconds == 0) {
14655         /* Note old gnuchess bug -- minutes:seconds used to not work.
14656            Fixed in later versions, but still avoid :seconds
14657            when seconds is 0. */
14658         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14659       } else {
14660         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14661                  seconds, inc/1000.);
14662       }
14663     }
14664     SendToProgram(buf, cps);
14665
14666     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14667     /* Orthogonally, limit search to given depth */
14668     if (sd > 0) {
14669       if (cps->sdKludge) {
14670         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14671       } else {
14672         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14673       }
14674       SendToProgram(buf, cps);
14675     }
14676
14677     if(cps->nps >= 0) { /* [HGM] nps */
14678         if(cps->supportsNPS == FALSE)
14679           cps->nps = -1; // don't use if engine explicitly says not supported!
14680         else {
14681           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14682           SendToProgram(buf, cps);
14683         }
14684     }
14685 }
14686
14687 ChessProgramState *WhitePlayer()
14688 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14689 {
14690     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14691        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14692         return &second;
14693     return &first;
14694 }
14695
14696 void
14697 SendTimeRemaining(cps, machineWhite)
14698      ChessProgramState *cps;
14699      int /*boolean*/ machineWhite;
14700 {
14701     char message[MSG_SIZ];
14702     long time, otime;
14703
14704     /* Note: this routine must be called when the clocks are stopped
14705        or when they have *just* been set or switched; otherwise
14706        it will be off by the time since the current tick started.
14707     */
14708     if (machineWhite) {
14709         time = whiteTimeRemaining / 10;
14710         otime = blackTimeRemaining / 10;
14711     } else {
14712         time = blackTimeRemaining / 10;
14713         otime = whiteTimeRemaining / 10;
14714     }
14715     /* [HGM] translate opponent's time by time-odds factor */
14716     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14717     if (appData.debugMode) {
14718         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14719     }
14720
14721     if (time <= 0) time = 1;
14722     if (otime <= 0) otime = 1;
14723
14724     snprintf(message, MSG_SIZ, "time %ld\n", time);
14725     SendToProgram(message, cps);
14726
14727     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14728     SendToProgram(message, cps);
14729 }
14730
14731 int
14732 BoolFeature(p, name, loc, cps)
14733      char **p;
14734      char *name;
14735      int *loc;
14736      ChessProgramState *cps;
14737 {
14738   char buf[MSG_SIZ];
14739   int len = strlen(name);
14740   int val;
14741
14742   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14743     (*p) += len + 1;
14744     sscanf(*p, "%d", &val);
14745     *loc = (val != 0);
14746     while (**p && **p != ' ')
14747       (*p)++;
14748     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14749     SendToProgram(buf, cps);
14750     return TRUE;
14751   }
14752   return FALSE;
14753 }
14754
14755 int
14756 IntFeature(p, name, loc, cps)
14757      char **p;
14758      char *name;
14759      int *loc;
14760      ChessProgramState *cps;
14761 {
14762   char buf[MSG_SIZ];
14763   int len = strlen(name);
14764   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14765     (*p) += len + 1;
14766     sscanf(*p, "%d", loc);
14767     while (**p && **p != ' ') (*p)++;
14768     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14769     SendToProgram(buf, cps);
14770     return TRUE;
14771   }
14772   return FALSE;
14773 }
14774
14775 int
14776 StringFeature(p, name, loc, cps)
14777      char **p;
14778      char *name;
14779      char loc[];
14780      ChessProgramState *cps;
14781 {
14782   char buf[MSG_SIZ];
14783   int len = strlen(name);
14784   if (strncmp((*p), name, len) == 0
14785       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14786     (*p) += len + 2;
14787     sscanf(*p, "%[^\"]", loc);
14788     while (**p && **p != '\"') (*p)++;
14789     if (**p == '\"') (*p)++;
14790     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14791     SendToProgram(buf, cps);
14792     return TRUE;
14793   }
14794   return FALSE;
14795 }
14796
14797 int
14798 ParseOption(Option *opt, ChessProgramState *cps)
14799 // [HGM] options: process the string that defines an engine option, and determine
14800 // name, type, default value, and allowed value range
14801 {
14802         char *p, *q, buf[MSG_SIZ];
14803         int n, min = (-1)<<31, max = 1<<31, def;
14804
14805         if(p = strstr(opt->name, " -spin ")) {
14806             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14807             if(max < min) max = min; // enforce consistency
14808             if(def < min) def = min;
14809             if(def > max) def = max;
14810             opt->value = def;
14811             opt->min = min;
14812             opt->max = max;
14813             opt->type = Spin;
14814         } else if((p = strstr(opt->name, " -slider "))) {
14815             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14816             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14817             if(max < min) max = min; // enforce consistency
14818             if(def < min) def = min;
14819             if(def > max) def = max;
14820             opt->value = def;
14821             opt->min = min;
14822             opt->max = max;
14823             opt->type = Spin; // Slider;
14824         } else if((p = strstr(opt->name, " -string "))) {
14825             opt->textValue = p+9;
14826             opt->type = TextBox;
14827         } else if((p = strstr(opt->name, " -file "))) {
14828             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14829             opt->textValue = p+7;
14830             opt->type = FileName; // FileName;
14831         } else if((p = strstr(opt->name, " -path "))) {
14832             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14833             opt->textValue = p+7;
14834             opt->type = PathName; // PathName;
14835         } else if(p = strstr(opt->name, " -check ")) {
14836             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14837             opt->value = (def != 0);
14838             opt->type = CheckBox;
14839         } else if(p = strstr(opt->name, " -combo ")) {
14840             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14841             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14842             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14843             opt->value = n = 0;
14844             while(q = StrStr(q, " /// ")) {
14845                 n++; *q = 0;    // count choices, and null-terminate each of them
14846                 q += 5;
14847                 if(*q == '*') { // remember default, which is marked with * prefix
14848                     q++;
14849                     opt->value = n;
14850                 }
14851                 cps->comboList[cps->comboCnt++] = q;
14852             }
14853             cps->comboList[cps->comboCnt++] = NULL;
14854             opt->max = n + 1;
14855             opt->type = ComboBox;
14856         } else if(p = strstr(opt->name, " -button")) {
14857             opt->type = Button;
14858         } else if(p = strstr(opt->name, " -save")) {
14859             opt->type = SaveButton;
14860         } else return FALSE;
14861         *p = 0; // terminate option name
14862         // now look if the command-line options define a setting for this engine option.
14863         if(cps->optionSettings && cps->optionSettings[0])
14864             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14865         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14866           snprintf(buf, MSG_SIZ, "option %s", p);
14867                 if(p = strstr(buf, ",")) *p = 0;
14868                 if(q = strchr(buf, '=')) switch(opt->type) {
14869                     case ComboBox:
14870                         for(n=0; n<opt->max; n++)
14871                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14872                         break;
14873                     case TextBox:
14874                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14875                         break;
14876                     case Spin:
14877                     case CheckBox:
14878                         opt->value = atoi(q+1);
14879                     default:
14880                         break;
14881                 }
14882                 strcat(buf, "\n");
14883                 SendToProgram(buf, cps);
14884         }
14885         return TRUE;
14886 }
14887
14888 void
14889 FeatureDone(cps, val)
14890      ChessProgramState* cps;
14891      int val;
14892 {
14893   DelayedEventCallback cb = GetDelayedEvent();
14894   if ((cb == InitBackEnd3 && cps == &first) ||
14895       (cb == SettingsMenuIfReady && cps == &second) ||
14896       (cb == LoadEngine) ||
14897       (cb == TwoMachinesEventIfReady)) {
14898     CancelDelayedEvent();
14899     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14900   }
14901   cps->initDone = val;
14902 }
14903
14904 /* Parse feature command from engine */
14905 void
14906 ParseFeatures(args, cps)
14907      char* args;
14908      ChessProgramState *cps;
14909 {
14910   char *p = args;
14911   char *q;
14912   int val;
14913   char buf[MSG_SIZ];
14914
14915   for (;;) {
14916     while (*p == ' ') p++;
14917     if (*p == NULLCHAR) return;
14918
14919     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14920     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14921     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14922     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14923     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14924     if (BoolFeature(&p, "reuse", &val, cps)) {
14925       /* Engine can disable reuse, but can't enable it if user said no */
14926       if (!val) cps->reuse = FALSE;
14927       continue;
14928     }
14929     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14930     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14931       if (gameMode == TwoMachinesPlay) {
14932         DisplayTwoMachinesTitle();
14933       } else {
14934         DisplayTitle("");
14935       }
14936       continue;
14937     }
14938     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14939     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14940     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14941     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14942     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14943     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14944     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14945     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14946     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14947     if (IntFeature(&p, "done", &val, cps)) {
14948       FeatureDone(cps, val);
14949       continue;
14950     }
14951     /* Added by Tord: */
14952     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14953     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14954     /* End of additions by Tord */
14955
14956     /* [HGM] added features: */
14957     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14958     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14959     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14960     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14961     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14962     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14963     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14964         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14965           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14966             SendToProgram(buf, cps);
14967             continue;
14968         }
14969         if(cps->nrOptions >= MAX_OPTIONS) {
14970             cps->nrOptions--;
14971             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14972             DisplayError(buf, 0);
14973         }
14974         continue;
14975     }
14976     /* End of additions by HGM */
14977
14978     /* unknown feature: complain and skip */
14979     q = p;
14980     while (*q && *q != '=') q++;
14981     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14982     SendToProgram(buf, cps);
14983     p = q;
14984     if (*p == '=') {
14985       p++;
14986       if (*p == '\"') {
14987         p++;
14988         while (*p && *p != '\"') p++;
14989         if (*p == '\"') p++;
14990       } else {
14991         while (*p && *p != ' ') p++;
14992       }
14993     }
14994   }
14995
14996 }
14997
14998 void
14999 PeriodicUpdatesEvent(newState)
15000      int newState;
15001 {
15002     if (newState == appData.periodicUpdates)
15003       return;
15004
15005     appData.periodicUpdates=newState;
15006
15007     /* Display type changes, so update it now */
15008 //    DisplayAnalysis();
15009
15010     /* Get the ball rolling again... */
15011     if (newState) {
15012         AnalysisPeriodicEvent(1);
15013         StartAnalysisClock();
15014     }
15015 }
15016
15017 void
15018 PonderNextMoveEvent(newState)
15019      int newState;
15020 {
15021     if (newState == appData.ponderNextMove) return;
15022     if (gameMode == EditPosition) EditPositionDone(TRUE);
15023     if (newState) {
15024         SendToProgram("hard\n", &first);
15025         if (gameMode == TwoMachinesPlay) {
15026             SendToProgram("hard\n", &second);
15027         }
15028     } else {
15029         SendToProgram("easy\n", &first);
15030         thinkOutput[0] = NULLCHAR;
15031         if (gameMode == TwoMachinesPlay) {
15032             SendToProgram("easy\n", &second);
15033         }
15034     }
15035     appData.ponderNextMove = newState;
15036 }
15037
15038 void
15039 NewSettingEvent(option, feature, command, value)
15040      char *command;
15041      int option, value, *feature;
15042 {
15043     char buf[MSG_SIZ];
15044
15045     if (gameMode == EditPosition) EditPositionDone(TRUE);
15046     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15047     if(feature == NULL || *feature) SendToProgram(buf, &first);
15048     if (gameMode == TwoMachinesPlay) {
15049         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15050     }
15051 }
15052
15053 void
15054 ShowThinkingEvent()
15055 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15056 {
15057     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15058     int newState = appData.showThinking
15059         // [HGM] thinking: other features now need thinking output as well
15060         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15061
15062     if (oldState == newState) return;
15063     oldState = newState;
15064     if (gameMode == EditPosition) EditPositionDone(TRUE);
15065     if (oldState) {
15066         SendToProgram("post\n", &first);
15067         if (gameMode == TwoMachinesPlay) {
15068             SendToProgram("post\n", &second);
15069         }
15070     } else {
15071         SendToProgram("nopost\n", &first);
15072         thinkOutput[0] = NULLCHAR;
15073         if (gameMode == TwoMachinesPlay) {
15074             SendToProgram("nopost\n", &second);
15075         }
15076     }
15077 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15078 }
15079
15080 void
15081 AskQuestionEvent(title, question, replyPrefix, which)
15082      char *title; char *question; char *replyPrefix; char *which;
15083 {
15084   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15085   if (pr == NoProc) return;
15086   AskQuestion(title, question, replyPrefix, pr);
15087 }
15088
15089 void
15090 TypeInEvent(char firstChar)
15091 {
15092     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15093         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15094         gameMode == AnalyzeMode || gameMode == EditGame || 
15095         gameMode == EditPosition || gameMode == IcsExamining ||
15096         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15097         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15098                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15099                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15100         gameMode == Training) PopUpMoveDialog(firstChar);
15101 }
15102
15103 void
15104 TypeInDoneEvent(char *move)
15105 {
15106         Board board;
15107         int n, fromX, fromY, toX, toY;
15108         char promoChar;
15109         ChessMove moveType;
15110
15111         // [HGM] FENedit
15112         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15113                 EditPositionPasteFEN(move);
15114                 return;
15115         }
15116         // [HGM] movenum: allow move number to be typed in any mode
15117         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15118           ToNrEvent(2*n-1);
15119           return;
15120         }
15121
15122       if (gameMode != EditGame && currentMove != forwardMostMove && 
15123         gameMode != Training) {
15124         DisplayMoveError(_("Displayed move is not current"));
15125       } else {
15126         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15127           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15128         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15129         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15130           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15131           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15132         } else {
15133           DisplayMoveError(_("Could not parse move"));
15134         }
15135       }
15136 }
15137
15138 void
15139 DisplayMove(moveNumber)
15140      int moveNumber;
15141 {
15142     char message[MSG_SIZ];
15143     char res[MSG_SIZ];
15144     char cpThinkOutput[MSG_SIZ];
15145
15146     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15147
15148     if (moveNumber == forwardMostMove - 1 ||
15149         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15150
15151         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15152
15153         if (strchr(cpThinkOutput, '\n')) {
15154             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15155         }
15156     } else {
15157         *cpThinkOutput = NULLCHAR;
15158     }
15159
15160     /* [AS] Hide thinking from human user */
15161     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15162         *cpThinkOutput = NULLCHAR;
15163         if( thinkOutput[0] != NULLCHAR ) {
15164             int i;
15165
15166             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15167                 cpThinkOutput[i] = '.';
15168             }
15169             cpThinkOutput[i] = NULLCHAR;
15170             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15171         }
15172     }
15173
15174     if (moveNumber == forwardMostMove - 1 &&
15175         gameInfo.resultDetails != NULL) {
15176         if (gameInfo.resultDetails[0] == NULLCHAR) {
15177           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15178         } else {
15179           snprintf(res, MSG_SIZ, " {%s} %s",
15180                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15181         }
15182     } else {
15183         res[0] = NULLCHAR;
15184     }
15185
15186     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15187         DisplayMessage(res, cpThinkOutput);
15188     } else {
15189       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15190                 WhiteOnMove(moveNumber) ? " " : ".. ",
15191                 parseList[moveNumber], res);
15192         DisplayMessage(message, cpThinkOutput);
15193     }
15194 }
15195
15196 void
15197 DisplayComment(moveNumber, text)
15198      int moveNumber;
15199      char *text;
15200 {
15201     char title[MSG_SIZ];
15202     char buf[8000]; // comment can be long!
15203     int score, depth;
15204
15205     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15206       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15207     } else {
15208       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15209               WhiteOnMove(moveNumber) ? " " : ".. ",
15210               parseList[moveNumber]);
15211     }
15212     // [HGM] PV info: display PV info together with (or as) comment
15213     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15214       if(text == NULL) text = "";
15215       score = pvInfoList[moveNumber].score;
15216       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15217               depth, (pvInfoList[moveNumber].time+50)/100, text);
15218       text = buf;
15219     }
15220     if (text != NULL && (appData.autoDisplayComment || commentUp))
15221         CommentPopUp(title, text);
15222 }
15223
15224 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15225  * might be busy thinking or pondering.  It can be omitted if your
15226  * gnuchess is configured to stop thinking immediately on any user
15227  * input.  However, that gnuchess feature depends on the FIONREAD
15228  * ioctl, which does not work properly on some flavors of Unix.
15229  */
15230 void
15231 Attention(cps)
15232      ChessProgramState *cps;
15233 {
15234 #if ATTENTION
15235     if (!cps->useSigint) return;
15236     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15237     switch (gameMode) {
15238       case MachinePlaysWhite:
15239       case MachinePlaysBlack:
15240       case TwoMachinesPlay:
15241       case IcsPlayingWhite:
15242       case IcsPlayingBlack:
15243       case AnalyzeMode:
15244       case AnalyzeFile:
15245         /* Skip if we know it isn't thinking */
15246         if (!cps->maybeThinking) return;
15247         if (appData.debugMode)
15248           fprintf(debugFP, "Interrupting %s\n", cps->which);
15249         InterruptChildProcess(cps->pr);
15250         cps->maybeThinking = FALSE;
15251         break;
15252       default:
15253         break;
15254     }
15255 #endif /*ATTENTION*/
15256 }
15257
15258 int
15259 CheckFlags()
15260 {
15261     if (whiteTimeRemaining <= 0) {
15262         if (!whiteFlag) {
15263             whiteFlag = TRUE;
15264             if (appData.icsActive) {
15265                 if (appData.autoCallFlag &&
15266                     gameMode == IcsPlayingBlack && !blackFlag) {
15267                   SendToICS(ics_prefix);
15268                   SendToICS("flag\n");
15269                 }
15270             } else {
15271                 if (blackFlag) {
15272                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15273                 } else {
15274                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15275                     if (appData.autoCallFlag) {
15276                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15277                         return TRUE;
15278                     }
15279                 }
15280             }
15281         }
15282     }
15283     if (blackTimeRemaining <= 0) {
15284         if (!blackFlag) {
15285             blackFlag = TRUE;
15286             if (appData.icsActive) {
15287                 if (appData.autoCallFlag &&
15288                     gameMode == IcsPlayingWhite && !whiteFlag) {
15289                   SendToICS(ics_prefix);
15290                   SendToICS("flag\n");
15291                 }
15292             } else {
15293                 if (whiteFlag) {
15294                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15295                 } else {
15296                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15297                     if (appData.autoCallFlag) {
15298                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15299                         return TRUE;
15300                     }
15301                 }
15302             }
15303         }
15304     }
15305     return FALSE;
15306 }
15307
15308 void
15309 CheckTimeControl()
15310 {
15311     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15312         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15313
15314     /*
15315      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15316      */
15317     if ( !WhiteOnMove(forwardMostMove) ) {
15318         /* White made time control */
15319         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15320         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15321         /* [HGM] time odds: correct new time quota for time odds! */
15322                                             / WhitePlayer()->timeOdds;
15323         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15324     } else {
15325         lastBlack -= blackTimeRemaining;
15326         /* Black made time control */
15327         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15328                                             / WhitePlayer()->other->timeOdds;
15329         lastWhite = whiteTimeRemaining;
15330     }
15331 }
15332
15333 void
15334 DisplayBothClocks()
15335 {
15336     int wom = gameMode == EditPosition ?
15337       !blackPlaysFirst : WhiteOnMove(currentMove);
15338     DisplayWhiteClock(whiteTimeRemaining, wom);
15339     DisplayBlackClock(blackTimeRemaining, !wom);
15340 }
15341
15342
15343 /* Timekeeping seems to be a portability nightmare.  I think everyone
15344    has ftime(), but I'm really not sure, so I'm including some ifdefs
15345    to use other calls if you don't.  Clocks will be less accurate if
15346    you have neither ftime nor gettimeofday.
15347 */
15348
15349 /* VS 2008 requires the #include outside of the function */
15350 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15351 #include <sys/timeb.h>
15352 #endif
15353
15354 /* Get the current time as a TimeMark */
15355 void
15356 GetTimeMark(tm)
15357      TimeMark *tm;
15358 {
15359 #if HAVE_GETTIMEOFDAY
15360
15361     struct timeval timeVal;
15362     struct timezone timeZone;
15363
15364     gettimeofday(&timeVal, &timeZone);
15365     tm->sec = (long) timeVal.tv_sec;
15366     tm->ms = (int) (timeVal.tv_usec / 1000L);
15367
15368 #else /*!HAVE_GETTIMEOFDAY*/
15369 #if HAVE_FTIME
15370
15371 // include <sys/timeb.h> / moved to just above start of function
15372     struct timeb timeB;
15373
15374     ftime(&timeB);
15375     tm->sec = (long) timeB.time;
15376     tm->ms = (int) timeB.millitm;
15377
15378 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15379     tm->sec = (long) time(NULL);
15380     tm->ms = 0;
15381 #endif
15382 #endif
15383 }
15384
15385 /* Return the difference in milliseconds between two
15386    time marks.  We assume the difference will fit in a long!
15387 */
15388 long
15389 SubtractTimeMarks(tm2, tm1)
15390      TimeMark *tm2, *tm1;
15391 {
15392     return 1000L*(tm2->sec - tm1->sec) +
15393            (long) (tm2->ms - tm1->ms);
15394 }
15395
15396
15397 /*
15398  * Code to manage the game clocks.
15399  *
15400  * In tournament play, black starts the clock and then white makes a move.
15401  * We give the human user a slight advantage if he is playing white---the
15402  * clocks don't run until he makes his first move, so it takes zero time.
15403  * Also, we don't account for network lag, so we could get out of sync
15404  * with GNU Chess's clock -- but then, referees are always right.
15405  */
15406
15407 static TimeMark tickStartTM;
15408 static long intendedTickLength;
15409
15410 long
15411 NextTickLength(timeRemaining)
15412      long timeRemaining;
15413 {
15414     long nominalTickLength, nextTickLength;
15415
15416     if (timeRemaining > 0L && timeRemaining <= 10000L)
15417       nominalTickLength = 100L;
15418     else
15419       nominalTickLength = 1000L;
15420     nextTickLength = timeRemaining % nominalTickLength;
15421     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15422
15423     return nextTickLength;
15424 }
15425
15426 /* Adjust clock one minute up or down */
15427 void
15428 AdjustClock(Boolean which, int dir)
15429 {
15430     if(which) blackTimeRemaining += 60000*dir;
15431     else      whiteTimeRemaining += 60000*dir;
15432     DisplayBothClocks();
15433 }
15434
15435 /* Stop clocks and reset to a fresh time control */
15436 void
15437 ResetClocks()
15438 {
15439     (void) StopClockTimer();
15440     if (appData.icsActive) {
15441         whiteTimeRemaining = blackTimeRemaining = 0;
15442     } else if (searchTime) {
15443         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15444         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15445     } else { /* [HGM] correct new time quote for time odds */
15446         whiteTC = blackTC = fullTimeControlString;
15447         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15448         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15449     }
15450     if (whiteFlag || blackFlag) {
15451         DisplayTitle("");
15452         whiteFlag = blackFlag = FALSE;
15453     }
15454     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15455     DisplayBothClocks();
15456 }
15457
15458 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15459
15460 /* Decrement running clock by amount of time that has passed */
15461 void
15462 DecrementClocks()
15463 {
15464     long timeRemaining;
15465     long lastTickLength, fudge;
15466     TimeMark now;
15467
15468     if (!appData.clockMode) return;
15469     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15470
15471     GetTimeMark(&now);
15472
15473     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15474
15475     /* Fudge if we woke up a little too soon */
15476     fudge = intendedTickLength - lastTickLength;
15477     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15478
15479     if (WhiteOnMove(forwardMostMove)) {
15480         if(whiteNPS >= 0) lastTickLength = 0;
15481         timeRemaining = whiteTimeRemaining -= lastTickLength;
15482         if(timeRemaining < 0 && !appData.icsActive) {
15483             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15484             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15485                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15486                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15487             }
15488         }
15489         DisplayWhiteClock(whiteTimeRemaining - fudge,
15490                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15491     } else {
15492         if(blackNPS >= 0) lastTickLength = 0;
15493         timeRemaining = blackTimeRemaining -= lastTickLength;
15494         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15495             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15496             if(suddenDeath) {
15497                 blackStartMove = forwardMostMove;
15498                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15499             }
15500         }
15501         DisplayBlackClock(blackTimeRemaining - fudge,
15502                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15503     }
15504     if (CheckFlags()) return;
15505
15506     tickStartTM = now;
15507     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15508     StartClockTimer(intendedTickLength);
15509
15510     /* if the time remaining has fallen below the alarm threshold, sound the
15511      * alarm. if the alarm has sounded and (due to a takeback or time control
15512      * with increment) the time remaining has increased to a level above the
15513      * threshold, reset the alarm so it can sound again.
15514      */
15515
15516     if (appData.icsActive && appData.icsAlarm) {
15517
15518         /* make sure we are dealing with the user's clock */
15519         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15520                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15521            )) return;
15522
15523         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15524             alarmSounded = FALSE;
15525         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15526             PlayAlarmSound();
15527             alarmSounded = TRUE;
15528         }
15529     }
15530 }
15531
15532
15533 /* A player has just moved, so stop the previously running
15534    clock and (if in clock mode) start the other one.
15535    We redisplay both clocks in case we're in ICS mode, because
15536    ICS gives us an update to both clocks after every move.
15537    Note that this routine is called *after* forwardMostMove
15538    is updated, so the last fractional tick must be subtracted
15539    from the color that is *not* on move now.
15540 */
15541 void
15542 SwitchClocks(int newMoveNr)
15543 {
15544     long lastTickLength;
15545     TimeMark now;
15546     int flagged = FALSE;
15547
15548     GetTimeMark(&now);
15549
15550     if (StopClockTimer() && appData.clockMode) {
15551         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15552         if (!WhiteOnMove(forwardMostMove)) {
15553             if(blackNPS >= 0) lastTickLength = 0;
15554             blackTimeRemaining -= lastTickLength;
15555            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15556 //         if(pvInfoList[forwardMostMove].time == -1)
15557                  pvInfoList[forwardMostMove].time =               // use GUI time
15558                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15559         } else {
15560            if(whiteNPS >= 0) lastTickLength = 0;
15561            whiteTimeRemaining -= lastTickLength;
15562            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15563 //         if(pvInfoList[forwardMostMove].time == -1)
15564                  pvInfoList[forwardMostMove].time =
15565                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15566         }
15567         flagged = CheckFlags();
15568     }
15569     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15570     CheckTimeControl();
15571
15572     if (flagged || !appData.clockMode) return;
15573
15574     switch (gameMode) {
15575       case MachinePlaysBlack:
15576       case MachinePlaysWhite:
15577       case BeginningOfGame:
15578         if (pausing) return;
15579         break;
15580
15581       case EditGame:
15582       case PlayFromGameFile:
15583       case IcsExamining:
15584         return;
15585
15586       default:
15587         break;
15588     }
15589
15590     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15591         if(WhiteOnMove(forwardMostMove))
15592              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15593         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15594     }
15595
15596     tickStartTM = now;
15597     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15598       whiteTimeRemaining : blackTimeRemaining);
15599     StartClockTimer(intendedTickLength);
15600 }
15601
15602
15603 /* Stop both clocks */
15604 void
15605 StopClocks()
15606 {
15607     long lastTickLength;
15608     TimeMark now;
15609
15610     if (!StopClockTimer()) return;
15611     if (!appData.clockMode) return;
15612
15613     GetTimeMark(&now);
15614
15615     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15616     if (WhiteOnMove(forwardMostMove)) {
15617         if(whiteNPS >= 0) lastTickLength = 0;
15618         whiteTimeRemaining -= lastTickLength;
15619         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15620     } else {
15621         if(blackNPS >= 0) lastTickLength = 0;
15622         blackTimeRemaining -= lastTickLength;
15623         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15624     }
15625     CheckFlags();
15626 }
15627
15628 /* Start clock of player on move.  Time may have been reset, so
15629    if clock is already running, stop and restart it. */
15630 void
15631 StartClocks()
15632 {
15633     (void) StopClockTimer(); /* in case it was running already */
15634     DisplayBothClocks();
15635     if (CheckFlags()) return;
15636
15637     if (!appData.clockMode) return;
15638     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15639
15640     GetTimeMark(&tickStartTM);
15641     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15642       whiteTimeRemaining : blackTimeRemaining);
15643
15644    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15645     whiteNPS = blackNPS = -1;
15646     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15647        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15648         whiteNPS = first.nps;
15649     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15650        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15651         blackNPS = first.nps;
15652     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15653         whiteNPS = second.nps;
15654     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15655         blackNPS = second.nps;
15656     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15657
15658     StartClockTimer(intendedTickLength);
15659 }
15660
15661 char *
15662 TimeString(ms)
15663      long ms;
15664 {
15665     long second, minute, hour, day;
15666     char *sign = "";
15667     static char buf[32];
15668
15669     if (ms > 0 && ms <= 9900) {
15670       /* convert milliseconds to tenths, rounding up */
15671       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15672
15673       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15674       return buf;
15675     }
15676
15677     /* convert milliseconds to seconds, rounding up */
15678     /* use floating point to avoid strangeness of integer division
15679        with negative dividends on many machines */
15680     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15681
15682     if (second < 0) {
15683         sign = "-";
15684         second = -second;
15685     }
15686
15687     day = second / (60 * 60 * 24);
15688     second = second % (60 * 60 * 24);
15689     hour = second / (60 * 60);
15690     second = second % (60 * 60);
15691     minute = second / 60;
15692     second = second % 60;
15693
15694     if (day > 0)
15695       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15696               sign, day, hour, minute, second);
15697     else if (hour > 0)
15698       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15699     else
15700       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15701
15702     return buf;
15703 }
15704
15705
15706 /*
15707  * This is necessary because some C libraries aren't ANSI C compliant yet.
15708  */
15709 char *
15710 StrStr(string, match)
15711      char *string, *match;
15712 {
15713     int i, length;
15714
15715     length = strlen(match);
15716
15717     for (i = strlen(string) - length; i >= 0; i--, string++)
15718       if (!strncmp(match, string, length))
15719         return string;
15720
15721     return NULL;
15722 }
15723
15724 char *
15725 StrCaseStr(string, match)
15726      char *string, *match;
15727 {
15728     int i, j, length;
15729
15730     length = strlen(match);
15731
15732     for (i = strlen(string) - length; i >= 0; i--, string++) {
15733         for (j = 0; j < length; j++) {
15734             if (ToLower(match[j]) != ToLower(string[j]))
15735               break;
15736         }
15737         if (j == length) return string;
15738     }
15739
15740     return NULL;
15741 }
15742
15743 #ifndef _amigados
15744 int
15745 StrCaseCmp(s1, s2)
15746      char *s1, *s2;
15747 {
15748     char c1, c2;
15749
15750     for (;;) {
15751         c1 = ToLower(*s1++);
15752         c2 = ToLower(*s2++);
15753         if (c1 > c2) return 1;
15754         if (c1 < c2) return -1;
15755         if (c1 == NULLCHAR) return 0;
15756     }
15757 }
15758
15759
15760 int
15761 ToLower(c)
15762      int c;
15763 {
15764     return isupper(c) ? tolower(c) : c;
15765 }
15766
15767
15768 int
15769 ToUpper(c)
15770      int c;
15771 {
15772     return islower(c) ? toupper(c) : c;
15773 }
15774 #endif /* !_amigados    */
15775
15776 char *
15777 StrSave(s)
15778      char *s;
15779 {
15780   char *ret;
15781
15782   if ((ret = (char *) malloc(strlen(s) + 1)))
15783     {
15784       safeStrCpy(ret, s, strlen(s)+1);
15785     }
15786   return ret;
15787 }
15788
15789 char *
15790 StrSavePtr(s, savePtr)
15791      char *s, **savePtr;
15792 {
15793     if (*savePtr) {
15794         free(*savePtr);
15795     }
15796     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15797       safeStrCpy(*savePtr, s, strlen(s)+1);
15798     }
15799     return(*savePtr);
15800 }
15801
15802 char *
15803 PGNDate()
15804 {
15805     time_t clock;
15806     struct tm *tm;
15807     char buf[MSG_SIZ];
15808
15809     clock = time((time_t *)NULL);
15810     tm = localtime(&clock);
15811     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15812             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15813     return StrSave(buf);
15814 }
15815
15816
15817 char *
15818 PositionToFEN(move, overrideCastling)
15819      int move;
15820      char *overrideCastling;
15821 {
15822     int i, j, fromX, fromY, toX, toY;
15823     int whiteToPlay;
15824     char buf[MSG_SIZ];
15825     char *p, *q;
15826     int emptycount;
15827     ChessSquare piece;
15828
15829     whiteToPlay = (gameMode == EditPosition) ?
15830       !blackPlaysFirst : (move % 2 == 0);
15831     p = buf;
15832
15833     /* Piece placement data */
15834     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15835         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
15836         emptycount = 0;
15837         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15838             if (boards[move][i][j] == EmptySquare) {
15839                 emptycount++;
15840             } else { ChessSquare piece = boards[move][i][j];
15841                 if (emptycount > 0) {
15842                     if(emptycount<10) /* [HGM] can be >= 10 */
15843                         *p++ = '0' + emptycount;
15844                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15845                     emptycount = 0;
15846                 }
15847                 if(PieceToChar(piece) == '+') {
15848                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15849                     *p++ = '+';
15850                     piece = (ChessSquare)(DEMOTED piece);
15851                 }
15852                 *p++ = PieceToChar(piece);
15853                 if(p[-1] == '~') {
15854                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15855                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15856                     *p++ = '~';
15857                 }
15858             }
15859         }
15860         if (emptycount > 0) {
15861             if(emptycount<10) /* [HGM] can be >= 10 */
15862                 *p++ = '0' + emptycount;
15863             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15864             emptycount = 0;
15865         }
15866         *p++ = '/';
15867     }
15868     *(p - 1) = ' ';
15869
15870     /* [HGM] print Crazyhouse or Shogi holdings */
15871     if( gameInfo.holdingsWidth ) {
15872         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15873         q = p;
15874         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15875             piece = boards[move][i][BOARD_WIDTH-1];
15876             if( piece != EmptySquare )
15877               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15878                   *p++ = PieceToChar(piece);
15879         }
15880         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15881             piece = boards[move][BOARD_HEIGHT-i-1][0];
15882             if( piece != EmptySquare )
15883               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15884                   *p++ = PieceToChar(piece);
15885         }
15886
15887         if( q == p ) *p++ = '-';
15888         *p++ = ']';
15889         *p++ = ' ';
15890     }
15891
15892     /* Active color */
15893     *p++ = whiteToPlay ? 'w' : 'b';
15894     *p++ = ' ';
15895
15896   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15897     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15898   } else {
15899   if(nrCastlingRights) {
15900      q = p;
15901      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15902        /* [HGM] write directly from rights */
15903            if(boards[move][CASTLING][2] != NoRights &&
15904               boards[move][CASTLING][0] != NoRights   )
15905                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15906            if(boards[move][CASTLING][2] != NoRights &&
15907               boards[move][CASTLING][1] != NoRights   )
15908                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15909            if(boards[move][CASTLING][5] != NoRights &&
15910               boards[move][CASTLING][3] != NoRights   )
15911                 *p++ = boards[move][CASTLING][3] + AAA;
15912            if(boards[move][CASTLING][5] != NoRights &&
15913               boards[move][CASTLING][4] != NoRights   )
15914                 *p++ = boards[move][CASTLING][4] + AAA;
15915      } else {
15916
15917         /* [HGM] write true castling rights */
15918         if( nrCastlingRights == 6 ) {
15919             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15920                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15921             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15922                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15923             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15924                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15925             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15926                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15927         }
15928      }
15929      if (q == p) *p++ = '-'; /* No castling rights */
15930      *p++ = ' ';
15931   }
15932
15933   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15934      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15935     /* En passant target square */
15936     if (move > backwardMostMove) {
15937         fromX = moveList[move - 1][0] - AAA;
15938         fromY = moveList[move - 1][1] - ONE;
15939         toX = moveList[move - 1][2] - AAA;
15940         toY = moveList[move - 1][3] - ONE;
15941         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15942             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15943             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15944             fromX == toX) {
15945             /* 2-square pawn move just happened */
15946             *p++ = toX + AAA;
15947             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15948         } else {
15949             *p++ = '-';
15950         }
15951     } else if(move == backwardMostMove) {
15952         // [HGM] perhaps we should always do it like this, and forget the above?
15953         if((signed char)boards[move][EP_STATUS] >= 0) {
15954             *p++ = boards[move][EP_STATUS] + AAA;
15955             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15956         } else {
15957             *p++ = '-';
15958         }
15959     } else {
15960         *p++ = '-';
15961     }
15962     *p++ = ' ';
15963   }
15964   }
15965
15966     /* [HGM] find reversible plies */
15967     {   int i = 0, j=move;
15968
15969         if (appData.debugMode) { int k;
15970             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15971             for(k=backwardMostMove; k<=forwardMostMove; k++)
15972                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15973
15974         }
15975
15976         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15977         if( j == backwardMostMove ) i += initialRulePlies;
15978         sprintf(p, "%d ", i);
15979         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15980     }
15981     /* Fullmove number */
15982     sprintf(p, "%d", (move / 2) + 1);
15983
15984     return StrSave(buf);
15985 }
15986
15987 Boolean
15988 ParseFEN(board, blackPlaysFirst, fen)
15989     Board board;
15990      int *blackPlaysFirst;
15991      char *fen;
15992 {
15993     int i, j;
15994     char *p, c;
15995     int emptycount;
15996     ChessSquare piece;
15997
15998     p = fen;
15999
16000     /* [HGM] by default clear Crazyhouse holdings, if present */
16001     if(gameInfo.holdingsWidth) {
16002        for(i=0; i<BOARD_HEIGHT; i++) {
16003            board[i][0]             = EmptySquare; /* black holdings */
16004            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16005            board[i][1]             = (ChessSquare) 0; /* black counts */
16006            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16007        }
16008     }
16009
16010     /* Piece placement data */
16011     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16012         j = 0;
16013         for (;;) {
16014             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16015                 if (*p == '/') p++;
16016                 emptycount = gameInfo.boardWidth - j;
16017                 while (emptycount--)
16018                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16019                 break;
16020 #if(BOARD_FILES >= 10)
16021             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16022                 p++; emptycount=10;
16023                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16024                 while (emptycount--)
16025                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16026 #endif
16027             } else if (isdigit(*p)) {
16028                 emptycount = *p++ - '0';
16029                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16030                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16031                 while (emptycount--)
16032                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16033             } else if (*p == '+' || isalpha(*p)) {
16034                 if (j >= gameInfo.boardWidth) return FALSE;
16035                 if(*p=='+') {
16036                     piece = CharToPiece(*++p);
16037                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16038                     piece = (ChessSquare) (PROMOTED piece ); p++;
16039                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16040                 } else piece = CharToPiece(*p++);
16041
16042                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16043                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16044                     piece = (ChessSquare) (PROMOTED piece);
16045                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16046                     p++;
16047                 }
16048                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16049             } else {
16050                 return FALSE;
16051             }
16052         }
16053     }
16054     while (*p == '/' || *p == ' ') p++;
16055
16056     /* [HGM] look for Crazyhouse holdings here */
16057     while(*p==' ') p++;
16058     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16059         if(*p == '[') p++;
16060         if(*p == '-' ) p++; /* empty holdings */ else {
16061             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16062             /* if we would allow FEN reading to set board size, we would   */
16063             /* have to add holdings and shift the board read so far here   */
16064             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16065                 p++;
16066                 if((int) piece >= (int) BlackPawn ) {
16067                     i = (int)piece - (int)BlackPawn;
16068                     i = PieceToNumber((ChessSquare)i);
16069                     if( i >= gameInfo.holdingsSize ) return FALSE;
16070                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16071                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16072                 } else {
16073                     i = (int)piece - (int)WhitePawn;
16074                     i = PieceToNumber((ChessSquare)i);
16075                     if( i >= gameInfo.holdingsSize ) return FALSE;
16076                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16077                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16078                 }
16079             }
16080         }
16081         if(*p == ']') p++;
16082     }
16083
16084     while(*p == ' ') p++;
16085
16086     /* Active color */
16087     c = *p++;
16088     if(appData.colorNickNames) {
16089       if( c == appData.colorNickNames[0] ) c = 'w'; else
16090       if( c == appData.colorNickNames[1] ) c = 'b';
16091     }
16092     switch (c) {
16093       case 'w':
16094         *blackPlaysFirst = FALSE;
16095         break;
16096       case 'b':
16097         *blackPlaysFirst = TRUE;
16098         break;
16099       default:
16100         return FALSE;
16101     }
16102
16103     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16104     /* return the extra info in global variiables             */
16105
16106     /* set defaults in case FEN is incomplete */
16107     board[EP_STATUS] = EP_UNKNOWN;
16108     for(i=0; i<nrCastlingRights; i++ ) {
16109         board[CASTLING][i] =
16110             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16111     }   /* assume possible unless obviously impossible */
16112     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16113     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16114     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16115                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16116     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16117     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16118     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16119                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16120     FENrulePlies = 0;
16121
16122     while(*p==' ') p++;
16123     if(nrCastlingRights) {
16124       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16125           /* castling indicator present, so default becomes no castlings */
16126           for(i=0; i<nrCastlingRights; i++ ) {
16127                  board[CASTLING][i] = NoRights;
16128           }
16129       }
16130       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16131              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16132              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16133              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16134         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16135
16136         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16137             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16138             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16139         }
16140         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16141             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16142         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16143                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16144         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16145                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16146         switch(c) {
16147           case'K':
16148               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16149               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16150               board[CASTLING][2] = whiteKingFile;
16151               break;
16152           case'Q':
16153               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16154               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16155               board[CASTLING][2] = whiteKingFile;
16156               break;
16157           case'k':
16158               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16159               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16160               board[CASTLING][5] = blackKingFile;
16161               break;
16162           case'q':
16163               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16164               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16165               board[CASTLING][5] = blackKingFile;
16166           case '-':
16167               break;
16168           default: /* FRC castlings */
16169               if(c >= 'a') { /* black rights */
16170                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16171                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16172                   if(i == BOARD_RGHT) break;
16173                   board[CASTLING][5] = i;
16174                   c -= AAA;
16175                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16176                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16177                   if(c > i)
16178                       board[CASTLING][3] = c;
16179                   else
16180                       board[CASTLING][4] = c;
16181               } else { /* white rights */
16182                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16183                     if(board[0][i] == WhiteKing) break;
16184                   if(i == BOARD_RGHT) break;
16185                   board[CASTLING][2] = i;
16186                   c -= AAA - 'a' + 'A';
16187                   if(board[0][c] >= WhiteKing) break;
16188                   if(c > i)
16189                       board[CASTLING][0] = c;
16190                   else
16191                       board[CASTLING][1] = c;
16192               }
16193         }
16194       }
16195       for(i=0; i<nrCastlingRights; i++)
16196         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16197     if (appData.debugMode) {
16198         fprintf(debugFP, "FEN castling rights:");
16199         for(i=0; i<nrCastlingRights; i++)
16200         fprintf(debugFP, " %d", board[CASTLING][i]);
16201         fprintf(debugFP, "\n");
16202     }
16203
16204       while(*p==' ') p++;
16205     }
16206
16207     /* read e.p. field in games that know e.p. capture */
16208     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16209        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16210       if(*p=='-') {
16211         p++; board[EP_STATUS] = EP_NONE;
16212       } else {
16213          char c = *p++ - AAA;
16214
16215          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16216          if(*p >= '0' && *p <='9') p++;
16217          board[EP_STATUS] = c;
16218       }
16219     }
16220
16221
16222     if(sscanf(p, "%d", &i) == 1) {
16223         FENrulePlies = i; /* 50-move ply counter */
16224         /* (The move number is still ignored)    */
16225     }
16226
16227     return TRUE;
16228 }
16229
16230 void
16231 EditPositionPasteFEN(char *fen)
16232 {
16233   if (fen != NULL) {
16234     Board initial_position;
16235
16236     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16237       DisplayError(_("Bad FEN position in clipboard"), 0);
16238       return ;
16239     } else {
16240       int savedBlackPlaysFirst = blackPlaysFirst;
16241       EditPositionEvent();
16242       blackPlaysFirst = savedBlackPlaysFirst;
16243       CopyBoard(boards[0], initial_position);
16244       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16245       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16246       DisplayBothClocks();
16247       DrawPosition(FALSE, boards[currentMove]);
16248     }
16249   }
16250 }
16251
16252 static char cseq[12] = "\\   ";
16253
16254 Boolean set_cont_sequence(char *new_seq)
16255 {
16256     int len;
16257     Boolean ret;
16258
16259     // handle bad attempts to set the sequence
16260         if (!new_seq)
16261                 return 0; // acceptable error - no debug
16262
16263     len = strlen(new_seq);
16264     ret = (len > 0) && (len < sizeof(cseq));
16265     if (ret)
16266       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16267     else if (appData.debugMode)
16268       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16269     return ret;
16270 }
16271
16272 /*
16273     reformat a source message so words don't cross the width boundary.  internal
16274     newlines are not removed.  returns the wrapped size (no null character unless
16275     included in source message).  If dest is NULL, only calculate the size required
16276     for the dest buffer.  lp argument indicats line position upon entry, and it's
16277     passed back upon exit.
16278 */
16279 int wrap(char *dest, char *src, int count, int width, int *lp)
16280 {
16281     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16282
16283     cseq_len = strlen(cseq);
16284     old_line = line = *lp;
16285     ansi = len = clen = 0;
16286
16287     for (i=0; i < count; i++)
16288     {
16289         if (src[i] == '\033')
16290             ansi = 1;
16291
16292         // if we hit the width, back up
16293         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16294         {
16295             // store i & len in case the word is too long
16296             old_i = i, old_len = len;
16297
16298             // find the end of the last word
16299             while (i && src[i] != ' ' && src[i] != '\n')
16300             {
16301                 i--;
16302                 len--;
16303             }
16304
16305             // word too long?  restore i & len before splitting it
16306             if ((old_i-i+clen) >= width)
16307             {
16308                 i = old_i;
16309                 len = old_len;
16310             }
16311
16312             // extra space?
16313             if (i && src[i-1] == ' ')
16314                 len--;
16315
16316             if (src[i] != ' ' && src[i] != '\n')
16317             {
16318                 i--;
16319                 if (len)
16320                     len--;
16321             }
16322
16323             // now append the newline and continuation sequence
16324             if (dest)
16325                 dest[len] = '\n';
16326             len++;
16327             if (dest)
16328                 strncpy(dest+len, cseq, cseq_len);
16329             len += cseq_len;
16330             line = cseq_len;
16331             clen = cseq_len;
16332             continue;
16333         }
16334
16335         if (dest)
16336             dest[len] = src[i];
16337         len++;
16338         if (!ansi)
16339             line++;
16340         if (src[i] == '\n')
16341             line = 0;
16342         if (src[i] == 'm')
16343             ansi = 0;
16344     }
16345     if (dest && appData.debugMode)
16346     {
16347         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16348             count, width, line, len, *lp);
16349         show_bytes(debugFP, src, count);
16350         fprintf(debugFP, "\ndest: ");
16351         show_bytes(debugFP, dest, len);
16352         fprintf(debugFP, "\n");
16353     }
16354     *lp = dest ? line : old_line;
16355
16356     return len;
16357 }
16358
16359 // [HGM] vari: routines for shelving variations
16360
16361 void
16362 PushInner(int firstMove, int lastMove)
16363 {
16364         int i, j, nrMoves = lastMove - firstMove;
16365
16366         // push current tail of game on stack
16367         savedResult[storedGames] = gameInfo.result;
16368         savedDetails[storedGames] = gameInfo.resultDetails;
16369         gameInfo.resultDetails = NULL;
16370         savedFirst[storedGames] = firstMove;
16371         savedLast [storedGames] = lastMove;
16372         savedFramePtr[storedGames] = framePtr;
16373         framePtr -= nrMoves; // reserve space for the boards
16374         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16375             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16376             for(j=0; j<MOVE_LEN; j++)
16377                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16378             for(j=0; j<2*MOVE_LEN; j++)
16379                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16380             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16381             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16382             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16383             pvInfoList[firstMove+i-1].depth = 0;
16384             commentList[framePtr+i] = commentList[firstMove+i];
16385             commentList[firstMove+i] = NULL;
16386         }
16387
16388         storedGames++;
16389         forwardMostMove = firstMove; // truncate game so we can start variation
16390 }
16391
16392 void
16393 PushTail(int firstMove, int lastMove)
16394 {
16395         if(appData.icsActive) { // only in local mode
16396                 forwardMostMove = currentMove; // mimic old ICS behavior
16397                 return;
16398         }
16399         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16400
16401         PushInner(firstMove, lastMove);
16402         if(storedGames == 1) GreyRevert(FALSE);
16403 }
16404
16405 void
16406 PopInner(Boolean annotate)
16407 {
16408         int i, j, nrMoves;
16409         char buf[8000], moveBuf[20];
16410
16411         storedGames--;
16412         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16413         nrMoves = savedLast[storedGames] - currentMove;
16414         if(annotate) {
16415                 int cnt = 10;
16416                 if(!WhiteOnMove(currentMove))
16417                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16418                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16419                 for(i=currentMove; i<forwardMostMove; i++) {
16420                         if(WhiteOnMove(i))
16421                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16422                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16423                         strcat(buf, moveBuf);
16424                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16425                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16426                 }
16427                 strcat(buf, ")");
16428         }
16429         for(i=1; i<=nrMoves; i++) { // copy last variation back
16430             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16431             for(j=0; j<MOVE_LEN; j++)
16432                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16433             for(j=0; j<2*MOVE_LEN; j++)
16434                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16435             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16436             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16437             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16438             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16439             commentList[currentMove+i] = commentList[framePtr+i];
16440             commentList[framePtr+i] = NULL;
16441         }
16442         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16443         framePtr = savedFramePtr[storedGames];
16444         gameInfo.result = savedResult[storedGames];
16445         if(gameInfo.resultDetails != NULL) {
16446             free(gameInfo.resultDetails);
16447       }
16448         gameInfo.resultDetails = savedDetails[storedGames];
16449         forwardMostMove = currentMove + nrMoves;
16450 }
16451
16452 Boolean
16453 PopTail(Boolean annotate)
16454 {
16455         if(appData.icsActive) return FALSE; // only in local mode
16456         if(!storedGames) return FALSE; // sanity
16457         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16458
16459         PopInner(annotate);
16460
16461         if(storedGames == 0) GreyRevert(TRUE);
16462         return TRUE;
16463 }
16464
16465 void
16466 CleanupTail()
16467 {       // remove all shelved variations
16468         int i;
16469         for(i=0; i<storedGames; i++) {
16470             if(savedDetails[i])
16471                 free(savedDetails[i]);
16472             savedDetails[i] = NULL;
16473         }
16474         for(i=framePtr; i<MAX_MOVES; i++) {
16475                 if(commentList[i]) free(commentList[i]);
16476                 commentList[i] = NULL;
16477         }
16478         framePtr = MAX_MOVES-1;
16479         storedGames = 0;
16480 }
16481
16482 void
16483 LoadVariation(int index, char *text)
16484 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16485         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16486         int level = 0, move;
16487
16488         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16489         // first find outermost bracketing variation
16490         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16491             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16492                 if(*p == '{') wait = '}'; else
16493                 if(*p == '[') wait = ']'; else
16494                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16495                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16496             }
16497             if(*p == wait) wait = NULLCHAR; // closing ]} found
16498             p++;
16499         }
16500         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16501         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16502         end[1] = NULLCHAR; // clip off comment beyond variation
16503         ToNrEvent(currentMove-1);
16504         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16505         // kludge: use ParsePV() to append variation to game
16506         move = currentMove;
16507         ParsePV(start, TRUE, TRUE);
16508         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16509         ClearPremoveHighlights();
16510         CommentPopDown();
16511         ToNrEvent(currentMove+1);
16512 }
16513