Implement Grand Chess
[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         snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4903                                              moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4904         SendToProgram(buf, cps);
4905       }
4906       else SendToProgram(moveList[moveNum], cps);
4907       /* End of additions by Tord */
4908     }
4909
4910     /* [HGM] setting up the opening has brought engine in force mode! */
4911     /*       Send 'go' if we are in a mode where machine should play. */
4912     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4913         (gameMode == TwoMachinesPlay   ||
4914 #if ZIPPY
4915          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4916 #endif
4917          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4918         SendToProgram("go\n", cps);
4919   if (appData.debugMode) {
4920     fprintf(debugFP, "(extra)\n");
4921   }
4922     }
4923     setboardSpoiledMachineBlack = 0;
4924 }
4925
4926 void
4927 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4928      ChessMove moveType;
4929      int fromX, fromY, toX, toY;
4930      char promoChar;
4931 {
4932     char user_move[MSG_SIZ];
4933
4934     switch (moveType) {
4935       default:
4936         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4937                 (int)moveType, fromX, fromY, toX, toY);
4938         DisplayError(user_move + strlen("say "), 0);
4939         break;
4940       case WhiteKingSideCastle:
4941       case BlackKingSideCastle:
4942       case WhiteQueenSideCastleWild:
4943       case BlackQueenSideCastleWild:
4944       /* PUSH Fabien */
4945       case WhiteHSideCastleFR:
4946       case BlackHSideCastleFR:
4947       /* POP Fabien */
4948         snprintf(user_move, MSG_SIZ, "o-o\n");
4949         break;
4950       case WhiteQueenSideCastle:
4951       case BlackQueenSideCastle:
4952       case WhiteKingSideCastleWild:
4953       case BlackKingSideCastleWild:
4954       /* PUSH Fabien */
4955       case WhiteASideCastleFR:
4956       case BlackASideCastleFR:
4957       /* POP Fabien */
4958         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4959         break;
4960       case WhiteNonPromotion:
4961       case BlackNonPromotion:
4962         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4963         break;
4964       case WhitePromotion:
4965       case BlackPromotion:
4966         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4967           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4968                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4969                 PieceToChar(WhiteFerz));
4970         else if(gameInfo.variant == VariantGreat)
4971           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4972                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4973                 PieceToChar(WhiteMan));
4974         else
4975           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4976                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4977                 promoChar);
4978         break;
4979       case WhiteDrop:
4980       case BlackDrop:
4981       drop:
4982         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4983                  ToUpper(PieceToChar((ChessSquare) fromX)),
4984                  AAA + toX, ONE + toY);
4985         break;
4986       case IllegalMove:  /* could be a variant we don't quite understand */
4987         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4988       case NormalMove:
4989       case WhiteCapturesEnPassant:
4990       case BlackCapturesEnPassant:
4991         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4992                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4993         break;
4994     }
4995     SendToICS(user_move);
4996     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4997         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4998 }
4999
5000 void
5001 UploadGameEvent()
5002 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5003     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5004     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5005     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5006         DisplayError("You cannot do this while you are playing or observing", 0);
5007         return;
5008     }
5009     if(gameMode != IcsExamining) { // is this ever not the case?
5010         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5011
5012         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5013           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5014         } else { // on FICS we must first go to general examine mode
5015           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5016         }
5017         if(gameInfo.variant != VariantNormal) {
5018             // try figure out wild number, as xboard names are not always valid on ICS
5019             for(i=1; i<=36; i++) {
5020               snprintf(buf, MSG_SIZ, "wild/%d", i);
5021                 if(StringToVariant(buf) == gameInfo.variant) break;
5022             }
5023             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5024             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5025             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5026         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5027         SendToICS(ics_prefix);
5028         SendToICS(buf);
5029         if(startedFromSetupPosition || backwardMostMove != 0) {
5030           fen = PositionToFEN(backwardMostMove, NULL);
5031           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5032             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5033             SendToICS(buf);
5034           } else { // FICS: everything has to set by separate bsetup commands
5035             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5036             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5037             SendToICS(buf);
5038             if(!WhiteOnMove(backwardMostMove)) {
5039                 SendToICS("bsetup tomove black\n");
5040             }
5041             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5042             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5043             SendToICS(buf);
5044             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5045             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5046             SendToICS(buf);
5047             i = boards[backwardMostMove][EP_STATUS];
5048             if(i >= 0) { // set e.p.
5049               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5050                 SendToICS(buf);
5051             }
5052             bsetup++;
5053           }
5054         }
5055       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5056             SendToICS("bsetup done\n"); // switch to normal examining.
5057     }
5058     for(i = backwardMostMove; i<last; i++) {
5059         char buf[20];
5060         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5061         SendToICS(buf);
5062     }
5063     SendToICS(ics_prefix);
5064     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5065 }
5066
5067 void
5068 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5069      int rf, ff, rt, ft;
5070      char promoChar;
5071      char move[7];
5072 {
5073     if (rf == DROP_RANK) {
5074       sprintf(move, "%c@%c%c\n",
5075                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5076     } else {
5077         if (promoChar == 'x' || promoChar == NULLCHAR) {
5078           sprintf(move, "%c%c%c%c\n",
5079                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5080         } else {
5081             sprintf(move, "%c%c%c%c%c\n",
5082                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5083         }
5084     }
5085 }
5086
5087 void
5088 ProcessICSInitScript(f)
5089      FILE *f;
5090 {
5091     char buf[MSG_SIZ];
5092
5093     while (fgets(buf, MSG_SIZ, f)) {
5094         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5095     }
5096
5097     fclose(f);
5098 }
5099
5100
5101 static int lastX, lastY, selectFlag, dragging;
5102
5103 void
5104 Sweep(int step)
5105 {
5106     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5107     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5108     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5109     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5110     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5111     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5112     do {
5113         promoSweep -= step;
5114         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5115         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5116         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5117         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5118         if(!step) step = 1;
5119     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5120             appData.testLegality && (promoSweep == king ||
5121             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5122     ChangeDragPiece(promoSweep);
5123 }
5124
5125 int PromoScroll(int x, int y)
5126 {
5127   int step = 0;
5128
5129   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5130   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5131   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5132   if(!step) return FALSE;
5133   lastX = x; lastY = y;
5134   if((promoSweep < BlackPawn) == flipView) step = -step;
5135   if(step > 0) selectFlag = 1;
5136   if(!selectFlag) Sweep(step);
5137   return FALSE;
5138 }
5139
5140 void
5141 NextPiece(int step)
5142 {
5143     ChessSquare piece = boards[currentMove][toY][toX];
5144     do {
5145         pieceSweep -= step;
5146         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5147         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5148         if(!step) step = -1;
5149     } while(PieceToChar(pieceSweep) == '.');
5150     boards[currentMove][toY][toX] = pieceSweep;
5151     DrawPosition(FALSE, boards[currentMove]);
5152     boards[currentMove][toY][toX] = piece;
5153 }
5154 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5155 void
5156 AlphaRank(char *move, int n)
5157 {
5158 //    char *p = move, c; int x, y;
5159
5160     if (appData.debugMode) {
5161         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5162     }
5163
5164     if(move[1]=='*' &&
5165        move[2]>='0' && move[2]<='9' &&
5166        move[3]>='a' && move[3]<='x'    ) {
5167         move[1] = '@';
5168         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5169         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5170     } else
5171     if(move[0]>='0' && move[0]<='9' &&
5172        move[1]>='a' && move[1]<='x' &&
5173        move[2]>='0' && move[2]<='9' &&
5174        move[3]>='a' && move[3]<='x'    ) {
5175         /* input move, Shogi -> normal */
5176         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5177         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5178         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5179         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5180     } else
5181     if(move[1]=='@' &&
5182        move[3]>='0' && move[3]<='9' &&
5183        move[2]>='a' && move[2]<='x'    ) {
5184         move[1] = '*';
5185         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5186         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5187     } else
5188     if(
5189        move[0]>='a' && move[0]<='x' &&
5190        move[3]>='0' && move[3]<='9' &&
5191        move[2]>='a' && move[2]<='x'    ) {
5192          /* output move, normal -> Shogi */
5193         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5194         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5195         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5196         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5197         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5198     }
5199     if (appData.debugMode) {
5200         fprintf(debugFP, "   out = '%s'\n", move);
5201     }
5202 }
5203
5204 char yy_textstr[8000];
5205
5206 /* Parser for moves from gnuchess, ICS, or user typein box */
5207 Boolean
5208 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5209      char *move;
5210      int moveNum;
5211      ChessMove *moveType;
5212      int *fromX, *fromY, *toX, *toY;
5213      char *promoChar;
5214 {
5215     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5216
5217     switch (*moveType) {
5218       case WhitePromotion:
5219       case BlackPromotion:
5220       case WhiteNonPromotion:
5221       case BlackNonPromotion:
5222       case NormalMove:
5223       case WhiteCapturesEnPassant:
5224       case BlackCapturesEnPassant:
5225       case WhiteKingSideCastle:
5226       case WhiteQueenSideCastle:
5227       case BlackKingSideCastle:
5228       case BlackQueenSideCastle:
5229       case WhiteKingSideCastleWild:
5230       case WhiteQueenSideCastleWild:
5231       case BlackKingSideCastleWild:
5232       case BlackQueenSideCastleWild:
5233       /* Code added by Tord: */
5234       case WhiteHSideCastleFR:
5235       case WhiteASideCastleFR:
5236       case BlackHSideCastleFR:
5237       case BlackASideCastleFR:
5238       /* End of code added by Tord */
5239       case IllegalMove:         /* bug or odd chess variant */
5240         *fromX = currentMoveString[0] - AAA;
5241         *fromY = currentMoveString[1] - ONE;
5242         *toX = currentMoveString[2] - AAA;
5243         *toY = currentMoveString[3] - ONE;
5244         *promoChar = currentMoveString[4];
5245         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5246             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5247     if (appData.debugMode) {
5248         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5249     }
5250             *fromX = *fromY = *toX = *toY = 0;
5251             return FALSE;
5252         }
5253         if (appData.testLegality) {
5254           return (*moveType != IllegalMove);
5255         } else {
5256           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5257                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5258         }
5259
5260       case WhiteDrop:
5261       case BlackDrop:
5262         *fromX = *moveType == WhiteDrop ?
5263           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5264           (int) CharToPiece(ToLower(currentMoveString[0]));
5265         *fromY = DROP_RANK;
5266         *toX = currentMoveString[2] - AAA;
5267         *toY = currentMoveString[3] - ONE;
5268         *promoChar = NULLCHAR;
5269         return TRUE;
5270
5271       case AmbiguousMove:
5272       case ImpossibleMove:
5273       case EndOfFile:
5274       case ElapsedTime:
5275       case Comment:
5276       case PGNTag:
5277       case NAG:
5278       case WhiteWins:
5279       case BlackWins:
5280       case GameIsDrawn:
5281       default:
5282     if (appData.debugMode) {
5283         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5284     }
5285         /* bug? */
5286         *fromX = *fromY = *toX = *toY = 0;
5287         *promoChar = NULLCHAR;
5288         return FALSE;
5289     }
5290 }
5291
5292 Boolean pushed = FALSE;
5293 char *lastParseAttempt;
5294
5295 void
5296 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5297 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5298   int fromX, fromY, toX, toY; char promoChar;
5299   ChessMove moveType;
5300   Boolean valid;
5301   int nr = 0;
5302
5303   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5304     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5305     pushed = TRUE;
5306   }
5307   endPV = forwardMostMove;
5308   do {
5309     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5310     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5311     lastParseAttempt = pv;
5312     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5313 if(appData.debugMode){
5314 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);
5315 }
5316     if(!valid && nr == 0 &&
5317        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5318         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5319         // Hande case where played move is different from leading PV move
5320         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5321         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5322         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5323         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5324           endPV += 2; // if position different, keep this
5325           moveList[endPV-1][0] = fromX + AAA;
5326           moveList[endPV-1][1] = fromY + ONE;
5327           moveList[endPV-1][2] = toX + AAA;
5328           moveList[endPV-1][3] = toY + ONE;
5329           parseList[endPV-1][0] = NULLCHAR;
5330           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5331         }
5332       }
5333     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5334     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5335     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5336     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5337         valid++; // allow comments in PV
5338         continue;
5339     }
5340     nr++;
5341     if(endPV+1 > framePtr) break; // no space, truncate
5342     if(!valid) break;
5343     endPV++;
5344     CopyBoard(boards[endPV], boards[endPV-1]);
5345     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5346     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5347     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5348     CoordsToAlgebraic(boards[endPV - 1],
5349                              PosFlags(endPV - 1),
5350                              fromY, fromX, toY, toX, promoChar,
5351                              parseList[endPV - 1]);
5352   } while(valid);
5353   if(atEnd == 2) return; // used hidden, for PV conversion
5354   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5355   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5356   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5357                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5358   DrawPosition(TRUE, boards[currentMove]);
5359 }
5360
5361 int
5362 MultiPV(ChessProgramState *cps)
5363 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5364         int i;
5365         for(i=0; i<cps->nrOptions; i++)
5366             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5367                 return i;
5368         return -1;
5369 }
5370
5371 Boolean
5372 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5373 {
5374         int startPV, multi, lineStart, origIndex = index;
5375         char *p, buf2[MSG_SIZ];
5376
5377         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5378         lastX = x; lastY = y;
5379         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5380         lineStart = startPV = index;
5381         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5382         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5383         index = startPV;
5384         do{ while(buf[index] && buf[index] != '\n') index++;
5385         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5386         buf[index] = 0;
5387         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5388                 int n = first.option[multi].value;
5389                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5390                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5391                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5392                 first.option[multi].value = n;
5393                 *start = *end = 0;
5394                 return FALSE;
5395         }
5396         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5397         *start = startPV; *end = index-1;
5398         return TRUE;
5399 }
5400
5401 char *
5402 PvToSAN(char *pv)
5403 {
5404         static char buf[10*MSG_SIZ];
5405         int i, k=0, savedEnd=endPV;
5406         *buf = NULLCHAR;
5407         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5408         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5409         for(i = forwardMostMove; i<endPV; i++){
5410             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5411             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5412             k += strlen(buf+k);
5413         }
5414         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5415         if(forwardMostMove < savedEnd) PopInner(0);
5416         endPV = savedEnd;
5417         return buf;
5418 }
5419
5420 Boolean
5421 LoadPV(int x, int y)
5422 { // called on right mouse click to load PV
5423   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5424   lastX = x; lastY = y;
5425   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5426   return TRUE;
5427 }
5428
5429 void
5430 UnLoadPV()
5431 {
5432   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5433   if(endPV < 0) return;
5434   endPV = -1;
5435   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5436         Boolean saveAnimate = appData.animate;
5437         if(pushed) {
5438             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5439                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5440             } else storedGames--; // abandon shelved tail of original game
5441         }
5442         pushed = FALSE;
5443         forwardMostMove = currentMove;
5444         currentMove = oldFMM;
5445         appData.animate = FALSE;
5446         ToNrEvent(forwardMostMove);
5447         appData.animate = saveAnimate;
5448   }
5449   currentMove = forwardMostMove;
5450   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5451   ClearPremoveHighlights();
5452   DrawPosition(TRUE, boards[currentMove]);
5453 }
5454
5455 void
5456 MovePV(int x, int y, int h)
5457 { // step through PV based on mouse coordinates (called on mouse move)
5458   int margin = h>>3, step = 0;
5459
5460   // we must somehow check if right button is still down (might be released off board!)
5461   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5462   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5463   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5464   if(!step) return;
5465   lastX = x; lastY = y;
5466
5467   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5468   if(endPV < 0) return;
5469   if(y < margin) step = 1; else
5470   if(y > h - margin) step = -1;
5471   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5472   currentMove += step;
5473   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5474   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5475                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5476   DrawPosition(FALSE, boards[currentMove]);
5477 }
5478
5479
5480 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5481 // All positions will have equal probability, but the current method will not provide a unique
5482 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5483 #define DARK 1
5484 #define LITE 2
5485 #define ANY 3
5486
5487 int squaresLeft[4];
5488 int piecesLeft[(int)BlackPawn];
5489 int seed, nrOfShuffles;
5490
5491 void GetPositionNumber()
5492 {       // sets global variable seed
5493         int i;
5494
5495         seed = appData.defaultFrcPosition;
5496         if(seed < 0) { // randomize based on time for negative FRC position numbers
5497                 for(i=0; i<50; i++) seed += random();
5498                 seed = random() ^ random() >> 8 ^ random() << 8;
5499                 if(seed<0) seed = -seed;
5500         }
5501 }
5502
5503 int put(Board board, int pieceType, int rank, int n, int shade)
5504 // put the piece on the (n-1)-th empty squares of the given shade
5505 {
5506         int i;
5507
5508         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5509                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5510                         board[rank][i] = (ChessSquare) pieceType;
5511                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5512                         squaresLeft[ANY]--;
5513                         piecesLeft[pieceType]--;
5514                         return i;
5515                 }
5516         }
5517         return -1;
5518 }
5519
5520
5521 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5522 // calculate where the next piece goes, (any empty square), and put it there
5523 {
5524         int i;
5525
5526         i = seed % squaresLeft[shade];
5527         nrOfShuffles *= squaresLeft[shade];
5528         seed /= squaresLeft[shade];
5529         put(board, pieceType, rank, i, shade);
5530 }
5531
5532 void AddTwoPieces(Board board, int pieceType, int rank)
5533 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5534 {
5535         int i, n=squaresLeft[ANY], j=n-1, k;
5536
5537         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5538         i = seed % k;  // pick one
5539         nrOfShuffles *= k;
5540         seed /= k;
5541         while(i >= j) i -= j--;
5542         j = n - 1 - j; i += j;
5543         put(board, pieceType, rank, j, ANY);
5544         put(board, pieceType, rank, i, ANY);
5545 }
5546
5547 void SetUpShuffle(Board board, int number)
5548 {
5549         int i, p, first=1;
5550
5551         GetPositionNumber(); nrOfShuffles = 1;
5552
5553         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5554         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5555         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5556
5557         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5558
5559         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5560             p = (int) board[0][i];
5561             if(p < (int) BlackPawn) piecesLeft[p] ++;
5562             board[0][i] = EmptySquare;
5563         }
5564
5565         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5566             // shuffles restricted to allow normal castling put KRR first
5567             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5568                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5569             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5570                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5571             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5572                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5573             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5574                 put(board, WhiteRook, 0, 0, ANY);
5575             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5576         }
5577
5578         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5579             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5580             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5581                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5582                 while(piecesLeft[p] >= 2) {
5583                     AddOnePiece(board, p, 0, LITE);
5584                     AddOnePiece(board, p, 0, DARK);
5585                 }
5586                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5587             }
5588
5589         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5590             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5591             // but we leave King and Rooks for last, to possibly obey FRC restriction
5592             if(p == (int)WhiteRook) continue;
5593             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5594             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5595         }
5596
5597         // now everything is placed, except perhaps King (Unicorn) and Rooks
5598
5599         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5600             // Last King gets castling rights
5601             while(piecesLeft[(int)WhiteUnicorn]) {
5602                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5603                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5604             }
5605
5606             while(piecesLeft[(int)WhiteKing]) {
5607                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5608                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5609             }
5610
5611
5612         } else {
5613             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5614             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5615         }
5616
5617         // Only Rooks can be left; simply place them all
5618         while(piecesLeft[(int)WhiteRook]) {
5619                 i = put(board, WhiteRook, 0, 0, ANY);
5620                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5621                         if(first) {
5622                                 first=0;
5623                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5624                         }
5625                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5626                 }
5627         }
5628         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5629             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5630         }
5631
5632         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5633 }
5634
5635 int SetCharTable( char *table, const char * map )
5636 /* [HGM] moved here from winboard.c because of its general usefulness */
5637 /*       Basically a safe strcpy that uses the last character as King */
5638 {
5639     int result = FALSE; int NrPieces;
5640
5641     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5642                     && NrPieces >= 12 && !(NrPieces&1)) {
5643         int i; /* [HGM] Accept even length from 12 to 34 */
5644
5645         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5646         for( i=0; i<NrPieces/2-1; i++ ) {
5647             table[i] = map[i];
5648             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5649         }
5650         table[(int) WhiteKing]  = map[NrPieces/2-1];
5651         table[(int) BlackKing]  = map[NrPieces-1];
5652
5653         result = TRUE;
5654     }
5655
5656     return result;
5657 }
5658
5659 void Prelude(Board board)
5660 {       // [HGM] superchess: random selection of exo-pieces
5661         int i, j, k; ChessSquare p;
5662         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5663
5664         GetPositionNumber(); // use FRC position number
5665
5666         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5667             SetCharTable(pieceToChar, appData.pieceToCharTable);
5668             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5669                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5670         }
5671
5672         j = seed%4;                 seed /= 4;
5673         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5674         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5675         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5676         j = seed%3 + (seed%3 >= j); seed /= 3;
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;
5681         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5689         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5690         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5691         put(board, exoPieces[0],    0, 0, ANY);
5692         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5693 }
5694
5695 void
5696 InitPosition(redraw)
5697      int redraw;
5698 {
5699     ChessSquare (* pieces)[BOARD_FILES];
5700     int i, j, pawnRow, overrule,
5701     oldx = gameInfo.boardWidth,
5702     oldy = gameInfo.boardHeight,
5703     oldh = gameInfo.holdingsWidth;
5704     static int oldv;
5705
5706     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5707
5708     /* [AS] Initialize pv info list [HGM] and game status */
5709     {
5710         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5711             pvInfoList[i].depth = 0;
5712             boards[i][EP_STATUS] = EP_NONE;
5713             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5714         }
5715
5716         initialRulePlies = 0; /* 50-move counter start */
5717
5718         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5719         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5720     }
5721
5722
5723     /* [HGM] logic here is completely changed. In stead of full positions */
5724     /* the initialized data only consist of the two backranks. The switch */
5725     /* selects which one we will use, which is than copied to the Board   */
5726     /* initialPosition, which for the rest is initialized by Pawns and    */
5727     /* empty squares. This initial position is then copied to boards[0],  */
5728     /* possibly after shuffling, so that it remains available.            */
5729
5730     gameInfo.holdingsWidth = 0; /* default board sizes */
5731     gameInfo.boardWidth    = 8;
5732     gameInfo.boardHeight   = 8;
5733     gameInfo.holdingsSize  = 0;
5734     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5735     for(i=0; i<BOARD_FILES-2; i++)
5736       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5737     initialPosition[EP_STATUS] = EP_NONE;
5738     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5739     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5740          SetCharTable(pieceNickName, appData.pieceNickNames);
5741     else SetCharTable(pieceNickName, "............");
5742     pieces = FIDEArray;
5743
5744     switch (gameInfo.variant) {
5745     case VariantFischeRandom:
5746       shuffleOpenings = TRUE;
5747     default:
5748       break;
5749     case VariantShatranj:
5750       pieces = ShatranjArray;
5751       nrCastlingRights = 0;
5752       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5753       break;
5754     case VariantMakruk:
5755       pieces = makrukArray;
5756       nrCastlingRights = 0;
5757       startedFromSetupPosition = TRUE;
5758       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5759       break;
5760     case VariantTwoKings:
5761       pieces = twoKingsArray;
5762       break;
5763     case VariantGrand:
5764       pieces = GrandArray;
5765       nrCastlingRights = 0;
5766       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5767       gameInfo.boardWidth = 10;
5768       gameInfo.boardHeight = 10;
5769       gameInfo.holdingsSize = 7;
5770       break;
5771     case VariantCapaRandom:
5772       shuffleOpenings = TRUE;
5773     case VariantCapablanca:
5774       pieces = CapablancaArray;
5775       gameInfo.boardWidth = 10;
5776       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5777       break;
5778     case VariantGothic:
5779       pieces = GothicArray;
5780       gameInfo.boardWidth = 10;
5781       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5782       break;
5783     case VariantSChess:
5784       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5785       gameInfo.holdingsSize = 7;
5786       break;
5787     case VariantJanus:
5788       pieces = JanusArray;
5789       gameInfo.boardWidth = 10;
5790       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5791       nrCastlingRights = 6;
5792         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5793         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5794         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5795         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5796         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5797         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5798       break;
5799     case VariantFalcon:
5800       pieces = FalconArray;
5801       gameInfo.boardWidth = 10;
5802       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5803       break;
5804     case VariantXiangqi:
5805       pieces = XiangqiArray;
5806       gameInfo.boardWidth  = 9;
5807       gameInfo.boardHeight = 10;
5808       nrCastlingRights = 0;
5809       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5810       break;
5811     case VariantShogi:
5812       pieces = ShogiArray;
5813       gameInfo.boardWidth  = 9;
5814       gameInfo.boardHeight = 9;
5815       gameInfo.holdingsSize = 7;
5816       nrCastlingRights = 0;
5817       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5818       break;
5819     case VariantCourier:
5820       pieces = CourierArray;
5821       gameInfo.boardWidth  = 12;
5822       nrCastlingRights = 0;
5823       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5824       break;
5825     case VariantKnightmate:
5826       pieces = KnightmateArray;
5827       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5828       break;
5829     case VariantSpartan:
5830       pieces = SpartanArray;
5831       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5832       break;
5833     case VariantFairy:
5834       pieces = fairyArray;
5835       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5836       break;
5837     case VariantGreat:
5838       pieces = GreatArray;
5839       gameInfo.boardWidth = 10;
5840       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5841       gameInfo.holdingsSize = 8;
5842       break;
5843     case VariantSuper:
5844       pieces = FIDEArray;
5845       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5846       gameInfo.holdingsSize = 8;
5847       startedFromSetupPosition = TRUE;
5848       break;
5849     case VariantCrazyhouse:
5850     case VariantBughouse:
5851       pieces = FIDEArray;
5852       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5853       gameInfo.holdingsSize = 5;
5854       break;
5855     case VariantWildCastle:
5856       pieces = FIDEArray;
5857       /* !!?shuffle with kings guaranteed to be on d or e file */
5858       shuffleOpenings = 1;
5859       break;
5860     case VariantNoCastle:
5861       pieces = FIDEArray;
5862       nrCastlingRights = 0;
5863       /* !!?unconstrained back-rank shuffle */
5864       shuffleOpenings = 1;
5865       break;
5866     }
5867
5868     overrule = 0;
5869     if(appData.NrFiles >= 0) {
5870         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5871         gameInfo.boardWidth = appData.NrFiles;
5872     }
5873     if(appData.NrRanks >= 0) {
5874         gameInfo.boardHeight = appData.NrRanks;
5875     }
5876     if(appData.holdingsSize >= 0) {
5877         i = appData.holdingsSize;
5878         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5879         gameInfo.holdingsSize = i;
5880     }
5881     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5882     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5883         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5884
5885     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5886     if(pawnRow < 1) pawnRow = 1;
5887     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5888
5889     /* User pieceToChar list overrules defaults */
5890     if(appData.pieceToCharTable != NULL)
5891         SetCharTable(pieceToChar, appData.pieceToCharTable);
5892
5893     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5894
5895         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5896             s = (ChessSquare) 0; /* account holding counts in guard band */
5897         for( i=0; i<BOARD_HEIGHT; i++ )
5898             initialPosition[i][j] = s;
5899
5900         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5901         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5902         initialPosition[pawnRow][j] = WhitePawn;
5903         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5904         if(gameInfo.variant == VariantXiangqi) {
5905             if(j&1) {
5906                 initialPosition[pawnRow][j] =
5907                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5908                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5909                    initialPosition[2][j] = WhiteCannon;
5910                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5911                 }
5912             }
5913         }
5914         if(gameInfo.variant == VariantGrand) {
5915             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5916                initialPosition[0][j] = WhiteRook;
5917                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5918             }
5919         }
5920         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5921     }
5922     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5923
5924             j=BOARD_LEFT+1;
5925             initialPosition[1][j] = WhiteBishop;
5926             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5927             j=BOARD_RGHT-2;
5928             initialPosition[1][j] = WhiteRook;
5929             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5930     }
5931
5932     if( nrCastlingRights == -1) {
5933         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5934         /*       This sets default castling rights from none to normal corners   */
5935         /* Variants with other castling rights must set them themselves above    */
5936         nrCastlingRights = 6;
5937
5938         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5939         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5940         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5941         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5942         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5943         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5944      }
5945
5946      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5947      if(gameInfo.variant == VariantGreat) { // promotion commoners
5948         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5949         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5950         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5951         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5952      }
5953      if( gameInfo.variant == VariantSChess ) {
5954       initialPosition[1][0] = BlackMarshall;
5955       initialPosition[2][0] = BlackAngel;
5956       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5957       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5958       initialPosition[1][1] = initialPosition[2][1] = 
5959       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5960      }
5961   if (appData.debugMode) {
5962     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5963   }
5964     if(shuffleOpenings) {
5965         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5966         startedFromSetupPosition = TRUE;
5967     }
5968     if(startedFromPositionFile) {
5969       /* [HGM] loadPos: use PositionFile for every new game */
5970       CopyBoard(initialPosition, filePosition);
5971       for(i=0; i<nrCastlingRights; i++)
5972           initialRights[i] = filePosition[CASTLING][i];
5973       startedFromSetupPosition = TRUE;
5974     }
5975
5976     CopyBoard(boards[0], initialPosition);
5977
5978     if(oldx != gameInfo.boardWidth ||
5979        oldy != gameInfo.boardHeight ||
5980        oldv != gameInfo.variant ||
5981        oldh != gameInfo.holdingsWidth
5982                                          )
5983             InitDrawingSizes(-2 ,0);
5984
5985     oldv = gameInfo.variant;
5986     if (redraw)
5987       DrawPosition(TRUE, boards[currentMove]);
5988 }
5989
5990 void
5991 SendBoard(cps, moveNum)
5992      ChessProgramState *cps;
5993      int moveNum;
5994 {
5995     char message[MSG_SIZ];
5996
5997     if (cps->useSetboard) {
5998       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5999       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6000       SendToProgram(message, cps);
6001       free(fen);
6002
6003     } else {
6004       ChessSquare *bp;
6005       int i, j;
6006       /* Kludge to set black to move, avoiding the troublesome and now
6007        * deprecated "black" command.
6008        */
6009       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6010         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6011
6012       SendToProgram("edit\n", cps);
6013       SendToProgram("#\n", cps);
6014       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6015         bp = &boards[moveNum][i][BOARD_LEFT];
6016         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6017           if ((int) *bp < (int) BlackPawn) {
6018             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6019                     AAA + j, ONE + i);
6020             if(message[0] == '+' || message[0] == '~') {
6021               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6022                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6023                         AAA + j, ONE + i);
6024             }
6025             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6026                 message[1] = BOARD_RGHT   - 1 - j + '1';
6027                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6028             }
6029             SendToProgram(message, cps);
6030           }
6031         }
6032       }
6033
6034       SendToProgram("c\n", cps);
6035       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6036         bp = &boards[moveNum][i][BOARD_LEFT];
6037         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6038           if (((int) *bp != (int) EmptySquare)
6039               && ((int) *bp >= (int) BlackPawn)) {
6040             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6041                     AAA + j, ONE + i);
6042             if(message[0] == '+' || message[0] == '~') {
6043               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6044                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6045                         AAA + j, ONE + i);
6046             }
6047             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6048                 message[1] = BOARD_RGHT   - 1 - j + '1';
6049                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6050             }
6051             SendToProgram(message, cps);
6052           }
6053         }
6054       }
6055
6056       SendToProgram(".\n", cps);
6057     }
6058     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6059 }
6060
6061 ChessSquare
6062 DefaultPromoChoice(int white)
6063 {
6064     ChessSquare result;
6065     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6066         result = WhiteFerz; // no choice
6067     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6068         result= WhiteKing; // in Suicide Q is the last thing we want
6069     else if(gameInfo.variant == VariantSpartan)
6070         result = white ? WhiteQueen : WhiteAngel;
6071     else result = WhiteQueen;
6072     if(!white) result = WHITE_TO_BLACK result;
6073     return result;
6074 }
6075
6076 static int autoQueen; // [HGM] oneclick
6077
6078 int
6079 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6080 {
6081     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6082     /* [HGM] add Shogi promotions */
6083     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6084     ChessSquare piece;
6085     ChessMove moveType;
6086     Boolean premove;
6087
6088     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6089     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6090
6091     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6092       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6093         return FALSE;
6094
6095     piece = boards[currentMove][fromY][fromX];
6096     if(gameInfo.variant == VariantShogi) {
6097         promotionZoneSize = BOARD_HEIGHT/3;
6098         highestPromotingPiece = (int)WhiteFerz;
6099     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6100         promotionZoneSize = 3;
6101     }
6102
6103     // Treat Lance as Pawn when it is not representing Amazon
6104     if(gameInfo.variant != VariantSuper) {
6105         if(piece == WhiteLance) piece = WhitePawn; else
6106         if(piece == BlackLance) piece = BlackPawn;
6107     }
6108
6109     // next weed out all moves that do not touch the promotion zone at all
6110     if((int)piece >= BlackPawn) {
6111         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6112              return FALSE;
6113         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6114     } else {
6115         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6116            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6117     }
6118
6119     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6120
6121     // weed out mandatory Shogi promotions
6122     if(gameInfo.variant == VariantShogi) {
6123         if(piece >= BlackPawn) {
6124             if(toY == 0 && piece == BlackPawn ||
6125                toY == 0 && piece == BlackQueen ||
6126                toY <= 1 && piece == BlackKnight) {
6127                 *promoChoice = '+';
6128                 return FALSE;
6129             }
6130         } else {
6131             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6132                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6133                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6134                 *promoChoice = '+';
6135                 return FALSE;
6136             }
6137         }
6138     }
6139
6140     // weed out obviously illegal Pawn moves
6141     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6142         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6143         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6144         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6145         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6146         // note we are not allowed to test for valid (non-)capture, due to premove
6147     }
6148
6149     // we either have a choice what to promote to, or (in Shogi) whether to promote
6150     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6151         *promoChoice = PieceToChar(BlackFerz);  // no choice
6152         return FALSE;
6153     }
6154     // no sense asking what we must promote to if it is going to explode...
6155     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6156         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6157         return FALSE;
6158     }
6159     // give caller the default choice even if we will not make it
6160     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6161     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6162     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6163                            && gameInfo.variant != VariantShogi
6164                            && gameInfo.variant != VariantSuper) return FALSE;
6165     if(autoQueen) return FALSE; // predetermined
6166
6167     // suppress promotion popup on illegal moves that are not premoves
6168     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6169               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6170     if(appData.testLegality && !premove) {
6171         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6172                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6173         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6174             return FALSE;
6175     }
6176
6177     return TRUE;
6178 }
6179
6180 int
6181 InPalace(row, column)
6182      int row, column;
6183 {   /* [HGM] for Xiangqi */
6184     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6185          column < (BOARD_WIDTH + 4)/2 &&
6186          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6187     return FALSE;
6188 }
6189
6190 int
6191 PieceForSquare (x, y)
6192      int x;
6193      int y;
6194 {
6195   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6196      return -1;
6197   else
6198      return boards[currentMove][y][x];
6199 }
6200
6201 int
6202 OKToStartUserMove(x, y)
6203      int x, y;
6204 {
6205     ChessSquare from_piece;
6206     int white_piece;
6207
6208     if (matchMode) return FALSE;
6209     if (gameMode == EditPosition) return TRUE;
6210
6211     if (x >= 0 && y >= 0)
6212       from_piece = boards[currentMove][y][x];
6213     else
6214       from_piece = EmptySquare;
6215
6216     if (from_piece == EmptySquare) return FALSE;
6217
6218     white_piece = (int)from_piece >= (int)WhitePawn &&
6219       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6220
6221     switch (gameMode) {
6222       case PlayFromGameFile:
6223       case AnalyzeFile:
6224       case TwoMachinesPlay:
6225       case EndOfGame:
6226         return FALSE;
6227
6228       case IcsObserving:
6229       case IcsIdle:
6230         return FALSE;
6231
6232       case MachinePlaysWhite:
6233       case IcsPlayingBlack:
6234         if (appData.zippyPlay) return FALSE;
6235         if (white_piece) {
6236             DisplayMoveError(_("You are playing Black"));
6237             return FALSE;
6238         }
6239         break;
6240
6241       case MachinePlaysBlack:
6242       case IcsPlayingWhite:
6243         if (appData.zippyPlay) return FALSE;
6244         if (!white_piece) {
6245             DisplayMoveError(_("You are playing White"));
6246             return FALSE;
6247         }
6248         break;
6249
6250       case EditGame:
6251         if (!white_piece && WhiteOnMove(currentMove)) {
6252             DisplayMoveError(_("It is White's turn"));
6253             return FALSE;
6254         }
6255         if (white_piece && !WhiteOnMove(currentMove)) {
6256             DisplayMoveError(_("It is Black's turn"));
6257             return FALSE;
6258         }
6259         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6260             /* Editing correspondence game history */
6261             /* Could disallow this or prompt for confirmation */
6262             cmailOldMove = -1;
6263         }
6264         break;
6265
6266       case BeginningOfGame:
6267         if (appData.icsActive) return FALSE;
6268         if (!appData.noChessProgram) {
6269             if (!white_piece) {
6270                 DisplayMoveError(_("You are playing White"));
6271                 return FALSE;
6272             }
6273         }
6274         break;
6275
6276       case Training:
6277         if (!white_piece && WhiteOnMove(currentMove)) {
6278             DisplayMoveError(_("It is White's turn"));
6279             return FALSE;
6280         }
6281         if (white_piece && !WhiteOnMove(currentMove)) {
6282             DisplayMoveError(_("It is Black's turn"));
6283             return FALSE;
6284         }
6285         break;
6286
6287       default:
6288       case IcsExamining:
6289         break;
6290     }
6291     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6292         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6293         && gameMode != AnalyzeFile && gameMode != Training) {
6294         DisplayMoveError(_("Displayed position is not current"));
6295         return FALSE;
6296     }
6297     return TRUE;
6298 }
6299
6300 Boolean
6301 OnlyMove(int *x, int *y, Boolean captures) {
6302     DisambiguateClosure cl;
6303     if (appData.zippyPlay) return FALSE;
6304     switch(gameMode) {
6305       case MachinePlaysBlack:
6306       case IcsPlayingWhite:
6307       case BeginningOfGame:
6308         if(!WhiteOnMove(currentMove)) return FALSE;
6309         break;
6310       case MachinePlaysWhite:
6311       case IcsPlayingBlack:
6312         if(WhiteOnMove(currentMove)) return FALSE;
6313         break;
6314       case EditGame:
6315         break;
6316       default:
6317         return FALSE;
6318     }
6319     cl.pieceIn = EmptySquare;
6320     cl.rfIn = *y;
6321     cl.ffIn = *x;
6322     cl.rtIn = -1;
6323     cl.ftIn = -1;
6324     cl.promoCharIn = NULLCHAR;
6325     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6326     if( cl.kind == NormalMove ||
6327         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6328         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6329         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6330       fromX = cl.ff;
6331       fromY = cl.rf;
6332       *x = cl.ft;
6333       *y = cl.rt;
6334       return TRUE;
6335     }
6336     if(cl.kind != ImpossibleMove) return FALSE;
6337     cl.pieceIn = EmptySquare;
6338     cl.rfIn = -1;
6339     cl.ffIn = -1;
6340     cl.rtIn = *y;
6341     cl.ftIn = *x;
6342     cl.promoCharIn = NULLCHAR;
6343     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6344     if( cl.kind == NormalMove ||
6345         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6346         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6347         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6348       fromX = cl.ff;
6349       fromY = cl.rf;
6350       *x = cl.ft;
6351       *y = cl.rt;
6352       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6353       return TRUE;
6354     }
6355     return FALSE;
6356 }
6357
6358 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6359 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6360 int lastLoadGameUseList = FALSE;
6361 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6362 ChessMove lastLoadGameStart = EndOfFile;
6363
6364 void
6365 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6366      int fromX, fromY, toX, toY;
6367      int promoChar;
6368 {
6369     ChessMove moveType;
6370     ChessSquare pdown, pup;
6371
6372     /* Check if the user is playing in turn.  This is complicated because we
6373        let the user "pick up" a piece before it is his turn.  So the piece he
6374        tried to pick up may have been captured by the time he puts it down!
6375        Therefore we use the color the user is supposed to be playing in this
6376        test, not the color of the piece that is currently on the starting
6377        square---except in EditGame mode, where the user is playing both
6378        sides; fortunately there the capture race can't happen.  (It can
6379        now happen in IcsExamining mode, but that's just too bad.  The user
6380        will get a somewhat confusing message in that case.)
6381        */
6382
6383     switch (gameMode) {
6384       case PlayFromGameFile:
6385       case AnalyzeFile:
6386       case TwoMachinesPlay:
6387       case EndOfGame:
6388       case IcsObserving:
6389       case IcsIdle:
6390         /* We switched into a game mode where moves are not accepted,
6391            perhaps while the mouse button was down. */
6392         return;
6393
6394       case MachinePlaysWhite:
6395         /* User is moving for Black */
6396         if (WhiteOnMove(currentMove)) {
6397             DisplayMoveError(_("It is White's turn"));
6398             return;
6399         }
6400         break;
6401
6402       case MachinePlaysBlack:
6403         /* User is moving for White */
6404         if (!WhiteOnMove(currentMove)) {
6405             DisplayMoveError(_("It is Black's turn"));
6406             return;
6407         }
6408         break;
6409
6410       case EditGame:
6411       case IcsExamining:
6412       case BeginningOfGame:
6413       case AnalyzeMode:
6414       case Training:
6415         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6416         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6417             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6418             /* User is moving for Black */
6419             if (WhiteOnMove(currentMove)) {
6420                 DisplayMoveError(_("It is White's turn"));
6421                 return;
6422             }
6423         } else {
6424             /* User is moving for White */
6425             if (!WhiteOnMove(currentMove)) {
6426                 DisplayMoveError(_("It is Black's turn"));
6427                 return;
6428             }
6429         }
6430         break;
6431
6432       case IcsPlayingBlack:
6433         /* User is moving for Black */
6434         if (WhiteOnMove(currentMove)) {
6435             if (!appData.premove) {
6436                 DisplayMoveError(_("It is White's turn"));
6437             } else if (toX >= 0 && toY >= 0) {
6438                 premoveToX = toX;
6439                 premoveToY = toY;
6440                 premoveFromX = fromX;
6441                 premoveFromY = fromY;
6442                 premovePromoChar = promoChar;
6443                 gotPremove = 1;
6444                 if (appData.debugMode)
6445                     fprintf(debugFP, "Got premove: fromX %d,"
6446                             "fromY %d, toX %d, toY %d\n",
6447                             fromX, fromY, toX, toY);
6448             }
6449             return;
6450         }
6451         break;
6452
6453       case IcsPlayingWhite:
6454         /* User is moving for White */
6455         if (!WhiteOnMove(currentMove)) {
6456             if (!appData.premove) {
6457                 DisplayMoveError(_("It is Black's turn"));
6458             } else if (toX >= 0 && toY >= 0) {
6459                 premoveToX = toX;
6460                 premoveToY = toY;
6461                 premoveFromX = fromX;
6462                 premoveFromY = fromY;
6463                 premovePromoChar = promoChar;
6464                 gotPremove = 1;
6465                 if (appData.debugMode)
6466                     fprintf(debugFP, "Got premove: fromX %d,"
6467                             "fromY %d, toX %d, toY %d\n",
6468                             fromX, fromY, toX, toY);
6469             }
6470             return;
6471         }
6472         break;
6473
6474       default:
6475         break;
6476
6477       case EditPosition:
6478         /* EditPosition, empty square, or different color piece;
6479            click-click move is possible */
6480         if (toX == -2 || toY == -2) {
6481             boards[0][fromY][fromX] = EmptySquare;
6482             DrawPosition(FALSE, boards[currentMove]);
6483             return;
6484         } else if (toX >= 0 && toY >= 0) {
6485             boards[0][toY][toX] = boards[0][fromY][fromX];
6486             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6487                 if(boards[0][fromY][0] != EmptySquare) {
6488                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6489                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6490                 }
6491             } else
6492             if(fromX == BOARD_RGHT+1) {
6493                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6494                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6495                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6496                 }
6497             } else
6498             boards[0][fromY][fromX] = EmptySquare;
6499             DrawPosition(FALSE, boards[currentMove]);
6500             return;
6501         }
6502         return;
6503     }
6504
6505     if(toX < 0 || toY < 0) return;
6506     pdown = boards[currentMove][fromY][fromX];
6507     pup = boards[currentMove][toY][toX];
6508
6509     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6510     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6511          if( pup != EmptySquare ) return;
6512          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6513            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6514                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6515            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6516            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6517            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6518            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6519          fromY = DROP_RANK;
6520     }
6521
6522     /* [HGM] always test for legality, to get promotion info */
6523     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6524                                          fromY, fromX, toY, toX, promoChar);
6525     /* [HGM] but possibly ignore an IllegalMove result */
6526     if (appData.testLegality) {
6527         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6528             DisplayMoveError(_("Illegal move"));
6529             return;
6530         }
6531     }
6532
6533     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6534 }
6535
6536 /* Common tail of UserMoveEvent and DropMenuEvent */
6537 int
6538 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6539      ChessMove moveType;
6540      int fromX, fromY, toX, toY;
6541      /*char*/int promoChar;
6542 {
6543     char *bookHit = 0;
6544
6545     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6546         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6547         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6548         if(WhiteOnMove(currentMove)) {
6549             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6550         } else {
6551             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6552         }
6553     }
6554
6555     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6556        move type in caller when we know the move is a legal promotion */
6557     if(moveType == NormalMove && promoChar)
6558         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6559
6560     /* [HGM] <popupFix> The following if has been moved here from
6561        UserMoveEvent(). Because it seemed to belong here (why not allow
6562        piece drops in training games?), and because it can only be
6563        performed after it is known to what we promote. */
6564     if (gameMode == Training) {
6565       /* compare the move played on the board to the next move in the
6566        * game. If they match, display the move and the opponent's response.
6567        * If they don't match, display an error message.
6568        */
6569       int saveAnimate;
6570       Board testBoard;
6571       CopyBoard(testBoard, boards[currentMove]);
6572       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6573
6574       if (CompareBoards(testBoard, boards[currentMove+1])) {
6575         ForwardInner(currentMove+1);
6576
6577         /* Autoplay the opponent's response.
6578          * if appData.animate was TRUE when Training mode was entered,
6579          * the response will be animated.
6580          */
6581         saveAnimate = appData.animate;
6582         appData.animate = animateTraining;
6583         ForwardInner(currentMove+1);
6584         appData.animate = saveAnimate;
6585
6586         /* check for the end of the game */
6587         if (currentMove >= forwardMostMove) {
6588           gameMode = PlayFromGameFile;
6589           ModeHighlight();
6590           SetTrainingModeOff();
6591           DisplayInformation(_("End of game"));
6592         }
6593       } else {
6594         DisplayError(_("Incorrect move"), 0);
6595       }
6596       return 1;
6597     }
6598
6599   /* Ok, now we know that the move is good, so we can kill
6600      the previous line in Analysis Mode */
6601   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6602                                 && currentMove < forwardMostMove) {
6603     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6604     else forwardMostMove = currentMove;
6605   }
6606
6607   /* If we need the chess program but it's dead, restart it */
6608   ResurrectChessProgram();
6609
6610   /* A user move restarts a paused game*/
6611   if (pausing)
6612     PauseEvent();
6613
6614   thinkOutput[0] = NULLCHAR;
6615
6616   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6617
6618   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6619     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6620     return 1;
6621   }
6622
6623   if (gameMode == BeginningOfGame) {
6624     if (appData.noChessProgram) {
6625       gameMode = EditGame;
6626       SetGameInfo();
6627     } else {
6628       char buf[MSG_SIZ];
6629       gameMode = MachinePlaysBlack;
6630       StartClocks();
6631       SetGameInfo();
6632       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6633       DisplayTitle(buf);
6634       if (first.sendName) {
6635         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6636         SendToProgram(buf, &first);
6637       }
6638       StartClocks();
6639     }
6640     ModeHighlight();
6641   }
6642
6643   /* Relay move to ICS or chess engine */
6644   if (appData.icsActive) {
6645     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6646         gameMode == IcsExamining) {
6647       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6648         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6649         SendToICS("draw ");
6650         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6651       }
6652       // also send plain move, in case ICS does not understand atomic claims
6653       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6654       ics_user_moved = 1;
6655     }
6656   } else {
6657     if (first.sendTime && (gameMode == BeginningOfGame ||
6658                            gameMode == MachinePlaysWhite ||
6659                            gameMode == MachinePlaysBlack)) {
6660       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6661     }
6662     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6663          // [HGM] book: if program might be playing, let it use book
6664         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6665         first.maybeThinking = TRUE;
6666     } else SendMoveToProgram(forwardMostMove-1, &first);
6667     if (currentMove == cmailOldMove + 1) {
6668       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6669     }
6670   }
6671
6672   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6673
6674   switch (gameMode) {
6675   case EditGame:
6676     if(appData.testLegality)
6677     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6678     case MT_NONE:
6679     case MT_CHECK:
6680       break;
6681     case MT_CHECKMATE:
6682     case MT_STAINMATE:
6683       if (WhiteOnMove(currentMove)) {
6684         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6685       } else {
6686         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6687       }
6688       break;
6689     case MT_STALEMATE:
6690       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6691       break;
6692     }
6693     break;
6694
6695   case MachinePlaysBlack:
6696   case MachinePlaysWhite:
6697     /* disable certain menu options while machine is thinking */
6698     SetMachineThinkingEnables();
6699     break;
6700
6701   default:
6702     break;
6703   }
6704
6705   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6706   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6707
6708   if(bookHit) { // [HGM] book: simulate book reply
6709         static char bookMove[MSG_SIZ]; // a bit generous?
6710
6711         programStats.nodes = programStats.depth = programStats.time =
6712         programStats.score = programStats.got_only_move = 0;
6713         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6714
6715         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6716         strcat(bookMove, bookHit);
6717         HandleMachineMove(bookMove, &first);
6718   }
6719   return 1;
6720 }
6721
6722 void
6723 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6724      Board board;
6725      int flags;
6726      ChessMove kind;
6727      int rf, ff, rt, ft;
6728      VOIDSTAR closure;
6729 {
6730     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6731     Markers *m = (Markers *) closure;
6732     if(rf == fromY && ff == fromX)
6733         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6734                          || kind == WhiteCapturesEnPassant
6735                          || kind == BlackCapturesEnPassant);
6736     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6737 }
6738
6739 void
6740 MarkTargetSquares(int clear)
6741 {
6742   int x, y;
6743   if(!appData.markers || !appData.highlightDragging ||
6744      !appData.testLegality || gameMode == EditPosition) return;
6745   if(clear) {
6746     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6747   } else {
6748     int capt = 0;
6749     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6750     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6751       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6752       if(capt)
6753       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6754     }
6755   }
6756   DrawPosition(TRUE, NULL);
6757 }
6758
6759 int
6760 Explode(Board board, int fromX, int fromY, int toX, int toY)
6761 {
6762     if(gameInfo.variant == VariantAtomic &&
6763        (board[toY][toX] != EmptySquare ||                     // capture?
6764         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6765                          board[fromY][fromX] == BlackPawn   )
6766       )) {
6767         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6768         return TRUE;
6769     }
6770     return FALSE;
6771 }
6772
6773 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6774
6775 int CanPromote(ChessSquare piece, int y)
6776 {
6777         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6778         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6779         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6780            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6781            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6782                                                   gameInfo.variant == VariantMakruk) return FALSE;
6783         return (piece == BlackPawn && y == 1 ||
6784                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6785                 piece == BlackLance && y == 1 ||
6786                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6787 }
6788
6789 void LeftClick(ClickType clickType, int xPix, int yPix)
6790 {
6791     int x, y;
6792     Boolean saveAnimate;
6793     static int second = 0, promotionChoice = 0, clearFlag = 0;
6794     char promoChoice = NULLCHAR;
6795     ChessSquare piece;
6796
6797     if(appData.seekGraph && appData.icsActive && loggedOn &&
6798         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6799         SeekGraphClick(clickType, xPix, yPix, 0);
6800         return;
6801     }
6802
6803     if (clickType == Press) ErrorPopDown();
6804     MarkTargetSquares(1);
6805
6806     x = EventToSquare(xPix, BOARD_WIDTH);
6807     y = EventToSquare(yPix, BOARD_HEIGHT);
6808     if (!flipView && y >= 0) {
6809         y = BOARD_HEIGHT - 1 - y;
6810     }
6811     if (flipView && x >= 0) {
6812         x = BOARD_WIDTH - 1 - x;
6813     }
6814
6815     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6816         defaultPromoChoice = promoSweep;
6817         promoSweep = EmptySquare;   // terminate sweep
6818         promoDefaultAltered = TRUE;
6819         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6820     }
6821
6822     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6823         if(clickType == Release) return; // ignore upclick of click-click destination
6824         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6825         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6826         if(gameInfo.holdingsWidth &&
6827                 (WhiteOnMove(currentMove)
6828                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6829                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6830             // click in right holdings, for determining promotion piece
6831             ChessSquare p = boards[currentMove][y][x];
6832             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6833             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6834             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6835                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6836                 fromX = fromY = -1;
6837                 return;
6838             }
6839         }
6840         DrawPosition(FALSE, boards[currentMove]);
6841         return;
6842     }
6843
6844     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6845     if(clickType == Press
6846             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6847               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6848               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6849         return;
6850
6851     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6852         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6853
6854     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6855         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6856                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6857         defaultPromoChoice = DefaultPromoChoice(side);
6858     }
6859
6860     autoQueen = appData.alwaysPromoteToQueen;
6861
6862     if (fromX == -1) {
6863       int originalY = y;
6864       gatingPiece = EmptySquare;
6865       if (clickType != Press) {
6866         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6867             DragPieceEnd(xPix, yPix); dragging = 0;
6868             DrawPosition(FALSE, NULL);
6869         }
6870         return;
6871       }
6872       fromX = x; fromY = y;
6873       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6874          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6875          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6876             /* First square */
6877             if (OKToStartUserMove(fromX, fromY)) {
6878                 second = 0;
6879                 MarkTargetSquares(0);
6880                 DragPieceBegin(xPix, yPix); dragging = 1;
6881                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6882                     promoSweep = defaultPromoChoice;
6883                     selectFlag = 0; lastX = xPix; lastY = yPix;
6884                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6885                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6886                 }
6887                 if (appData.highlightDragging) {
6888                     SetHighlights(fromX, fromY, -1, -1);
6889                 }
6890             } else fromX = fromY = -1;
6891             return;
6892         }
6893     }
6894
6895     /* fromX != -1 */
6896     if (clickType == Press && gameMode != EditPosition) {
6897         ChessSquare fromP;
6898         ChessSquare toP;
6899         int frc;
6900
6901         // ignore off-board to clicks
6902         if(y < 0 || x < 0) return;
6903
6904         /* Check if clicking again on the same color piece */
6905         fromP = boards[currentMove][fromY][fromX];
6906         toP = boards[currentMove][y][x];
6907         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6908         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6909              WhitePawn <= toP && toP <= WhiteKing &&
6910              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6911              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6912             (BlackPawn <= fromP && fromP <= BlackKing &&
6913              BlackPawn <= toP && toP <= BlackKing &&
6914              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6915              !(fromP == BlackKing && toP == BlackRook && frc))) {
6916             /* Clicked again on same color piece -- changed his mind */
6917             second = (x == fromX && y == fromY);
6918             promoDefaultAltered = FALSE;
6919            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6920             if (appData.highlightDragging) {
6921                 SetHighlights(x, y, -1, -1);
6922             } else {
6923                 ClearHighlights();
6924             }
6925             if (OKToStartUserMove(x, y)) {
6926                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6927                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6928                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6929                  gatingPiece = boards[currentMove][fromY][fromX];
6930                 else gatingPiece = EmptySquare;
6931                 fromX = x;
6932                 fromY = y; dragging = 1;
6933                 MarkTargetSquares(0);
6934                 DragPieceBegin(xPix, yPix);
6935                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6936                     promoSweep = defaultPromoChoice;
6937                     selectFlag = 0; lastX = xPix; lastY = yPix;
6938                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6939                 }
6940             }
6941            }
6942            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6943            second = FALSE; 
6944         }
6945         // ignore clicks on holdings
6946         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6947     }
6948
6949     if (clickType == Release && x == fromX && y == fromY) {
6950         DragPieceEnd(xPix, yPix); dragging = 0;
6951         if(clearFlag) {
6952             // a deferred attempt to click-click move an empty square on top of a piece
6953             boards[currentMove][y][x] = EmptySquare;
6954             ClearHighlights();
6955             DrawPosition(FALSE, boards[currentMove]);
6956             fromX = fromY = -1; clearFlag = 0;
6957             return;
6958         }
6959         if (appData.animateDragging) {
6960             /* Undo animation damage if any */
6961             DrawPosition(FALSE, NULL);
6962         }
6963         if (second) {
6964             /* Second up/down in same square; just abort move */
6965             second = 0;
6966             fromX = fromY = -1;
6967             gatingPiece = EmptySquare;
6968             ClearHighlights();
6969             gotPremove = 0;
6970             ClearPremoveHighlights();
6971         } else {
6972             /* First upclick in same square; start click-click mode */
6973             SetHighlights(x, y, -1, -1);
6974         }
6975         return;
6976     }
6977
6978     clearFlag = 0;
6979
6980     /* we now have a different from- and (possibly off-board) to-square */
6981     /* Completed move */
6982     toX = x;
6983     toY = y;
6984     saveAnimate = appData.animate;
6985     if (clickType == Press) {
6986         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6987             // must be Edit Position mode with empty-square selected
6988             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6989             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6990             return;
6991         }
6992         /* Finish clickclick move */
6993         if (appData.animate || appData.highlightLastMove) {
6994             SetHighlights(fromX, fromY, toX, toY);
6995         } else {
6996             ClearHighlights();
6997         }
6998     } else {
6999         /* Finish drag move */
7000         if (appData.highlightLastMove) {
7001             SetHighlights(fromX, fromY, toX, toY);
7002         } else {
7003             ClearHighlights();
7004         }
7005         DragPieceEnd(xPix, yPix); dragging = 0;
7006         /* Don't animate move and drag both */
7007         appData.animate = FALSE;
7008     }
7009
7010     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7011     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7012         ChessSquare piece = boards[currentMove][fromY][fromX];
7013         if(gameMode == EditPosition && piece != EmptySquare &&
7014            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7015             int n;
7016
7017             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7018                 n = PieceToNumber(piece - (int)BlackPawn);
7019                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7020                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7021                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7022             } else
7023             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7024                 n = PieceToNumber(piece);
7025                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7026                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7027                 boards[currentMove][n][BOARD_WIDTH-2]++;
7028             }
7029             boards[currentMove][fromY][fromX] = EmptySquare;
7030         }
7031         ClearHighlights();
7032         fromX = fromY = -1;
7033         DrawPosition(TRUE, boards[currentMove]);
7034         return;
7035     }
7036
7037     // off-board moves should not be highlighted
7038     if(x < 0 || y < 0) ClearHighlights();
7039
7040     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7041
7042     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
7043         SetHighlights(fromX, fromY, toX, toY);
7044         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7045             // [HGM] super: promotion to captured piece selected from holdings
7046             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7047             promotionChoice = TRUE;
7048             // kludge follows to temporarily execute move on display, without promoting yet
7049             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7050             boards[currentMove][toY][toX] = p;
7051             DrawPosition(FALSE, boards[currentMove]);
7052             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7053             boards[currentMove][toY][toX] = q;
7054             DisplayMessage("Click in holdings to choose piece", "");
7055             return;
7056         }
7057         PromotionPopUp();
7058     } else {
7059         int oldMove = currentMove;
7060         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7061         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7062         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7063         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7064            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7065             DrawPosition(TRUE, boards[currentMove]);
7066         fromX = fromY = -1;
7067     }
7068     appData.animate = saveAnimate;
7069     if (appData.animate || appData.animateDragging) {
7070         /* Undo animation damage if needed */
7071         DrawPosition(FALSE, NULL);
7072     }
7073 }
7074
7075 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7076 {   // front-end-free part taken out of PieceMenuPopup
7077     int whichMenu; int xSqr, ySqr;
7078
7079     if(seekGraphUp) { // [HGM] seekgraph
7080         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7081         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7082         return -2;
7083     }
7084
7085     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7086          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7087         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7088         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7089         if(action == Press)   {
7090             originalFlip = flipView;
7091             flipView = !flipView; // temporarily flip board to see game from partners perspective
7092             DrawPosition(TRUE, partnerBoard);
7093             DisplayMessage(partnerStatus, "");
7094             partnerUp = TRUE;
7095         } else if(action == Release) {
7096             flipView = originalFlip;
7097             DrawPosition(TRUE, boards[currentMove]);
7098             partnerUp = FALSE;
7099         }
7100         return -2;
7101     }
7102
7103     xSqr = EventToSquare(x, BOARD_WIDTH);
7104     ySqr = EventToSquare(y, BOARD_HEIGHT);
7105     if (action == Release) {
7106         if(pieceSweep != EmptySquare) {
7107             EditPositionMenuEvent(pieceSweep, toX, toY);
7108             pieceSweep = EmptySquare;
7109         } else UnLoadPV(); // [HGM] pv
7110     }
7111     if (action != Press) return -2; // return code to be ignored
7112     switch (gameMode) {
7113       case IcsExamining:
7114         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7115       case EditPosition:
7116         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7117         if (xSqr < 0 || ySqr < 0) return -1;
7118         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7119         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7120         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7121         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7122         NextPiece(0);
7123         return -2;
7124       case IcsObserving:
7125         if(!appData.icsEngineAnalyze) return -1;
7126       case IcsPlayingWhite:
7127       case IcsPlayingBlack:
7128         if(!appData.zippyPlay) goto noZip;
7129       case AnalyzeMode:
7130       case AnalyzeFile:
7131       case MachinePlaysWhite:
7132       case MachinePlaysBlack:
7133       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7134         if (!appData.dropMenu) {
7135           LoadPV(x, y);
7136           return 2; // flag front-end to grab mouse events
7137         }
7138         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7139            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7140       case EditGame:
7141       noZip:
7142         if (xSqr < 0 || ySqr < 0) return -1;
7143         if (!appData.dropMenu || appData.testLegality &&
7144             gameInfo.variant != VariantBughouse &&
7145             gameInfo.variant != VariantCrazyhouse) return -1;
7146         whichMenu = 1; // drop menu
7147         break;
7148       default:
7149         return -1;
7150     }
7151
7152     if (((*fromX = xSqr) < 0) ||
7153         ((*fromY = ySqr) < 0)) {
7154         *fromX = *fromY = -1;
7155         return -1;
7156     }
7157     if (flipView)
7158       *fromX = BOARD_WIDTH - 1 - *fromX;
7159     else
7160       *fromY = BOARD_HEIGHT - 1 - *fromY;
7161
7162     return whichMenu;
7163 }
7164
7165 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7166 {
7167 //    char * hint = lastHint;
7168     FrontEndProgramStats stats;
7169
7170     stats.which = cps == &first ? 0 : 1;
7171     stats.depth = cpstats->depth;
7172     stats.nodes = cpstats->nodes;
7173     stats.score = cpstats->score;
7174     stats.time = cpstats->time;
7175     stats.pv = cpstats->movelist;
7176     stats.hint = lastHint;
7177     stats.an_move_index = 0;
7178     stats.an_move_count = 0;
7179
7180     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7181         stats.hint = cpstats->move_name;
7182         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7183         stats.an_move_count = cpstats->nr_moves;
7184     }
7185
7186     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
7187
7188     SetProgramStats( &stats );
7189 }
7190
7191 void
7192 ClearEngineOutputPane(int which)
7193 {
7194     static FrontEndProgramStats dummyStats;
7195     dummyStats.which = which;
7196     dummyStats.pv = "#";
7197     SetProgramStats( &dummyStats );
7198 }
7199
7200 #define MAXPLAYERS 500
7201
7202 char *
7203 TourneyStandings(int display)
7204 {
7205     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7206     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7207     char result, *p, *names[MAXPLAYERS];
7208
7209     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7210         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7211     names[0] = p = strdup(appData.participants);
7212     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7213
7214     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7215
7216     while(result = appData.results[nr]) {
7217         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7218         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7219         wScore = bScore = 0;
7220         switch(result) {
7221           case '+': wScore = 2; break;
7222           case '-': bScore = 2; break;
7223           case '=': wScore = bScore = 1; break;
7224           case ' ':
7225           case '*': return strdup("busy"); // tourney not finished
7226         }
7227         score[w] += wScore;
7228         score[b] += bScore;
7229         games[w]++;
7230         games[b]++;
7231         nr++;
7232     }
7233     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7234     for(w=0; w<nPlayers; w++) {
7235         bScore = -1;
7236         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7237         ranking[w] = b; points[w] = bScore; score[b] = -2;
7238     }
7239     p = malloc(nPlayers*34+1);
7240     for(w=0; w<nPlayers && w<display; w++)
7241         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7242     free(names[0]);
7243     return p;
7244 }
7245
7246 void
7247 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7248 {       // count all piece types
7249         int p, f, r;
7250         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7251         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7252         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7253                 p = board[r][f];
7254                 pCnt[p]++;
7255                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7256                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7257                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7258                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7259                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7260                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7261         }
7262 }
7263
7264 int
7265 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7266 {
7267         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7268         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7269
7270         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7271         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7272         if(myPawns == 2 && nMine == 3) // KPP
7273             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7274         if(myPawns == 1 && nMine == 2) // KP
7275             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7276         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7277             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7278         if(myPawns) return FALSE;
7279         if(pCnt[WhiteRook+side])
7280             return pCnt[BlackRook-side] ||
7281                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7282                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7283                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7284         if(pCnt[WhiteCannon+side]) {
7285             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7286             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7287         }
7288         if(pCnt[WhiteKnight+side])
7289             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7290         return FALSE;
7291 }
7292
7293 int
7294 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7295 {
7296         VariantClass v = gameInfo.variant;
7297
7298         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7299         if(v == VariantShatranj) return TRUE; // always winnable through baring
7300         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7301         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7302
7303         if(v == VariantXiangqi) {
7304                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7305
7306                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7307                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7308                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7309                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7310                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7311                 if(stale) // we have at least one last-rank P plus perhaps C
7312                     return majors // KPKX
7313                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7314                 else // KCA*E*
7315                     return pCnt[WhiteFerz+side] // KCAK
7316                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7317                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7318                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7319
7320         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7321                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7322
7323                 if(nMine == 1) return FALSE; // bare King
7324                 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
7325                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7326                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7327                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7328                 if(pCnt[WhiteKnight+side])
7329                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7330                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7331                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7332                 if(nBishops)
7333                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7334                 if(pCnt[WhiteAlfil+side])
7335                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7336                 if(pCnt[WhiteWazir+side])
7337                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7338         }
7339
7340         return TRUE;
7341 }
7342
7343 int
7344 Adjudicate(ChessProgramState *cps)
7345 {       // [HGM] some adjudications useful with buggy engines
7346         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7347         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7348         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7349         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7350         int k, count = 0; static int bare = 1;
7351         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7352         Boolean canAdjudicate = !appData.icsActive;
7353
7354         // most tests only when we understand the game, i.e. legality-checking on
7355             if( appData.testLegality )
7356             {   /* [HGM] Some more adjudications for obstinate engines */
7357                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7358                 static int moveCount = 6;
7359                 ChessMove result;
7360                 char *reason = NULL;
7361
7362                 /* Count what is on board. */
7363                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7364
7365                 /* Some material-based adjudications that have to be made before stalemate test */
7366                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7367                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7368                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7369                      if(canAdjudicate && appData.checkMates) {
7370                          if(engineOpponent)
7371                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7372                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7373                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7374                          return 1;
7375                      }
7376                 }
7377
7378                 /* Bare King in Shatranj (loses) or Losers (wins) */
7379                 if( nrW == 1 || nrB == 1) {
7380                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7381                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7382                      if(canAdjudicate && appData.checkMates) {
7383                          if(engineOpponent)
7384                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7385                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7386                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7387                          return 1;
7388                      }
7389                   } else
7390                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7391                   {    /* bare King */
7392                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7393                         if(canAdjudicate && appData.checkMates) {
7394                             /* but only adjudicate if adjudication enabled */
7395                             if(engineOpponent)
7396                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7397                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7398                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7399                             return 1;
7400                         }
7401                   }
7402                 } else bare = 1;
7403
7404
7405             // don't wait for engine to announce game end if we can judge ourselves
7406             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7407               case MT_CHECK:
7408                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7409                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7410                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7411                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7412                             checkCnt++;
7413                         if(checkCnt >= 2) {
7414                             reason = "Xboard adjudication: 3rd check";
7415                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7416                             break;
7417                         }
7418                     }
7419                 }
7420               case MT_NONE:
7421               default:
7422                 break;
7423               case MT_STALEMATE:
7424               case MT_STAINMATE:
7425                 reason = "Xboard adjudication: Stalemate";
7426                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7427                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7428                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7429                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7430                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7431                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7432                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7433                                                                         EP_CHECKMATE : EP_WINS);
7434                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7435                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7436                 }
7437                 break;
7438               case MT_CHECKMATE:
7439                 reason = "Xboard adjudication: Checkmate";
7440                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7441                 break;
7442             }
7443
7444                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7445                     case EP_STALEMATE:
7446                         result = GameIsDrawn; break;
7447                     case EP_CHECKMATE:
7448                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7449                     case EP_WINS:
7450                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7451                     default:
7452                         result = EndOfFile;
7453                 }
7454                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7455                     if(engineOpponent)
7456                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7457                     GameEnds( result, reason, GE_XBOARD );
7458                     return 1;
7459                 }
7460
7461                 /* Next absolutely insufficient mating material. */
7462                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7463                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7464                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7465
7466                      /* always flag draws, for judging claims */
7467                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7468
7469                      if(canAdjudicate && appData.materialDraws) {
7470                          /* but only adjudicate them if adjudication enabled */
7471                          if(engineOpponent) {
7472                            SendToProgram("force\n", engineOpponent); // suppress reply
7473                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7474                          }
7475                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7476                          return 1;
7477                      }
7478                 }
7479
7480                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7481                 if(gameInfo.variant == VariantXiangqi ?
7482                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7483                  : nrW + nrB == 4 &&
7484                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7485                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7486                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7487                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7488                    ) ) {
7489                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7490                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7491                           if(engineOpponent) {
7492                             SendToProgram("force\n", engineOpponent); // suppress reply
7493                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7494                           }
7495                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7496                           return 1;
7497                      }
7498                 } else moveCount = 6;
7499             }
7500         if (appData.debugMode) { int i;
7501             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7502                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7503                     appData.drawRepeats);
7504             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7505               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7506
7507         }
7508
7509         // Repetition draws and 50-move rule can be applied independently of legality testing
7510
7511                 /* Check for rep-draws */
7512                 count = 0;
7513                 for(k = forwardMostMove-2;
7514                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7515                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7516                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7517                     k-=2)
7518                 {   int rights=0;
7519                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7520                         /* compare castling rights */
7521                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7522                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7523                                 rights++; /* King lost rights, while rook still had them */
7524                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7525                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7526                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7527                                    rights++; /* but at least one rook lost them */
7528                         }
7529                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7530                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7531                                 rights++;
7532                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7533                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7534                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7535                                    rights++;
7536                         }
7537                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7538                             && appData.drawRepeats > 1) {
7539                              /* adjudicate after user-specified nr of repeats */
7540                              int result = GameIsDrawn;
7541                              char *details = "XBoard adjudication: repetition draw";
7542                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7543                                 // [HGM] xiangqi: check for forbidden perpetuals
7544                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7545                                 for(m=forwardMostMove; m>k; m-=2) {
7546                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7547                                         ourPerpetual = 0; // the current mover did not always check
7548                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7549                                         hisPerpetual = 0; // the opponent did not always check
7550                                 }
7551                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7552                                                                         ourPerpetual, hisPerpetual);
7553                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7554                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7555                                     details = "Xboard adjudication: perpetual checking";
7556                                 } else
7557                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7558                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7559                                 } else
7560                                 // Now check for perpetual chases
7561                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7562                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7563                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7564                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7565                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7566                                         details = "Xboard adjudication: perpetual chasing";
7567                                     } else
7568                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7569                                         break; // Abort repetition-checking loop.
7570                                 }
7571                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7572                              }
7573                              if(engineOpponent) {
7574                                SendToProgram("force\n", engineOpponent); // suppress reply
7575                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7576                              }
7577                              GameEnds( result, details, GE_XBOARD );
7578                              return 1;
7579                         }
7580                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7581                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7582                     }
7583                 }
7584
7585                 /* Now we test for 50-move draws. Determine ply count */
7586                 count = forwardMostMove;
7587                 /* look for last irreversble move */
7588                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7589                     count--;
7590                 /* if we hit starting position, add initial plies */
7591                 if( count == backwardMostMove )
7592                     count -= initialRulePlies;
7593                 count = forwardMostMove - count;
7594                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7595                         // adjust reversible move counter for checks in Xiangqi
7596                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7597                         if(i < backwardMostMove) i = backwardMostMove;
7598                         while(i <= forwardMostMove) {
7599                                 lastCheck = inCheck; // check evasion does not count
7600                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7601                                 if(inCheck || lastCheck) count--; // check does not count
7602                                 i++;
7603                         }
7604                 }
7605                 if( count >= 100)
7606                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7607                          /* this is used to judge if draw claims are legal */
7608                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7609                          if(engineOpponent) {
7610                            SendToProgram("force\n", engineOpponent); // suppress reply
7611                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7612                          }
7613                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7614                          return 1;
7615                 }
7616
7617                 /* if draw offer is pending, treat it as a draw claim
7618                  * when draw condition present, to allow engines a way to
7619                  * claim draws before making their move to avoid a race
7620                  * condition occurring after their move
7621                  */
7622                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7623                          char *p = NULL;
7624                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7625                              p = "Draw claim: 50-move rule";
7626                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7627                              p = "Draw claim: 3-fold repetition";
7628                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7629                              p = "Draw claim: insufficient mating material";
7630                          if( p != NULL && canAdjudicate) {
7631                              if(engineOpponent) {
7632                                SendToProgram("force\n", engineOpponent); // suppress reply
7633                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7634                              }
7635                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7636                              return 1;
7637                          }
7638                 }
7639
7640                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7641                     if(engineOpponent) {
7642                       SendToProgram("force\n", engineOpponent); // suppress reply
7643                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7644                     }
7645                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7646                     return 1;
7647                 }
7648         return 0;
7649 }
7650
7651 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7652 {   // [HGM] book: this routine intercepts moves to simulate book replies
7653     char *bookHit = NULL;
7654
7655     //first determine if the incoming move brings opponent into his book
7656     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7657         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7658     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7659     if(bookHit != NULL && !cps->bookSuspend) {
7660         // make sure opponent is not going to reply after receiving move to book position
7661         SendToProgram("force\n", cps);
7662         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7663     }
7664     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7665     // now arrange restart after book miss
7666     if(bookHit) {
7667         // after a book hit we never send 'go', and the code after the call to this routine
7668         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7669         char buf[MSG_SIZ], *move = bookHit;
7670         if(cps->useSAN) {
7671             int fromX, fromY, toX, toY;
7672             char promoChar;
7673             ChessMove moveType;
7674             move = buf + 30;
7675             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7676                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7677                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7678                                     PosFlags(forwardMostMove),
7679                                     fromY, fromX, toY, toX, promoChar, move);
7680             } else {
7681                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7682                 bookHit = NULL;
7683             }
7684         }
7685         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7686         SendToProgram(buf, cps);
7687         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7688     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7689         SendToProgram("go\n", cps);
7690         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7691     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7692         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7693             SendToProgram("go\n", cps);
7694         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7695     }
7696     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7697 }
7698
7699 char *savedMessage;
7700 ChessProgramState *savedState;
7701 void DeferredBookMove(void)
7702 {
7703         if(savedState->lastPing != savedState->lastPong)
7704                     ScheduleDelayedEvent(DeferredBookMove, 10);
7705         else
7706         HandleMachineMove(savedMessage, savedState);
7707 }
7708
7709 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7710
7711 void
7712 HandleMachineMove(message, cps)
7713      char *message;
7714      ChessProgramState *cps;
7715 {
7716     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7717     char realname[MSG_SIZ];
7718     int fromX, fromY, toX, toY;
7719     ChessMove moveType;
7720     char promoChar;
7721     char *p, *pv=buf1;
7722     int machineWhite;
7723     char *bookHit;
7724
7725     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7726         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7727         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7728             DisplayError(_("Invalid pairing from pairing engine"), 0);
7729             return;
7730         }
7731         pairingReceived = 1;
7732         NextMatchGame();
7733         return; // Skim the pairing messages here.
7734     }
7735
7736     cps->userError = 0;
7737
7738 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7739     /*
7740      * Kludge to ignore BEL characters
7741      */
7742     while (*message == '\007') message++;
7743
7744     /*
7745      * [HGM] engine debug message: ignore lines starting with '#' character
7746      */
7747     if(cps->debug && *message == '#') return;
7748
7749     /*
7750      * Look for book output
7751      */
7752     if (cps == &first && bookRequested) {
7753         if (message[0] == '\t' || message[0] == ' ') {
7754             /* Part of the book output is here; append it */
7755             strcat(bookOutput, message);
7756             strcat(bookOutput, "  \n");
7757             return;
7758         } else if (bookOutput[0] != NULLCHAR) {
7759             /* All of book output has arrived; display it */
7760             char *p = bookOutput;
7761             while (*p != NULLCHAR) {
7762                 if (*p == '\t') *p = ' ';
7763                 p++;
7764             }
7765             DisplayInformation(bookOutput);
7766             bookRequested = FALSE;
7767             /* Fall through to parse the current output */
7768         }
7769     }
7770
7771     /*
7772      * Look for machine move.
7773      */
7774     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7775         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7776     {
7777         /* This method is only useful on engines that support ping */
7778         if (cps->lastPing != cps->lastPong) {
7779           if (gameMode == BeginningOfGame) {
7780             /* Extra move from before last new; ignore */
7781             if (appData.debugMode) {
7782                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7783             }
7784           } else {
7785             if (appData.debugMode) {
7786                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7787                         cps->which, gameMode);
7788             }
7789
7790             SendToProgram("undo\n", cps);
7791           }
7792           return;
7793         }
7794
7795         switch (gameMode) {
7796           case BeginningOfGame:
7797             /* Extra move from before last reset; ignore */
7798             if (appData.debugMode) {
7799                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7800             }
7801             return;
7802
7803           case EndOfGame:
7804           case IcsIdle:
7805           default:
7806             /* Extra move after we tried to stop.  The mode test is
7807                not a reliable way of detecting this problem, but it's
7808                the best we can do on engines that don't support ping.
7809             */
7810             if (appData.debugMode) {
7811                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7812                         cps->which, gameMode);
7813             }
7814             SendToProgram("undo\n", cps);
7815             return;
7816
7817           case MachinePlaysWhite:
7818           case IcsPlayingWhite:
7819             machineWhite = TRUE;
7820             break;
7821
7822           case MachinePlaysBlack:
7823           case IcsPlayingBlack:
7824             machineWhite = FALSE;
7825             break;
7826
7827           case TwoMachinesPlay:
7828             machineWhite = (cps->twoMachinesColor[0] == 'w');
7829             break;
7830         }
7831         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7832             if (appData.debugMode) {
7833                 fprintf(debugFP,
7834                         "Ignoring move out of turn by %s, gameMode %d"
7835                         ", forwardMost %d\n",
7836                         cps->which, gameMode, forwardMostMove);
7837             }
7838             return;
7839         }
7840
7841     if (appData.debugMode) { int f = forwardMostMove;
7842         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7843                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7844                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7845     }
7846         if(cps->alphaRank) AlphaRank(machineMove, 4);
7847         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7848                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7849             /* Machine move could not be parsed; ignore it. */
7850           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7851                     machineMove, _(cps->which));
7852             DisplayError(buf1, 0);
7853             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7854                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7855             if (gameMode == TwoMachinesPlay) {
7856               GameEnds(machineWhite ? BlackWins : WhiteWins,
7857                        buf1, GE_XBOARD);
7858             }
7859             return;
7860         }
7861
7862         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7863         /* So we have to redo legality test with true e.p. status here,  */
7864         /* to make sure an illegal e.p. capture does not slip through,   */
7865         /* to cause a forfeit on a justified illegal-move complaint      */
7866         /* of the opponent.                                              */
7867         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7868            ChessMove moveType;
7869            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7870                              fromY, fromX, toY, toX, promoChar);
7871             if (appData.debugMode) {
7872                 int i;
7873                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7874                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7875                 fprintf(debugFP, "castling rights\n");
7876             }
7877             if(moveType == IllegalMove) {
7878               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7879                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7880                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7881                            buf1, GE_XBOARD);
7882                 return;
7883            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7884            /* [HGM] Kludge to handle engines that send FRC-style castling
7885               when they shouldn't (like TSCP-Gothic) */
7886            switch(moveType) {
7887              case WhiteASideCastleFR:
7888              case BlackASideCastleFR:
7889                toX+=2;
7890                currentMoveString[2]++;
7891                break;
7892              case WhiteHSideCastleFR:
7893              case BlackHSideCastleFR:
7894                toX--;
7895                currentMoveString[2]--;
7896                break;
7897              default: ; // nothing to do, but suppresses warning of pedantic compilers
7898            }
7899         }
7900         hintRequested = FALSE;
7901         lastHint[0] = NULLCHAR;
7902         bookRequested = FALSE;
7903         /* Program may be pondering now */
7904         cps->maybeThinking = TRUE;
7905         if (cps->sendTime == 2) cps->sendTime = 1;
7906         if (cps->offeredDraw) cps->offeredDraw--;
7907
7908         /* [AS] Save move info*/
7909         pvInfoList[ forwardMostMove ].score = programStats.score;
7910         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7911         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7912
7913         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7914
7915         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7916         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7917             int count = 0;
7918
7919             while( count < adjudicateLossPlies ) {
7920                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7921
7922                 if( count & 1 ) {
7923                     score = -score; /* Flip score for winning side */
7924                 }
7925
7926                 if( score > adjudicateLossThreshold ) {
7927                     break;
7928                 }
7929
7930                 count++;
7931             }
7932
7933             if( count >= adjudicateLossPlies ) {
7934                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7935
7936                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7937                     "Xboard adjudication",
7938                     GE_XBOARD );
7939
7940                 return;
7941             }
7942         }
7943
7944         if(Adjudicate(cps)) {
7945             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7946             return; // [HGM] adjudicate: for all automatic game ends
7947         }
7948
7949 #if ZIPPY
7950         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7951             first.initDone) {
7952           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7953                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7954                 SendToICS("draw ");
7955                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7956           }
7957           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7958           ics_user_moved = 1;
7959           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7960                 char buf[3*MSG_SIZ];
7961
7962                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7963                         programStats.score / 100.,
7964                         programStats.depth,
7965                         programStats.time / 100.,
7966                         (unsigned int)programStats.nodes,
7967                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7968                         programStats.movelist);
7969                 SendToICS(buf);
7970 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7971           }
7972         }
7973 #endif
7974
7975         /* [AS] Clear stats for next move */
7976         ClearProgramStats();
7977         thinkOutput[0] = NULLCHAR;
7978         hiddenThinkOutputState = 0;
7979
7980         bookHit = NULL;
7981         if (gameMode == TwoMachinesPlay) {
7982             /* [HGM] relaying draw offers moved to after reception of move */
7983             /* and interpreting offer as claim if it brings draw condition */
7984             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7985                 SendToProgram("draw\n", cps->other);
7986             }
7987             if (cps->other->sendTime) {
7988                 SendTimeRemaining(cps->other,
7989                                   cps->other->twoMachinesColor[0] == 'w');
7990             }
7991             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7992             if (firstMove && !bookHit) {
7993                 firstMove = FALSE;
7994                 if (cps->other->useColors) {
7995                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7996                 }
7997                 SendToProgram("go\n", cps->other);
7998             }
7999             cps->other->maybeThinking = TRUE;
8000         }
8001
8002         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8003
8004         if (!pausing && appData.ringBellAfterMoves) {
8005             RingBell();
8006         }
8007
8008         /*
8009          * Reenable menu items that were disabled while
8010          * machine was thinking
8011          */
8012         if (gameMode != TwoMachinesPlay)
8013             SetUserThinkingEnables();
8014
8015         // [HGM] book: after book hit opponent has received move and is now in force mode
8016         // force the book reply into it, and then fake that it outputted this move by jumping
8017         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8018         if(bookHit) {
8019                 static char bookMove[MSG_SIZ]; // a bit generous?
8020
8021                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8022                 strcat(bookMove, bookHit);
8023                 message = bookMove;
8024                 cps = cps->other;
8025                 programStats.nodes = programStats.depth = programStats.time =
8026                 programStats.score = programStats.got_only_move = 0;
8027                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8028
8029                 if(cps->lastPing != cps->lastPong) {
8030                     savedMessage = message; // args for deferred call
8031                     savedState = cps;
8032                     ScheduleDelayedEvent(DeferredBookMove, 10);
8033                     return;
8034                 }
8035                 goto FakeBookMove;
8036         }
8037
8038         return;
8039     }
8040
8041     /* Set special modes for chess engines.  Later something general
8042      *  could be added here; for now there is just one kludge feature,
8043      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8044      *  when "xboard" is given as an interactive command.
8045      */
8046     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8047         cps->useSigint = FALSE;
8048         cps->useSigterm = FALSE;
8049     }
8050     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8051       ParseFeatures(message+8, cps);
8052       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8053     }
8054
8055     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8056       int dummy, s=6; char buf[MSG_SIZ];
8057       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8058       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8059       ParseFEN(boards[0], &dummy, message+s);
8060       DrawPosition(TRUE, boards[0]);
8061       startedFromSetupPosition = TRUE;
8062       return;
8063     }
8064     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8065      * want this, I was asked to put it in, and obliged.
8066      */
8067     if (!strncmp(message, "setboard ", 9)) {
8068         Board initial_position;
8069
8070         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8071
8072         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8073             DisplayError(_("Bad FEN received from engine"), 0);
8074             return ;
8075         } else {
8076            Reset(TRUE, FALSE);
8077            CopyBoard(boards[0], initial_position);
8078            initialRulePlies = FENrulePlies;
8079            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8080            else gameMode = MachinePlaysBlack;
8081            DrawPosition(FALSE, boards[currentMove]);
8082         }
8083         return;
8084     }
8085
8086     /*
8087      * Look for communication commands
8088      */
8089     if (!strncmp(message, "telluser ", 9)) {
8090         if(message[9] == '\\' && message[10] == '\\')
8091             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8092         PlayTellSound();
8093         DisplayNote(message + 9);
8094         return;
8095     }
8096     if (!strncmp(message, "tellusererror ", 14)) {
8097         cps->userError = 1;
8098         if(message[14] == '\\' && message[15] == '\\')
8099             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8100         PlayTellSound();
8101         DisplayError(message + 14, 0);
8102         return;
8103     }
8104     if (!strncmp(message, "tellopponent ", 13)) {
8105       if (appData.icsActive) {
8106         if (loggedOn) {
8107           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8108           SendToICS(buf1);
8109         }
8110       } else {
8111         DisplayNote(message + 13);
8112       }
8113       return;
8114     }
8115     if (!strncmp(message, "tellothers ", 11)) {
8116       if (appData.icsActive) {
8117         if (loggedOn) {
8118           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8119           SendToICS(buf1);
8120         }
8121       }
8122       return;
8123     }
8124     if (!strncmp(message, "tellall ", 8)) {
8125       if (appData.icsActive) {
8126         if (loggedOn) {
8127           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8128           SendToICS(buf1);
8129         }
8130       } else {
8131         DisplayNote(message + 8);
8132       }
8133       return;
8134     }
8135     if (strncmp(message, "warning", 7) == 0) {
8136         /* Undocumented feature, use tellusererror in new code */
8137         DisplayError(message, 0);
8138         return;
8139     }
8140     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8141         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8142         strcat(realname, " query");
8143         AskQuestion(realname, buf2, buf1, cps->pr);
8144         return;
8145     }
8146     /* Commands from the engine directly to ICS.  We don't allow these to be
8147      *  sent until we are logged on. Crafty kibitzes have been known to
8148      *  interfere with the login process.
8149      */
8150     if (loggedOn) {
8151         if (!strncmp(message, "tellics ", 8)) {
8152             SendToICS(message + 8);
8153             SendToICS("\n");
8154             return;
8155         }
8156         if (!strncmp(message, "tellicsnoalias ", 15)) {
8157             SendToICS(ics_prefix);
8158             SendToICS(message + 15);
8159             SendToICS("\n");
8160             return;
8161         }
8162         /* The following are for backward compatibility only */
8163         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8164             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8165             SendToICS(ics_prefix);
8166             SendToICS(message);
8167             SendToICS("\n");
8168             return;
8169         }
8170     }
8171     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8172         return;
8173     }
8174     /*
8175      * If the move is illegal, cancel it and redraw the board.
8176      * Also deal with other error cases.  Matching is rather loose
8177      * here to accommodate engines written before the spec.
8178      */
8179     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8180         strncmp(message, "Error", 5) == 0) {
8181         if (StrStr(message, "name") ||
8182             StrStr(message, "rating") || StrStr(message, "?") ||
8183             StrStr(message, "result") || StrStr(message, "board") ||
8184             StrStr(message, "bk") || StrStr(message, "computer") ||
8185             StrStr(message, "variant") || StrStr(message, "hint") ||
8186             StrStr(message, "random") || StrStr(message, "depth") ||
8187             StrStr(message, "accepted")) {
8188             return;
8189         }
8190         if (StrStr(message, "protover")) {
8191           /* Program is responding to input, so it's apparently done
8192              initializing, and this error message indicates it is
8193              protocol version 1.  So we don't need to wait any longer
8194              for it to initialize and send feature commands. */
8195           FeatureDone(cps, 1);
8196           cps->protocolVersion = 1;
8197           return;
8198         }
8199         cps->maybeThinking = FALSE;
8200
8201         if (StrStr(message, "draw")) {
8202             /* Program doesn't have "draw" command */
8203             cps->sendDrawOffers = 0;
8204             return;
8205         }
8206         if (cps->sendTime != 1 &&
8207             (StrStr(message, "time") || StrStr(message, "otim"))) {
8208           /* Program apparently doesn't have "time" or "otim" command */
8209           cps->sendTime = 0;
8210           return;
8211         }
8212         if (StrStr(message, "analyze")) {
8213             cps->analysisSupport = FALSE;
8214             cps->analyzing = FALSE;
8215             Reset(FALSE, TRUE);
8216             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8217             DisplayError(buf2, 0);
8218             return;
8219         }
8220         if (StrStr(message, "(no matching move)st")) {
8221           /* Special kludge for GNU Chess 4 only */
8222           cps->stKludge = TRUE;
8223           SendTimeControl(cps, movesPerSession, timeControl,
8224                           timeIncrement, appData.searchDepth,
8225                           searchTime);
8226           return;
8227         }
8228         if (StrStr(message, "(no matching move)sd")) {
8229           /* Special kludge for GNU Chess 4 only */
8230           cps->sdKludge = TRUE;
8231           SendTimeControl(cps, movesPerSession, timeControl,
8232                           timeIncrement, appData.searchDepth,
8233                           searchTime);
8234           return;
8235         }
8236         if (!StrStr(message, "llegal")) {
8237             return;
8238         }
8239         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8240             gameMode == IcsIdle) return;
8241         if (forwardMostMove <= backwardMostMove) return;
8242         if (pausing) PauseEvent();
8243       if(appData.forceIllegal) {
8244             // [HGM] illegal: machine refused move; force position after move into it
8245           SendToProgram("force\n", cps);
8246           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8247                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8248                 // when black is to move, while there might be nothing on a2 or black
8249                 // might already have the move. So send the board as if white has the move.
8250                 // But first we must change the stm of the engine, as it refused the last move
8251                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8252                 if(WhiteOnMove(forwardMostMove)) {
8253                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8254                     SendBoard(cps, forwardMostMove); // kludgeless board
8255                 } else {
8256                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8257                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8258                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8259                 }
8260           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8261             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8262                  gameMode == TwoMachinesPlay)
8263               SendToProgram("go\n", cps);
8264             return;
8265       } else
8266         if (gameMode == PlayFromGameFile) {
8267             /* Stop reading this game file */
8268             gameMode = EditGame;
8269             ModeHighlight();
8270         }
8271         /* [HGM] illegal-move claim should forfeit game when Xboard */
8272         /* only passes fully legal moves                            */
8273         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8274             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8275                                 "False illegal-move claim", GE_XBOARD );
8276             return; // do not take back move we tested as valid
8277         }
8278         currentMove = forwardMostMove-1;
8279         DisplayMove(currentMove-1); /* before DisplayMoveError */
8280         SwitchClocks(forwardMostMove-1); // [HGM] race
8281         DisplayBothClocks();
8282         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8283                 parseList[currentMove], _(cps->which));
8284         DisplayMoveError(buf1);
8285         DrawPosition(FALSE, boards[currentMove]);
8286         return;
8287     }
8288     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8289         /* Program has a broken "time" command that
8290            outputs a string not ending in newline.
8291            Don't use it. */
8292         cps->sendTime = 0;
8293     }
8294
8295     /*
8296      * If chess program startup fails, exit with an error message.
8297      * Attempts to recover here are futile.
8298      */
8299     if ((StrStr(message, "unknown host") != NULL)
8300         || (StrStr(message, "No remote directory") != NULL)
8301         || (StrStr(message, "not found") != NULL)
8302         || (StrStr(message, "No such file") != NULL)
8303         || (StrStr(message, "can't alloc") != NULL)
8304         || (StrStr(message, "Permission denied") != NULL)) {
8305
8306         cps->maybeThinking = FALSE;
8307         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8308                 _(cps->which), cps->program, cps->host, message);
8309         RemoveInputSource(cps->isr);
8310         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8311             if(cps == &first) appData.noChessProgram = TRUE;
8312             DisplayError(buf1, 0);
8313         }
8314         return;
8315     }
8316
8317     /*
8318      * Look for hint output
8319      */
8320     if (sscanf(message, "Hint: %s", buf1) == 1) {
8321         if (cps == &first && hintRequested) {
8322             hintRequested = FALSE;
8323             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8324                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8325                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8326                                     PosFlags(forwardMostMove),
8327                                     fromY, fromX, toY, toX, promoChar, buf1);
8328                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8329                 DisplayInformation(buf2);
8330             } else {
8331                 /* Hint move could not be parsed!? */
8332               snprintf(buf2, sizeof(buf2),
8333                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8334                         buf1, _(cps->which));
8335                 DisplayError(buf2, 0);
8336             }
8337         } else {
8338           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8339         }
8340         return;
8341     }
8342
8343     /*
8344      * Ignore other messages if game is not in progress
8345      */
8346     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8347         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8348
8349     /*
8350      * look for win, lose, draw, or draw offer
8351      */
8352     if (strncmp(message, "1-0", 3) == 0) {
8353         char *p, *q, *r = "";
8354         p = strchr(message, '{');
8355         if (p) {
8356             q = strchr(p, '}');
8357             if (q) {
8358                 *q = NULLCHAR;
8359                 r = p + 1;
8360             }
8361         }
8362         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8363         return;
8364     } else if (strncmp(message, "0-1", 3) == 0) {
8365         char *p, *q, *r = "";
8366         p = strchr(message, '{');
8367         if (p) {
8368             q = strchr(p, '}');
8369             if (q) {
8370                 *q = NULLCHAR;
8371                 r = p + 1;
8372             }
8373         }
8374         /* Kludge for Arasan 4.1 bug */
8375         if (strcmp(r, "Black resigns") == 0) {
8376             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8377             return;
8378         }
8379         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8380         return;
8381     } else if (strncmp(message, "1/2", 3) == 0) {
8382         char *p, *q, *r = "";
8383         p = strchr(message, '{');
8384         if (p) {
8385             q = strchr(p, '}');
8386             if (q) {
8387                 *q = NULLCHAR;
8388                 r = p + 1;
8389             }
8390         }
8391
8392         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8393         return;
8394
8395     } else if (strncmp(message, "White resign", 12) == 0) {
8396         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8397         return;
8398     } else if (strncmp(message, "Black resign", 12) == 0) {
8399         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8400         return;
8401     } else if (strncmp(message, "White matches", 13) == 0 ||
8402                strncmp(message, "Black matches", 13) == 0   ) {
8403         /* [HGM] ignore GNUShogi noises */
8404         return;
8405     } else if (strncmp(message, "White", 5) == 0 &&
8406                message[5] != '(' &&
8407                StrStr(message, "Black") == NULL) {
8408         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8409         return;
8410     } else if (strncmp(message, "Black", 5) == 0 &&
8411                message[5] != '(') {
8412         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8413         return;
8414     } else if (strcmp(message, "resign") == 0 ||
8415                strcmp(message, "computer resigns") == 0) {
8416         switch (gameMode) {
8417           case MachinePlaysBlack:
8418           case IcsPlayingBlack:
8419             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8420             break;
8421           case MachinePlaysWhite:
8422           case IcsPlayingWhite:
8423             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8424             break;
8425           case TwoMachinesPlay:
8426             if (cps->twoMachinesColor[0] == 'w')
8427               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8428             else
8429               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8430             break;
8431           default:
8432             /* can't happen */
8433             break;
8434         }
8435         return;
8436     } else if (strncmp(message, "opponent mates", 14) == 0) {
8437         switch (gameMode) {
8438           case MachinePlaysBlack:
8439           case IcsPlayingBlack:
8440             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8441             break;
8442           case MachinePlaysWhite:
8443           case IcsPlayingWhite:
8444             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8445             break;
8446           case TwoMachinesPlay:
8447             if (cps->twoMachinesColor[0] == 'w')
8448               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8449             else
8450               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8451             break;
8452           default:
8453             /* can't happen */
8454             break;
8455         }
8456         return;
8457     } else if (strncmp(message, "computer mates", 14) == 0) {
8458         switch (gameMode) {
8459           case MachinePlaysBlack:
8460           case IcsPlayingBlack:
8461             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8462             break;
8463           case MachinePlaysWhite:
8464           case IcsPlayingWhite:
8465             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8466             break;
8467           case TwoMachinesPlay:
8468             if (cps->twoMachinesColor[0] == 'w')
8469               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8470             else
8471               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8472             break;
8473           default:
8474             /* can't happen */
8475             break;
8476         }
8477         return;
8478     } else if (strncmp(message, "checkmate", 9) == 0) {
8479         if (WhiteOnMove(forwardMostMove)) {
8480             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8481         } else {
8482             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8483         }
8484         return;
8485     } else if (strstr(message, "Draw") != NULL ||
8486                strstr(message, "game is a draw") != NULL) {
8487         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8488         return;
8489     } else if (strstr(message, "offer") != NULL &&
8490                strstr(message, "draw") != NULL) {
8491 #if ZIPPY
8492         if (appData.zippyPlay && first.initDone) {
8493             /* Relay offer to ICS */
8494             SendToICS(ics_prefix);
8495             SendToICS("draw\n");
8496         }
8497 #endif
8498         cps->offeredDraw = 2; /* valid until this engine moves twice */
8499         if (gameMode == TwoMachinesPlay) {
8500             if (cps->other->offeredDraw) {
8501                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8502             /* [HGM] in two-machine mode we delay relaying draw offer      */
8503             /* until after we also have move, to see if it is really claim */
8504             }
8505         } else if (gameMode == MachinePlaysWhite ||
8506                    gameMode == MachinePlaysBlack) {
8507           if (userOfferedDraw) {
8508             DisplayInformation(_("Machine accepts your draw offer"));
8509             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8510           } else {
8511             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8512           }
8513         }
8514     }
8515
8516
8517     /*
8518      * Look for thinking output
8519      */
8520     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8521           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8522                                 ) {
8523         int plylev, mvleft, mvtot, curscore, time;
8524         char mvname[MOVE_LEN];
8525         u64 nodes; // [DM]
8526         char plyext;
8527         int ignore = FALSE;
8528         int prefixHint = FALSE;
8529         mvname[0] = NULLCHAR;
8530
8531         switch (gameMode) {
8532           case MachinePlaysBlack:
8533           case IcsPlayingBlack:
8534             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8535             break;
8536           case MachinePlaysWhite:
8537           case IcsPlayingWhite:
8538             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8539             break;
8540           case AnalyzeMode:
8541           case AnalyzeFile:
8542             break;
8543           case IcsObserving: /* [DM] icsEngineAnalyze */
8544             if (!appData.icsEngineAnalyze) ignore = TRUE;
8545             break;
8546           case TwoMachinesPlay:
8547             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8548                 ignore = TRUE;
8549             }
8550             break;
8551           default:
8552             ignore = TRUE;
8553             break;
8554         }
8555
8556         if (!ignore) {
8557             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8558             buf1[0] = NULLCHAR;
8559             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8560                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8561
8562                 if (plyext != ' ' && plyext != '\t') {
8563                     time *= 100;
8564                 }
8565
8566                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8567                 if( cps->scoreIsAbsolute &&
8568                     ( gameMode == MachinePlaysBlack ||
8569                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8570                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8571                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8572                      !WhiteOnMove(currentMove)
8573                     ) )
8574                 {
8575                     curscore = -curscore;
8576                 }
8577
8578                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8579
8580                 tempStats.depth = plylev;
8581                 tempStats.nodes = nodes;
8582                 tempStats.time = time;
8583                 tempStats.score = curscore;
8584                 tempStats.got_only_move = 0;
8585
8586                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8587                         int ticklen;
8588
8589                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8590                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8591                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8592                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8593                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8594                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8595                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8596                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8597                 }
8598
8599                 /* Buffer overflow protection */
8600                 if (pv[0] != NULLCHAR) {
8601                     if (strlen(pv) >= sizeof(tempStats.movelist)
8602                         && appData.debugMode) {
8603                         fprintf(debugFP,
8604                                 "PV is too long; using the first %u bytes.\n",
8605                                 (unsigned) sizeof(tempStats.movelist) - 1);
8606                     }
8607
8608                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8609                 } else {
8610                     sprintf(tempStats.movelist, " no PV\n");
8611                 }
8612
8613                 if (tempStats.seen_stat) {
8614                     tempStats.ok_to_send = 1;
8615                 }
8616
8617                 if (strchr(tempStats.movelist, '(') != NULL) {
8618                     tempStats.line_is_book = 1;
8619                     tempStats.nr_moves = 0;
8620                     tempStats.moves_left = 0;
8621                 } else {
8622                     tempStats.line_is_book = 0;
8623                 }
8624
8625                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8626                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8627
8628                 SendProgramStatsToFrontend( cps, &tempStats );
8629
8630                 /*
8631                     [AS] Protect the thinkOutput buffer from overflow... this
8632                     is only useful if buf1 hasn't overflowed first!
8633                 */
8634                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8635                          plylev,
8636                          (gameMode == TwoMachinesPlay ?
8637                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8638                          ((double) curscore) / 100.0,
8639                          prefixHint ? lastHint : "",
8640                          prefixHint ? " " : "" );
8641
8642                 if( buf1[0] != NULLCHAR ) {
8643                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8644
8645                     if( strlen(pv) > max_len ) {
8646                         if( appData.debugMode) {
8647                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8648                         }
8649                         pv[max_len+1] = '\0';
8650                     }
8651
8652                     strcat( thinkOutput, pv);
8653                 }
8654
8655                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8656                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8657                     DisplayMove(currentMove - 1);
8658                 }
8659                 return;
8660
8661             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8662                 /* crafty (9.25+) says "(only move) <move>"
8663                  * if there is only 1 legal move
8664                  */
8665                 sscanf(p, "(only move) %s", buf1);
8666                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8667                 sprintf(programStats.movelist, "%s (only move)", buf1);
8668                 programStats.depth = 1;
8669                 programStats.nr_moves = 1;
8670                 programStats.moves_left = 1;
8671                 programStats.nodes = 1;
8672                 programStats.time = 1;
8673                 programStats.got_only_move = 1;
8674
8675                 /* Not really, but we also use this member to
8676                    mean "line isn't going to change" (Crafty
8677                    isn't searching, so stats won't change) */
8678                 programStats.line_is_book = 1;
8679
8680                 SendProgramStatsToFrontend( cps, &programStats );
8681
8682                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8683                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8684                     DisplayMove(currentMove - 1);
8685                 }
8686                 return;
8687             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8688                               &time, &nodes, &plylev, &mvleft,
8689                               &mvtot, mvname) >= 5) {
8690                 /* The stat01: line is from Crafty (9.29+) in response
8691                    to the "." command */
8692                 programStats.seen_stat = 1;
8693                 cps->maybeThinking = TRUE;
8694
8695                 if (programStats.got_only_move || !appData.periodicUpdates)
8696                   return;
8697
8698                 programStats.depth = plylev;
8699                 programStats.time = time;
8700                 programStats.nodes = nodes;
8701                 programStats.moves_left = mvleft;
8702                 programStats.nr_moves = mvtot;
8703                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8704                 programStats.ok_to_send = 1;
8705                 programStats.movelist[0] = '\0';
8706
8707                 SendProgramStatsToFrontend( cps, &programStats );
8708
8709                 return;
8710
8711             } else if (strncmp(message,"++",2) == 0) {
8712                 /* Crafty 9.29+ outputs this */
8713                 programStats.got_fail = 2;
8714                 return;
8715
8716             } else if (strncmp(message,"--",2) == 0) {
8717                 /* Crafty 9.29+ outputs this */
8718                 programStats.got_fail = 1;
8719                 return;
8720
8721             } else if (thinkOutput[0] != NULLCHAR &&
8722                        strncmp(message, "    ", 4) == 0) {
8723                 unsigned message_len;
8724
8725                 p = message;
8726                 while (*p && *p == ' ') p++;
8727
8728                 message_len = strlen( p );
8729
8730                 /* [AS] Avoid buffer overflow */
8731                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8732                     strcat(thinkOutput, " ");
8733                     strcat(thinkOutput, p);
8734                 }
8735
8736                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8737                     strcat(programStats.movelist, " ");
8738                     strcat(programStats.movelist, p);
8739                 }
8740
8741                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8742                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8743                     DisplayMove(currentMove - 1);
8744                 }
8745                 return;
8746             }
8747         }
8748         else {
8749             buf1[0] = NULLCHAR;
8750
8751             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8752                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8753             {
8754                 ChessProgramStats cpstats;
8755
8756                 if (plyext != ' ' && plyext != '\t') {
8757                     time *= 100;
8758                 }
8759
8760                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8761                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8762                     curscore = -curscore;
8763                 }
8764
8765                 cpstats.depth = plylev;
8766                 cpstats.nodes = nodes;
8767                 cpstats.time = time;
8768                 cpstats.score = curscore;
8769                 cpstats.got_only_move = 0;
8770                 cpstats.movelist[0] = '\0';
8771
8772                 if (buf1[0] != NULLCHAR) {
8773                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8774                 }
8775
8776                 cpstats.ok_to_send = 0;
8777                 cpstats.line_is_book = 0;
8778                 cpstats.nr_moves = 0;
8779                 cpstats.moves_left = 0;
8780
8781                 SendProgramStatsToFrontend( cps, &cpstats );
8782             }
8783         }
8784     }
8785 }
8786
8787
8788 /* Parse a game score from the character string "game", and
8789    record it as the history of the current game.  The game
8790    score is NOT assumed to start from the standard position.
8791    The display is not updated in any way.
8792    */
8793 void
8794 ParseGameHistory(game)
8795      char *game;
8796 {
8797     ChessMove moveType;
8798     int fromX, fromY, toX, toY, boardIndex;
8799     char promoChar;
8800     char *p, *q;
8801     char buf[MSG_SIZ];
8802
8803     if (appData.debugMode)
8804       fprintf(debugFP, "Parsing game history: %s\n", game);
8805
8806     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8807     gameInfo.site = StrSave(appData.icsHost);
8808     gameInfo.date = PGNDate();
8809     gameInfo.round = StrSave("-");
8810
8811     /* Parse out names of players */
8812     while (*game == ' ') game++;
8813     p = buf;
8814     while (*game != ' ') *p++ = *game++;
8815     *p = NULLCHAR;
8816     gameInfo.white = StrSave(buf);
8817     while (*game == ' ') game++;
8818     p = buf;
8819     while (*game != ' ' && *game != '\n') *p++ = *game++;
8820     *p = NULLCHAR;
8821     gameInfo.black = StrSave(buf);
8822
8823     /* Parse moves */
8824     boardIndex = blackPlaysFirst ? 1 : 0;
8825     yynewstr(game);
8826     for (;;) {
8827         yyboardindex = boardIndex;
8828         moveType = (ChessMove) Myylex();
8829         switch (moveType) {
8830           case IllegalMove:             /* maybe suicide chess, etc. */
8831   if (appData.debugMode) {
8832     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8833     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8834     setbuf(debugFP, NULL);
8835   }
8836           case WhitePromotion:
8837           case BlackPromotion:
8838           case WhiteNonPromotion:
8839           case BlackNonPromotion:
8840           case NormalMove:
8841           case WhiteCapturesEnPassant:
8842           case BlackCapturesEnPassant:
8843           case WhiteKingSideCastle:
8844           case WhiteQueenSideCastle:
8845           case BlackKingSideCastle:
8846           case BlackQueenSideCastle:
8847           case WhiteKingSideCastleWild:
8848           case WhiteQueenSideCastleWild:
8849           case BlackKingSideCastleWild:
8850           case BlackQueenSideCastleWild:
8851           /* PUSH Fabien */
8852           case WhiteHSideCastleFR:
8853           case WhiteASideCastleFR:
8854           case BlackHSideCastleFR:
8855           case BlackASideCastleFR:
8856           /* POP Fabien */
8857             fromX = currentMoveString[0] - AAA;
8858             fromY = currentMoveString[1] - ONE;
8859             toX = currentMoveString[2] - AAA;
8860             toY = currentMoveString[3] - ONE;
8861             promoChar = currentMoveString[4];
8862             break;
8863           case WhiteDrop:
8864           case BlackDrop:
8865             fromX = moveType == WhiteDrop ?
8866               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8867             (int) CharToPiece(ToLower(currentMoveString[0]));
8868             fromY = DROP_RANK;
8869             toX = currentMoveString[2] - AAA;
8870             toY = currentMoveString[3] - ONE;
8871             promoChar = NULLCHAR;
8872             break;
8873           case AmbiguousMove:
8874             /* bug? */
8875             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8876   if (appData.debugMode) {
8877     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8878     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8879     setbuf(debugFP, NULL);
8880   }
8881             DisplayError(buf, 0);
8882             return;
8883           case ImpossibleMove:
8884             /* bug? */
8885             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8886   if (appData.debugMode) {
8887     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8888     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8889     setbuf(debugFP, NULL);
8890   }
8891             DisplayError(buf, 0);
8892             return;
8893           case EndOfFile:
8894             if (boardIndex < backwardMostMove) {
8895                 /* Oops, gap.  How did that happen? */
8896                 DisplayError(_("Gap in move list"), 0);
8897                 return;
8898             }
8899             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8900             if (boardIndex > forwardMostMove) {
8901                 forwardMostMove = boardIndex;
8902             }
8903             return;
8904           case ElapsedTime:
8905             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8906                 strcat(parseList[boardIndex-1], " ");
8907                 strcat(parseList[boardIndex-1], yy_text);
8908             }
8909             continue;
8910           case Comment:
8911           case PGNTag:
8912           case NAG:
8913           default:
8914             /* ignore */
8915             continue;
8916           case WhiteWins:
8917           case BlackWins:
8918           case GameIsDrawn:
8919           case GameUnfinished:
8920             if (gameMode == IcsExamining) {
8921                 if (boardIndex < backwardMostMove) {
8922                     /* Oops, gap.  How did that happen? */
8923                     return;
8924                 }
8925                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8926                 return;
8927             }
8928             gameInfo.result = moveType;
8929             p = strchr(yy_text, '{');
8930             if (p == NULL) p = strchr(yy_text, '(');
8931             if (p == NULL) {
8932                 p = yy_text;
8933                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8934             } else {
8935                 q = strchr(p, *p == '{' ? '}' : ')');
8936                 if (q != NULL) *q = NULLCHAR;
8937                 p++;
8938             }
8939             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8940             gameInfo.resultDetails = StrSave(p);
8941             continue;
8942         }
8943         if (boardIndex >= forwardMostMove &&
8944             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8945             backwardMostMove = blackPlaysFirst ? 1 : 0;
8946             return;
8947         }
8948         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8949                                  fromY, fromX, toY, toX, promoChar,
8950                                  parseList[boardIndex]);
8951         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8952         /* currentMoveString is set as a side-effect of yylex */
8953         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8954         strcat(moveList[boardIndex], "\n");
8955         boardIndex++;
8956         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8957         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8958           case MT_NONE:
8959           case MT_STALEMATE:
8960           default:
8961             break;
8962           case MT_CHECK:
8963             if(gameInfo.variant != VariantShogi)
8964                 strcat(parseList[boardIndex - 1], "+");
8965             break;
8966           case MT_CHECKMATE:
8967           case MT_STAINMATE:
8968             strcat(parseList[boardIndex - 1], "#");
8969             break;
8970         }
8971     }
8972 }
8973
8974
8975 /* Apply a move to the given board  */
8976 void
8977 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8978      int fromX, fromY, toX, toY;
8979      int promoChar;
8980      Board board;
8981 {
8982   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8983   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
8984
8985     /* [HGM] compute & store e.p. status and castling rights for new position */
8986     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8987
8988       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8989       oldEP = (signed char)board[EP_STATUS];
8990       board[EP_STATUS] = EP_NONE;
8991
8992       if( board[toY][toX] != EmptySquare )
8993            board[EP_STATUS] = EP_CAPTURE;
8994
8995   if (fromY == DROP_RANK) {
8996         /* must be first */
8997         piece = board[toY][toX] = (ChessSquare) fromX;
8998   } else {
8999       int i;
9000
9001       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9002            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9003                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9004       } else
9005       if( board[fromY][fromX] == WhitePawn ) {
9006            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9007                board[EP_STATUS] = EP_PAWN_MOVE;
9008            if( toY-fromY==2) {
9009                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9010                         gameInfo.variant != VariantBerolina || toX < fromX)
9011                       board[EP_STATUS] = toX | berolina;
9012                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9013                         gameInfo.variant != VariantBerolina || toX > fromX)
9014                       board[EP_STATUS] = toX;
9015            }
9016       } else
9017       if( board[fromY][fromX] == BlackPawn ) {
9018            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9019                board[EP_STATUS] = EP_PAWN_MOVE;
9020            if( toY-fromY== -2) {
9021                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9022                         gameInfo.variant != VariantBerolina || toX < fromX)
9023                       board[EP_STATUS] = toX | berolina;
9024                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9025                         gameInfo.variant != VariantBerolina || toX > fromX)
9026                       board[EP_STATUS] = toX;
9027            }
9028        }
9029
9030        for(i=0; i<nrCastlingRights; i++) {
9031            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9032               board[CASTLING][i] == toX   && castlingRank[i] == toY
9033              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9034        }
9035
9036      if (fromX == toX && fromY == toY) return;
9037
9038      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9039      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9040      if(gameInfo.variant == VariantKnightmate)
9041          king += (int) WhiteUnicorn - (int) WhiteKing;
9042
9043     /* Code added by Tord: */
9044     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9045     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9046         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9047       board[fromY][fromX] = EmptySquare;
9048       board[toY][toX] = EmptySquare;
9049       if((toX > fromX) != (piece == WhiteRook)) {
9050         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9051       } else {
9052         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9053       }
9054     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9055                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9056       board[fromY][fromX] = EmptySquare;
9057       board[toY][toX] = EmptySquare;
9058       if((toX > fromX) != (piece == BlackRook)) {
9059         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9060       } else {
9061         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9062       }
9063     /* End of code added by Tord */
9064
9065     } else if (board[fromY][fromX] == king
9066         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9067         && toY == fromY && toX > fromX+1) {
9068         board[fromY][fromX] = EmptySquare;
9069         board[toY][toX] = king;
9070         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9071         board[fromY][BOARD_RGHT-1] = EmptySquare;
9072     } else if (board[fromY][fromX] == king
9073         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9074                && toY == fromY && toX < fromX-1) {
9075         board[fromY][fromX] = EmptySquare;
9076         board[toY][toX] = king;
9077         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9078         board[fromY][BOARD_LEFT] = EmptySquare;
9079     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9080                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9081                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9082                ) {
9083         /* white pawn promotion */
9084         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9085         if(gameInfo.variant==VariantBughouse ||
9086            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9087             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9088         board[fromY][fromX] = EmptySquare;
9089     } else if ((fromY >= BOARD_HEIGHT>>1)
9090                && (toX != fromX)
9091                && gameInfo.variant != VariantXiangqi
9092                && gameInfo.variant != VariantBerolina
9093                && (board[fromY][fromX] == WhitePawn)
9094                && (board[toY][toX] == EmptySquare)) {
9095         board[fromY][fromX] = EmptySquare;
9096         board[toY][toX] = WhitePawn;
9097         captured = board[toY - 1][toX];
9098         board[toY - 1][toX] = EmptySquare;
9099     } else if ((fromY == BOARD_HEIGHT-4)
9100                && (toX == fromX)
9101                && gameInfo.variant == VariantBerolina
9102                && (board[fromY][fromX] == WhitePawn)
9103                && (board[toY][toX] == EmptySquare)) {
9104         board[fromY][fromX] = EmptySquare;
9105         board[toY][toX] = WhitePawn;
9106         if(oldEP & EP_BEROLIN_A) {
9107                 captured = board[fromY][fromX-1];
9108                 board[fromY][fromX-1] = EmptySquare;
9109         }else{  captured = board[fromY][fromX+1];
9110                 board[fromY][fromX+1] = EmptySquare;
9111         }
9112     } else if (board[fromY][fromX] == king
9113         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9114                && toY == fromY && toX > fromX+1) {
9115         board[fromY][fromX] = EmptySquare;
9116         board[toY][toX] = king;
9117         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9118         board[fromY][BOARD_RGHT-1] = EmptySquare;
9119     } else if (board[fromY][fromX] == king
9120         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9121                && toY == fromY && toX < fromX-1) {
9122         board[fromY][fromX] = EmptySquare;
9123         board[toY][toX] = king;
9124         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9125         board[fromY][BOARD_LEFT] = EmptySquare;
9126     } else if (fromY == 7 && fromX == 3
9127                && board[fromY][fromX] == BlackKing
9128                && toY == 7 && toX == 5) {
9129         board[fromY][fromX] = EmptySquare;
9130         board[toY][toX] = BlackKing;
9131         board[fromY][7] = EmptySquare;
9132         board[toY][4] = BlackRook;
9133     } else if (fromY == 7 && fromX == 3
9134                && board[fromY][fromX] == BlackKing
9135                && toY == 7 && toX == 1) {
9136         board[fromY][fromX] = EmptySquare;
9137         board[toY][toX] = BlackKing;
9138         board[fromY][0] = EmptySquare;
9139         board[toY][2] = BlackRook;
9140     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9141                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9142                && toY < promoRank && promoChar
9143                ) {
9144         /* black pawn promotion */
9145         board[toY][toX] = CharToPiece(ToLower(promoChar));
9146         if(gameInfo.variant==VariantBughouse ||
9147            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9148             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9149         board[fromY][fromX] = EmptySquare;
9150     } else if ((fromY < BOARD_HEIGHT>>1)
9151                && (toX != fromX)
9152                && gameInfo.variant != VariantXiangqi
9153                && gameInfo.variant != VariantBerolina
9154                && (board[fromY][fromX] == BlackPawn)
9155                && (board[toY][toX] == EmptySquare)) {
9156         board[fromY][fromX] = EmptySquare;
9157         board[toY][toX] = BlackPawn;
9158         captured = board[toY + 1][toX];
9159         board[toY + 1][toX] = EmptySquare;
9160     } else if ((fromY == 3)
9161                && (toX == fromX)
9162                && gameInfo.variant == VariantBerolina
9163                && (board[fromY][fromX] == BlackPawn)
9164                && (board[toY][toX] == EmptySquare)) {
9165         board[fromY][fromX] = EmptySquare;
9166         board[toY][toX] = BlackPawn;
9167         if(oldEP & EP_BEROLIN_A) {
9168                 captured = board[fromY][fromX-1];
9169                 board[fromY][fromX-1] = EmptySquare;
9170         }else{  captured = board[fromY][fromX+1];
9171                 board[fromY][fromX+1] = EmptySquare;
9172         }
9173     } else {
9174         board[toY][toX] = board[fromY][fromX];
9175         board[fromY][fromX] = EmptySquare;
9176     }
9177   }
9178
9179     if (gameInfo.holdingsWidth != 0) {
9180
9181       /* !!A lot more code needs to be written to support holdings  */
9182       /* [HGM] OK, so I have written it. Holdings are stored in the */
9183       /* penultimate board files, so they are automaticlly stored   */
9184       /* in the game history.                                       */
9185       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9186                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9187         /* Delete from holdings, by decreasing count */
9188         /* and erasing image if necessary            */
9189         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9190         if(p < (int) BlackPawn) { /* white drop */
9191              p -= (int)WhitePawn;
9192                  p = PieceToNumber((ChessSquare)p);
9193              if(p >= gameInfo.holdingsSize) p = 0;
9194              if(--board[p][BOARD_WIDTH-2] <= 0)
9195                   board[p][BOARD_WIDTH-1] = EmptySquare;
9196              if((int)board[p][BOARD_WIDTH-2] < 0)
9197                         board[p][BOARD_WIDTH-2] = 0;
9198         } else {                  /* black drop */
9199              p -= (int)BlackPawn;
9200                  p = PieceToNumber((ChessSquare)p);
9201              if(p >= gameInfo.holdingsSize) p = 0;
9202              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9203                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9204              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9205                         board[BOARD_HEIGHT-1-p][1] = 0;
9206         }
9207       }
9208       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9209           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9210         /* [HGM] holdings: Add to holdings, if holdings exist */
9211         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9212                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9213                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9214         }
9215         p = (int) captured;
9216         if (p >= (int) BlackPawn) {
9217           p -= (int)BlackPawn;
9218           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9219                   /* in Shogi restore piece to its original  first */
9220                   captured = (ChessSquare) (DEMOTED captured);
9221                   p = DEMOTED p;
9222           }
9223           p = PieceToNumber((ChessSquare)p);
9224           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9225           board[p][BOARD_WIDTH-2]++;
9226           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9227         } else {
9228           p -= (int)WhitePawn;
9229           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9230                   captured = (ChessSquare) (DEMOTED captured);
9231                   p = DEMOTED p;
9232           }
9233           p = PieceToNumber((ChessSquare)p);
9234           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9235           board[BOARD_HEIGHT-1-p][1]++;
9236           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9237         }
9238       }
9239     } else if (gameInfo.variant == VariantAtomic) {
9240       if (captured != EmptySquare) {
9241         int y, x;
9242         for (y = toY-1; y <= toY+1; y++) {
9243           for (x = toX-1; x <= toX+1; x++) {
9244             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9245                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9246               board[y][x] = EmptySquare;
9247             }
9248           }
9249         }
9250         board[toY][toX] = EmptySquare;
9251       }
9252     }
9253     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9254         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9255     } else
9256     if(promoChar == '+') {
9257         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9258         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9259     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9260         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9261     }
9262     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9263                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9264         // [HGM] superchess: take promotion piece out of holdings
9265         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9266         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9267             if(!--board[k][BOARD_WIDTH-2])
9268                 board[k][BOARD_WIDTH-1] = EmptySquare;
9269         } else {
9270             if(!--board[BOARD_HEIGHT-1-k][1])
9271                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9272         }
9273     }
9274
9275 }
9276
9277 /* Updates forwardMostMove */
9278 void
9279 MakeMove(fromX, fromY, toX, toY, promoChar)
9280      int fromX, fromY, toX, toY;
9281      int promoChar;
9282 {
9283 //    forwardMostMove++; // [HGM] bare: moved downstream
9284
9285     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9286         int timeLeft; static int lastLoadFlag=0; int king, piece;
9287         piece = boards[forwardMostMove][fromY][fromX];
9288         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9289         if(gameInfo.variant == VariantKnightmate)
9290             king += (int) WhiteUnicorn - (int) WhiteKing;
9291         if(forwardMostMove == 0) {
9292             if(blackPlaysFirst)
9293                 fprintf(serverMoves, "%s;", second.tidy);
9294             fprintf(serverMoves, "%s;", first.tidy);
9295             if(!blackPlaysFirst)
9296                 fprintf(serverMoves, "%s;", second.tidy);
9297         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9298         lastLoadFlag = loadFlag;
9299         // print base move
9300         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9301         // print castling suffix
9302         if( toY == fromY && piece == king ) {
9303             if(toX-fromX > 1)
9304                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9305             if(fromX-toX >1)
9306                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9307         }
9308         // e.p. suffix
9309         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9310              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9311              boards[forwardMostMove][toY][toX] == EmptySquare
9312              && fromX != toX && fromY != toY)
9313                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9314         // promotion suffix
9315         if(promoChar != NULLCHAR)
9316                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9317         if(!loadFlag) {
9318             fprintf(serverMoves, "/%d/%d",
9319                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9320             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9321             else                      timeLeft = blackTimeRemaining/1000;
9322             fprintf(serverMoves, "/%d", timeLeft);
9323         }
9324         fflush(serverMoves);
9325     }
9326
9327     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9328       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9329                         0, 1);
9330       return;
9331     }
9332     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9333     if (commentList[forwardMostMove+1] != NULL) {
9334         free(commentList[forwardMostMove+1]);
9335         commentList[forwardMostMove+1] = NULL;
9336     }
9337     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9338     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9339     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9340     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9341     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9342     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9343     gameInfo.result = GameUnfinished;
9344     if (gameInfo.resultDetails != NULL) {
9345         free(gameInfo.resultDetails);
9346         gameInfo.resultDetails = NULL;
9347     }
9348     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9349                               moveList[forwardMostMove - 1]);
9350     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9351                              PosFlags(forwardMostMove - 1),
9352                              fromY, fromX, toY, toX, promoChar,
9353                              parseList[forwardMostMove - 1]);
9354     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9355       case MT_NONE:
9356       case MT_STALEMATE:
9357       default:
9358         break;
9359       case MT_CHECK:
9360         if(gameInfo.variant != VariantShogi)
9361             strcat(parseList[forwardMostMove - 1], "+");
9362         break;
9363       case MT_CHECKMATE:
9364       case MT_STAINMATE:
9365         strcat(parseList[forwardMostMove - 1], "#");
9366         break;
9367     }
9368     if (appData.debugMode) {
9369         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9370     }
9371
9372 }
9373
9374 /* Updates currentMove if not pausing */
9375 void
9376 ShowMove(fromX, fromY, toX, toY)
9377 {
9378     int instant = (gameMode == PlayFromGameFile) ?
9379         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9380     if(appData.noGUI) return;
9381     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9382         if (!instant) {
9383             if (forwardMostMove == currentMove + 1) {
9384                 AnimateMove(boards[forwardMostMove - 1],
9385                             fromX, fromY, toX, toY);
9386             }
9387             if (appData.highlightLastMove) {
9388                 SetHighlights(fromX, fromY, toX, toY);
9389             }
9390         }
9391         currentMove = forwardMostMove;
9392     }
9393
9394     if (instant) return;
9395
9396     DisplayMove(currentMove - 1);
9397     DrawPosition(FALSE, boards[currentMove]);
9398     DisplayBothClocks();
9399     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9400     DisplayBook(currentMove);
9401 }
9402
9403 void SendEgtPath(ChessProgramState *cps)
9404 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9405         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9406
9407         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9408
9409         while(*p) {
9410             char c, *q = name+1, *r, *s;
9411
9412             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9413             while(*p && *p != ',') *q++ = *p++;
9414             *q++ = ':'; *q = 0;
9415             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9416                 strcmp(name, ",nalimov:") == 0 ) {
9417                 // take nalimov path from the menu-changeable option first, if it is defined
9418               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9419                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9420             } else
9421             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9422                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9423                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9424                 s = r = StrStr(s, ":") + 1; // beginning of path info
9425                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9426                 c = *r; *r = 0;             // temporarily null-terminate path info
9427                     *--q = 0;               // strip of trailig ':' from name
9428                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9429                 *r = c;
9430                 SendToProgram(buf,cps);     // send egtbpath command for this format
9431             }
9432             if(*p == ',') p++; // read away comma to position for next format name
9433         }
9434 }
9435
9436 void
9437 InitChessProgram(cps, setup)
9438      ChessProgramState *cps;
9439      int setup; /* [HGM] needed to setup FRC opening position */
9440 {
9441     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9442     if (appData.noChessProgram) return;
9443     hintRequested = FALSE;
9444     bookRequested = FALSE;
9445
9446     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9447     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9448     if(cps->memSize) { /* [HGM] memory */
9449       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9450         SendToProgram(buf, cps);
9451     }
9452     SendEgtPath(cps); /* [HGM] EGT */
9453     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9454       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9455         SendToProgram(buf, cps);
9456     }
9457
9458     SendToProgram(cps->initString, cps);
9459     if (gameInfo.variant != VariantNormal &&
9460         gameInfo.variant != VariantLoadable
9461         /* [HGM] also send variant if board size non-standard */
9462         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9463                                             ) {
9464       char *v = VariantName(gameInfo.variant);
9465       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9466         /* [HGM] in protocol 1 we have to assume all variants valid */
9467         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9468         DisplayFatalError(buf, 0, 1);
9469         return;
9470       }
9471
9472       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9473       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9474       if( gameInfo.variant == VariantXiangqi )
9475            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9476       if( gameInfo.variant == VariantShogi )
9477            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9478       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9479            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9480       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9481           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9482            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9483       if( gameInfo.variant == VariantCourier )
9484            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9485       if( gameInfo.variant == VariantSuper )
9486            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9487       if( gameInfo.variant == VariantGreat )
9488            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9489       if( gameInfo.variant == VariantSChess )
9490            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9491       if( gameInfo.variant == VariantGrand )
9492            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9493
9494       if(overruled) {
9495         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9496                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9497            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9498            if(StrStr(cps->variants, b) == NULL) {
9499                // specific sized variant not known, check if general sizing allowed
9500                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9501                    if(StrStr(cps->variants, "boardsize") == NULL) {
9502                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9503                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9504                        DisplayFatalError(buf, 0, 1);
9505                        return;
9506                    }
9507                    /* [HGM] here we really should compare with the maximum supported board size */
9508                }
9509            }
9510       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9511       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9512       SendToProgram(buf, cps);
9513     }
9514     currentlyInitializedVariant = gameInfo.variant;
9515
9516     /* [HGM] send opening position in FRC to first engine */
9517     if(setup) {
9518           SendToProgram("force\n", cps);
9519           SendBoard(cps, 0);
9520           /* engine is now in force mode! Set flag to wake it up after first move. */
9521           setboardSpoiledMachineBlack = 1;
9522     }
9523
9524     if (cps->sendICS) {
9525       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9526       SendToProgram(buf, cps);
9527     }
9528     cps->maybeThinking = FALSE;
9529     cps->offeredDraw = 0;
9530     if (!appData.icsActive) {
9531         SendTimeControl(cps, movesPerSession, timeControl,
9532                         timeIncrement, appData.searchDepth,
9533                         searchTime);
9534     }
9535     if (appData.showThinking
9536         // [HGM] thinking: four options require thinking output to be sent
9537         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9538                                 ) {
9539         SendToProgram("post\n", cps);
9540     }
9541     SendToProgram("hard\n", cps);
9542     if (!appData.ponderNextMove) {
9543         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9544            it without being sure what state we are in first.  "hard"
9545            is not a toggle, so that one is OK.
9546          */
9547         SendToProgram("easy\n", cps);
9548     }
9549     if (cps->usePing) {
9550       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9551       SendToProgram(buf, cps);
9552     }
9553     cps->initDone = TRUE;
9554     ClearEngineOutputPane(cps == &second);
9555 }
9556
9557
9558 void
9559 StartChessProgram(cps)
9560      ChessProgramState *cps;
9561 {
9562     char buf[MSG_SIZ];
9563     int err;
9564
9565     if (appData.noChessProgram) return;
9566     cps->initDone = FALSE;
9567
9568     if (strcmp(cps->host, "localhost") == 0) {
9569         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9570     } else if (*appData.remoteShell == NULLCHAR) {
9571         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9572     } else {
9573         if (*appData.remoteUser == NULLCHAR) {
9574           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9575                     cps->program);
9576         } else {
9577           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9578                     cps->host, appData.remoteUser, cps->program);
9579         }
9580         err = StartChildProcess(buf, "", &cps->pr);
9581     }
9582
9583     if (err != 0) {
9584       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9585         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9586         if(cps != &first) return;
9587         appData.noChessProgram = TRUE;
9588         ThawUI();
9589         SetNCPMode();
9590 //      DisplayFatalError(buf, err, 1);
9591 //      cps->pr = NoProc;
9592 //      cps->isr = NULL;
9593         return;
9594     }
9595
9596     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9597     if (cps->protocolVersion > 1) {
9598       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9599       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9600       cps->comboCnt = 0;  //                and values of combo boxes
9601       SendToProgram(buf, cps);
9602     } else {
9603       SendToProgram("xboard\n", cps);
9604     }
9605 }
9606
9607 void
9608 TwoMachinesEventIfReady P((void))
9609 {
9610   static int curMess = 0;
9611   if (first.lastPing != first.lastPong) {
9612     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9613     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9614     return;
9615   }
9616   if (second.lastPing != second.lastPong) {
9617     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9618     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9619     return;
9620   }
9621   DisplayMessage("", ""); curMess = 0;
9622   ThawUI();
9623   TwoMachinesEvent();
9624 }
9625
9626 char *
9627 MakeName(char *template)
9628 {
9629     time_t clock;
9630     struct tm *tm;
9631     static char buf[MSG_SIZ];
9632     char *p = buf;
9633     int i;
9634
9635     clock = time((time_t *)NULL);
9636     tm = localtime(&clock);
9637
9638     while(*p++ = *template++) if(p[-1] == '%') {
9639         switch(*template++) {
9640           case 0:   *p = 0; return buf;
9641           case 'Y': i = tm->tm_year+1900; break;
9642           case 'y': i = tm->tm_year-100; break;
9643           case 'M': i = tm->tm_mon+1; break;
9644           case 'd': i = tm->tm_mday; break;
9645           case 'h': i = tm->tm_hour; break;
9646           case 'm': i = tm->tm_min; break;
9647           case 's': i = tm->tm_sec; break;
9648           default:  i = 0;
9649         }
9650         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9651     }
9652     return buf;
9653 }
9654
9655 int
9656 CountPlayers(char *p)
9657 {
9658     int n = 0;
9659     while(p = strchr(p, '\n')) p++, n++; // count participants
9660     return n;
9661 }
9662
9663 FILE *
9664 WriteTourneyFile(char *results)
9665 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9666     FILE *f = fopen(appData.tourneyFile, "w");
9667     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9668         // create a file with tournament description
9669         fprintf(f, "-participants {%s}\n", appData.participants);
9670         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9671         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9672         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9673         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9674         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9675         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9676         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9677         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9678         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9679         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9680         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9681         if(searchTime > 0)
9682                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9683         else {
9684                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9685                 fprintf(f, "-tc %s\n", appData.timeControl);
9686                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9687         }
9688         fprintf(f, "-results \"%s\"\n", results);
9689     }
9690     return f;
9691 }
9692
9693 int
9694 CreateTourney(char *name)
9695 {
9696         FILE *f;
9697         if(name[0] == NULLCHAR) {
9698             if(appData.participants[0])
9699                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9700             return 0;
9701         }
9702         f = fopen(name, "r");
9703         if(f) { // file exists
9704             ASSIGN(appData.tourneyFile, name);
9705             ParseArgsFromFile(f); // parse it
9706         } else {
9707             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9708             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9709                 DisplayError(_("Not enough participants"), 0);
9710                 return 0;
9711             }
9712             ASSIGN(appData.tourneyFile, name);
9713             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9714             if((f = WriteTourneyFile("")) == NULL) return 0;
9715         }
9716         fclose(f);
9717         appData.noChessProgram = FALSE;
9718         appData.clockMode = TRUE;
9719         SetGNUMode();
9720         return 1;
9721 }
9722
9723 #define MAXENGINES 1000
9724 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9725
9726 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9727 {
9728     char buf[MSG_SIZ], *p, *q;
9729     int i=1;
9730     while(*names) {
9731         p = names; q = buf;
9732         while(*p && *p != '\n') *q++ = *p++;
9733         *q = 0;
9734         if(engineList[i]) free(engineList[i]);
9735         engineList[i] = strdup(buf);
9736         if(*p == '\n') p++;
9737         TidyProgramName(engineList[i], "localhost", buf);
9738         if(engineMnemonic[i]) free(engineMnemonic[i]);
9739         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9740             strcat(buf, " (");
9741             sscanf(q + 8, "%s", buf + strlen(buf));
9742             strcat(buf, ")");
9743         }
9744         engineMnemonic[i] = strdup(buf);
9745         names = p; i++;
9746       if(i > MAXENGINES - 2) break;
9747     }
9748     engineList[i] = NULL;
9749 }
9750
9751 // following implemented as macro to avoid type limitations
9752 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9753
9754 void SwapEngines(int n)
9755 {   // swap settings for first engine and other engine (so far only some selected options)
9756     int h;
9757     char *p;
9758     if(n == 0) return;
9759     SWAP(directory, p)
9760     SWAP(chessProgram, p)
9761     SWAP(isUCI, h)
9762     SWAP(hasOwnBookUCI, h)
9763     SWAP(protocolVersion, h)
9764     SWAP(reuse, h)
9765     SWAP(scoreIsAbsolute, h)
9766     SWAP(timeOdds, h)
9767     SWAP(logo, p)
9768     SWAP(pgnName, p)
9769     SWAP(pvSAN, h)
9770 }
9771
9772 void
9773 SetPlayer(int player)
9774 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9775     int i;
9776     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9777     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9778     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9779     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9780     if(mnemonic[i]) {
9781         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9782         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9783         ParseArgsFromString(buf);
9784     }
9785     free(engineName);
9786 }
9787
9788 int
9789 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9790 {   // determine players from game number
9791     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9792
9793     if(appData.tourneyType == 0) {
9794         roundsPerCycle = (nPlayers - 1) | 1;
9795         pairingsPerRound = nPlayers / 2;
9796     } else if(appData.tourneyType > 0) {
9797         roundsPerCycle = nPlayers - appData.tourneyType;
9798         pairingsPerRound = appData.tourneyType;
9799     }
9800     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9801     gamesPerCycle = gamesPerRound * roundsPerCycle;
9802     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9803     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9804     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9805     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9806     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9807     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9808
9809     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9810     if(appData.roundSync) *syncInterval = gamesPerRound;
9811
9812     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9813
9814     if(appData.tourneyType == 0) {
9815         if(curPairing == (nPlayers-1)/2 ) {
9816             *whitePlayer = curRound;
9817             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9818         } else {
9819             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9820             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9821             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9822             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9823         }
9824     } else if(appData.tourneyType > 0) {
9825         *whitePlayer = curPairing;
9826         *blackPlayer = curRound + appData.tourneyType;
9827     }
9828
9829     // take care of white/black alternation per round. 
9830     // For cycles and games this is already taken care of by default, derived from matchGame!
9831     return curRound & 1;
9832 }
9833
9834 int
9835 NextTourneyGame(int nr, int *swapColors)
9836 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9837     char *p, *q;
9838     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9839     FILE *tf;
9840     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9841     tf = fopen(appData.tourneyFile, "r");
9842     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9843     ParseArgsFromFile(tf); fclose(tf);
9844     InitTimeControls(); // TC might be altered from tourney file
9845
9846     nPlayers = CountPlayers(appData.participants); // count participants
9847     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9848     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9849
9850     if(syncInterval) {
9851         p = q = appData.results;
9852         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9853         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9854             DisplayMessage(_("Waiting for other game(s)"),"");
9855             waitingForGame = TRUE;
9856             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9857             return 0;
9858         }
9859         waitingForGame = FALSE;
9860     }
9861
9862     if(appData.tourneyType < 0) {
9863         if(nr>=0 && !pairingReceived) {
9864             char buf[1<<16];
9865             if(pairing.pr == NoProc) {
9866                 if(!appData.pairingEngine[0]) {
9867                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9868                     return 0;
9869                 }
9870                 StartChessProgram(&pairing); // starts the pairing engine
9871             }
9872             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9873             SendToProgram(buf, &pairing);
9874             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9875             SendToProgram(buf, &pairing);
9876             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9877         }
9878         pairingReceived = 0;                              // ... so we continue here 
9879         *swapColors = 0;
9880         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9881         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9882         matchGame = 1; roundNr = nr / syncInterval + 1;
9883     }
9884
9885     if(first.pr != NoProc) return 1; // engines already loaded
9886
9887     // redefine engines, engine dir, etc.
9888     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9889     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9890     SwapEngines(1);
9891     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9892     SwapEngines(1);         // and make that valid for second engine by swapping
9893     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9894     InitEngine(&second, 1);
9895     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9896     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9897     return 1;
9898 }
9899
9900 void
9901 NextMatchGame()
9902 {   // performs game initialization that does not invoke engines, and then tries to start the game
9903     int firstWhite, swapColors = 0;
9904     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9905     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9906     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9907     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9908     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9909     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9910     Reset(FALSE, first.pr != NoProc);
9911     appData.noChessProgram = FALSE;
9912     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9913     TwoMachinesEvent();
9914 }
9915
9916 void UserAdjudicationEvent( int result )
9917 {
9918     ChessMove gameResult = GameIsDrawn;
9919
9920     if( result > 0 ) {
9921         gameResult = WhiteWins;
9922     }
9923     else if( result < 0 ) {
9924         gameResult = BlackWins;
9925     }
9926
9927     if( gameMode == TwoMachinesPlay ) {
9928         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9929     }
9930 }
9931
9932
9933 // [HGM] save: calculate checksum of game to make games easily identifiable
9934 int StringCheckSum(char *s)
9935 {
9936         int i = 0;
9937         if(s==NULL) return 0;
9938         while(*s) i = i*259 + *s++;
9939         return i;
9940 }
9941
9942 int GameCheckSum()
9943 {
9944         int i, sum=0;
9945         for(i=backwardMostMove; i<forwardMostMove; i++) {
9946                 sum += pvInfoList[i].depth;
9947                 sum += StringCheckSum(parseList[i]);
9948                 sum += StringCheckSum(commentList[i]);
9949                 sum *= 261;
9950         }
9951         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9952         return sum + StringCheckSum(commentList[i]);
9953 } // end of save patch
9954
9955 void
9956 GameEnds(result, resultDetails, whosays)
9957      ChessMove result;
9958      char *resultDetails;
9959      int whosays;
9960 {
9961     GameMode nextGameMode;
9962     int isIcsGame;
9963     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9964
9965     if(endingGame) return; /* [HGM] crash: forbid recursion */
9966     endingGame = 1;
9967     if(twoBoards) { // [HGM] dual: switch back to one board
9968         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9969         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9970     }
9971     if (appData.debugMode) {
9972       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9973               result, resultDetails ? resultDetails : "(null)", whosays);
9974     }
9975
9976     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9977
9978     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9979         /* If we are playing on ICS, the server decides when the
9980            game is over, but the engine can offer to draw, claim
9981            a draw, or resign.
9982          */
9983 #if ZIPPY
9984         if (appData.zippyPlay && first.initDone) {
9985             if (result == GameIsDrawn) {
9986                 /* In case draw still needs to be claimed */
9987                 SendToICS(ics_prefix);
9988                 SendToICS("draw\n");
9989             } else if (StrCaseStr(resultDetails, "resign")) {
9990                 SendToICS(ics_prefix);
9991                 SendToICS("resign\n");
9992             }
9993         }
9994 #endif
9995         endingGame = 0; /* [HGM] crash */
9996         return;
9997     }
9998
9999     /* If we're loading the game from a file, stop */
10000     if (whosays == GE_FILE) {
10001       (void) StopLoadGameTimer();
10002       gameFileFP = NULL;
10003     }
10004
10005     /* Cancel draw offers */
10006     first.offeredDraw = second.offeredDraw = 0;
10007
10008     /* If this is an ICS game, only ICS can really say it's done;
10009        if not, anyone can. */
10010     isIcsGame = (gameMode == IcsPlayingWhite ||
10011                  gameMode == IcsPlayingBlack ||
10012                  gameMode == IcsObserving    ||
10013                  gameMode == IcsExamining);
10014
10015     if (!isIcsGame || whosays == GE_ICS) {
10016         /* OK -- not an ICS game, or ICS said it was done */
10017         StopClocks();
10018         if (!isIcsGame && !appData.noChessProgram)
10019           SetUserThinkingEnables();
10020
10021         /* [HGM] if a machine claims the game end we verify this claim */
10022         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10023             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10024                 char claimer;
10025                 ChessMove trueResult = (ChessMove) -1;
10026
10027                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10028                                             first.twoMachinesColor[0] :
10029                                             second.twoMachinesColor[0] ;
10030
10031                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10032                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10033                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10034                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10035                 } else
10036                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10037                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10038                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10039                 } else
10040                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10041                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10042                 }
10043
10044                 // now verify win claims, but not in drop games, as we don't understand those yet
10045                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10046                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10047                     (result == WhiteWins && claimer == 'w' ||
10048                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10049                       if (appData.debugMode) {
10050                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10051                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10052                       }
10053                       if(result != trueResult) {
10054                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10055                               result = claimer == 'w' ? BlackWins : WhiteWins;
10056                               resultDetails = buf;
10057                       }
10058                 } else
10059                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10060                     && (forwardMostMove <= backwardMostMove ||
10061                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10062                         (claimer=='b')==(forwardMostMove&1))
10063                                                                                   ) {
10064                       /* [HGM] verify: draws that were not flagged are false claims */
10065                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10066                       result = claimer == 'w' ? BlackWins : WhiteWins;
10067                       resultDetails = buf;
10068                 }
10069                 /* (Claiming a loss is accepted no questions asked!) */
10070             }
10071             /* [HGM] bare: don't allow bare King to win */
10072             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10073                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10074                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10075                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10076                && result != GameIsDrawn)
10077             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10078                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10079                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10080                         if(p >= 0 && p <= (int)WhiteKing) k++;
10081                 }
10082                 if (appData.debugMode) {
10083                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10084                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10085                 }
10086                 if(k <= 1) {
10087                         result = GameIsDrawn;
10088                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10089                         resultDetails = buf;
10090                 }
10091             }
10092         }
10093
10094
10095         if(serverMoves != NULL && !loadFlag) { char c = '=';
10096             if(result==WhiteWins) c = '+';
10097             if(result==BlackWins) c = '-';
10098             if(resultDetails != NULL)
10099                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10100         }
10101         if (resultDetails != NULL) {
10102             gameInfo.result = result;
10103             gameInfo.resultDetails = StrSave(resultDetails);
10104
10105             /* display last move only if game was not loaded from file */
10106             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10107                 DisplayMove(currentMove - 1);
10108
10109             if (forwardMostMove != 0) {
10110                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10111                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10112                                                                 ) {
10113                     if (*appData.saveGameFile != NULLCHAR) {
10114                         SaveGameToFile(appData.saveGameFile, TRUE);
10115                     } else if (appData.autoSaveGames) {
10116                         AutoSaveGame();
10117                     }
10118                     if (*appData.savePositionFile != NULLCHAR) {
10119                         SavePositionToFile(appData.savePositionFile);
10120                     }
10121                 }
10122             }
10123
10124             /* Tell program how game ended in case it is learning */
10125             /* [HGM] Moved this to after saving the PGN, just in case */
10126             /* engine died and we got here through time loss. In that */
10127             /* case we will get a fatal error writing the pipe, which */
10128             /* would otherwise lose us the PGN.                       */
10129             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10130             /* output during GameEnds should never be fatal anymore   */
10131             if (gameMode == MachinePlaysWhite ||
10132                 gameMode == MachinePlaysBlack ||
10133                 gameMode == TwoMachinesPlay ||
10134                 gameMode == IcsPlayingWhite ||
10135                 gameMode == IcsPlayingBlack ||
10136                 gameMode == BeginningOfGame) {
10137                 char buf[MSG_SIZ];
10138                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10139                         resultDetails);
10140                 if (first.pr != NoProc) {
10141                     SendToProgram(buf, &first);
10142                 }
10143                 if (second.pr != NoProc &&
10144                     gameMode == TwoMachinesPlay) {
10145                     SendToProgram(buf, &second);
10146                 }
10147             }
10148         }
10149
10150         if (appData.icsActive) {
10151             if (appData.quietPlay &&
10152                 (gameMode == IcsPlayingWhite ||
10153                  gameMode == IcsPlayingBlack)) {
10154                 SendToICS(ics_prefix);
10155                 SendToICS("set shout 1\n");
10156             }
10157             nextGameMode = IcsIdle;
10158             ics_user_moved = FALSE;
10159             /* clean up premove.  It's ugly when the game has ended and the
10160              * premove highlights are still on the board.
10161              */
10162             if (gotPremove) {
10163               gotPremove = FALSE;
10164               ClearPremoveHighlights();
10165               DrawPosition(FALSE, boards[currentMove]);
10166             }
10167             if (whosays == GE_ICS) {
10168                 switch (result) {
10169                 case WhiteWins:
10170                     if (gameMode == IcsPlayingWhite)
10171                         PlayIcsWinSound();
10172                     else if(gameMode == IcsPlayingBlack)
10173                         PlayIcsLossSound();
10174                     break;
10175                 case BlackWins:
10176                     if (gameMode == IcsPlayingBlack)
10177                         PlayIcsWinSound();
10178                     else if(gameMode == IcsPlayingWhite)
10179                         PlayIcsLossSound();
10180                     break;
10181                 case GameIsDrawn:
10182                     PlayIcsDrawSound();
10183                     break;
10184                 default:
10185                     PlayIcsUnfinishedSound();
10186                 }
10187             }
10188         } else if (gameMode == EditGame ||
10189                    gameMode == PlayFromGameFile ||
10190                    gameMode == AnalyzeMode ||
10191                    gameMode == AnalyzeFile) {
10192             nextGameMode = gameMode;
10193         } else {
10194             nextGameMode = EndOfGame;
10195         }
10196         pausing = FALSE;
10197         ModeHighlight();
10198     } else {
10199         nextGameMode = gameMode;
10200     }
10201
10202     if (appData.noChessProgram) {
10203         gameMode = nextGameMode;
10204         ModeHighlight();
10205         endingGame = 0; /* [HGM] crash */
10206         return;
10207     }
10208
10209     if (first.reuse) {
10210         /* Put first chess program into idle state */
10211         if (first.pr != NoProc &&
10212             (gameMode == MachinePlaysWhite ||
10213              gameMode == MachinePlaysBlack ||
10214              gameMode == TwoMachinesPlay ||
10215              gameMode == IcsPlayingWhite ||
10216              gameMode == IcsPlayingBlack ||
10217              gameMode == BeginningOfGame)) {
10218             SendToProgram("force\n", &first);
10219             if (first.usePing) {
10220               char buf[MSG_SIZ];
10221               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10222               SendToProgram(buf, &first);
10223             }
10224         }
10225     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10226         /* Kill off first chess program */
10227         if (first.isr != NULL)
10228           RemoveInputSource(first.isr);
10229         first.isr = NULL;
10230
10231         if (first.pr != NoProc) {
10232             ExitAnalyzeMode();
10233             DoSleep( appData.delayBeforeQuit );
10234             SendToProgram("quit\n", &first);
10235             DoSleep( appData.delayAfterQuit );
10236             DestroyChildProcess(first.pr, first.useSigterm);
10237         }
10238         first.pr = NoProc;
10239     }
10240     if (second.reuse) {
10241         /* Put second chess program into idle state */
10242         if (second.pr != NoProc &&
10243             gameMode == TwoMachinesPlay) {
10244             SendToProgram("force\n", &second);
10245             if (second.usePing) {
10246               char buf[MSG_SIZ];
10247               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10248               SendToProgram(buf, &second);
10249             }
10250         }
10251     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10252         /* Kill off second chess program */
10253         if (second.isr != NULL)
10254           RemoveInputSource(second.isr);
10255         second.isr = NULL;
10256
10257         if (second.pr != NoProc) {
10258             DoSleep( appData.delayBeforeQuit );
10259             SendToProgram("quit\n", &second);
10260             DoSleep( appData.delayAfterQuit );
10261             DestroyChildProcess(second.pr, second.useSigterm);
10262         }
10263         second.pr = NoProc;
10264     }
10265
10266     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10267         char resChar = '=';
10268         switch (result) {
10269         case WhiteWins:
10270           resChar = '+';
10271           if (first.twoMachinesColor[0] == 'w') {
10272             first.matchWins++;
10273           } else {
10274             second.matchWins++;
10275           }
10276           break;
10277         case BlackWins:
10278           resChar = '-';
10279           if (first.twoMachinesColor[0] == 'b') {
10280             first.matchWins++;
10281           } else {
10282             second.matchWins++;
10283           }
10284           break;
10285         case GameUnfinished:
10286           resChar = ' ';
10287         default:
10288           break;
10289         }
10290
10291         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10292         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10293             ReserveGame(nextGame, resChar); // sets nextGame
10294             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10295             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10296         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10297
10298         if (nextGame <= appData.matchGames && !abortMatch) {
10299             gameMode = nextGameMode;
10300             matchGame = nextGame; // this will be overruled in tourney mode!
10301             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10302             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10303             endingGame = 0; /* [HGM] crash */
10304             return;
10305         } else {
10306             gameMode = nextGameMode;
10307             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10308                      first.tidy, second.tidy,
10309                      first.matchWins, second.matchWins,
10310                      appData.matchGames - (first.matchWins + second.matchWins));
10311             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10312             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10313             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10314                 first.twoMachinesColor = "black\n";
10315                 second.twoMachinesColor = "white\n";
10316             } else {
10317                 first.twoMachinesColor = "white\n";
10318                 second.twoMachinesColor = "black\n";
10319             }
10320         }
10321     }
10322     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10323         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10324       ExitAnalyzeMode();
10325     gameMode = nextGameMode;
10326     ModeHighlight();
10327     endingGame = 0;  /* [HGM] crash */
10328     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10329         if(matchMode == TRUE) { // match through command line: exit with or without popup
10330             if(ranking) {
10331                 ToNrEvent(forwardMostMove);
10332                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10333                 else ExitEvent(0);
10334             } else DisplayFatalError(buf, 0, 0);
10335         } else { // match through menu; just stop, with or without popup
10336             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10337             ModeHighlight();
10338             if(ranking){
10339                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10340             } else DisplayNote(buf);
10341       }
10342       if(ranking) free(ranking);
10343     }
10344 }
10345
10346 /* Assumes program was just initialized (initString sent).
10347    Leaves program in force mode. */
10348 void
10349 FeedMovesToProgram(cps, upto)
10350      ChessProgramState *cps;
10351      int upto;
10352 {
10353     int i;
10354
10355     if (appData.debugMode)
10356       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10357               startedFromSetupPosition ? "position and " : "",
10358               backwardMostMove, upto, cps->which);
10359     if(currentlyInitializedVariant != gameInfo.variant) {
10360       char buf[MSG_SIZ];
10361         // [HGM] variantswitch: make engine aware of new variant
10362         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10363                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10364         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10365         SendToProgram(buf, cps);
10366         currentlyInitializedVariant = gameInfo.variant;
10367     }
10368     SendToProgram("force\n", cps);
10369     if (startedFromSetupPosition) {
10370         SendBoard(cps, backwardMostMove);
10371     if (appData.debugMode) {
10372         fprintf(debugFP, "feedMoves\n");
10373     }
10374     }
10375     for (i = backwardMostMove; i < upto; i++) {
10376         SendMoveToProgram(i, cps);
10377     }
10378 }
10379
10380
10381 int
10382 ResurrectChessProgram()
10383 {
10384      /* The chess program may have exited.
10385         If so, restart it and feed it all the moves made so far. */
10386     static int doInit = 0;
10387
10388     if (appData.noChessProgram) return 1;
10389
10390     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10391         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10392         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10393         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10394     } else {
10395         if (first.pr != NoProc) return 1;
10396         StartChessProgram(&first);
10397     }
10398     InitChessProgram(&first, FALSE);
10399     FeedMovesToProgram(&first, currentMove);
10400
10401     if (!first.sendTime) {
10402         /* can't tell gnuchess what its clock should read,
10403            so we bow to its notion. */
10404         ResetClocks();
10405         timeRemaining[0][currentMove] = whiteTimeRemaining;
10406         timeRemaining[1][currentMove] = blackTimeRemaining;
10407     }
10408
10409     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10410                 appData.icsEngineAnalyze) && first.analysisSupport) {
10411       SendToProgram("analyze\n", &first);
10412       first.analyzing = TRUE;
10413     }
10414     return 1;
10415 }
10416
10417 /*
10418  * Button procedures
10419  */
10420 void
10421 Reset(redraw, init)
10422      int redraw, init;
10423 {
10424     int i;
10425
10426     if (appData.debugMode) {
10427         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10428                 redraw, init, gameMode);
10429     }
10430     CleanupTail(); // [HGM] vari: delete any stored variations
10431     pausing = pauseExamInvalid = FALSE;
10432     startedFromSetupPosition = blackPlaysFirst = FALSE;
10433     firstMove = TRUE;
10434     whiteFlag = blackFlag = FALSE;
10435     userOfferedDraw = FALSE;
10436     hintRequested = bookRequested = FALSE;
10437     first.maybeThinking = FALSE;
10438     second.maybeThinking = FALSE;
10439     first.bookSuspend = FALSE; // [HGM] book
10440     second.bookSuspend = FALSE;
10441     thinkOutput[0] = NULLCHAR;
10442     lastHint[0] = NULLCHAR;
10443     ClearGameInfo(&gameInfo);
10444     gameInfo.variant = StringToVariant(appData.variant);
10445     ics_user_moved = ics_clock_paused = FALSE;
10446     ics_getting_history = H_FALSE;
10447     ics_gamenum = -1;
10448     white_holding[0] = black_holding[0] = NULLCHAR;
10449     ClearProgramStats();
10450     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10451
10452     ResetFrontEnd();
10453     ClearHighlights();
10454     flipView = appData.flipView;
10455     ClearPremoveHighlights();
10456     gotPremove = FALSE;
10457     alarmSounded = FALSE;
10458
10459     GameEnds(EndOfFile, NULL, GE_PLAYER);
10460     if(appData.serverMovesName != NULL) {
10461         /* [HGM] prepare to make moves file for broadcasting */
10462         clock_t t = clock();
10463         if(serverMoves != NULL) fclose(serverMoves);
10464         serverMoves = fopen(appData.serverMovesName, "r");
10465         if(serverMoves != NULL) {
10466             fclose(serverMoves);
10467             /* delay 15 sec before overwriting, so all clients can see end */
10468             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10469         }
10470         serverMoves = fopen(appData.serverMovesName, "w");
10471     }
10472
10473     ExitAnalyzeMode();
10474     gameMode = BeginningOfGame;
10475     ModeHighlight();
10476     if(appData.icsActive) gameInfo.variant = VariantNormal;
10477     currentMove = forwardMostMove = backwardMostMove = 0;
10478     InitPosition(redraw);
10479     for (i = 0; i < MAX_MOVES; i++) {
10480         if (commentList[i] != NULL) {
10481             free(commentList[i]);
10482             commentList[i] = NULL;
10483         }
10484     }
10485     ResetClocks();
10486     timeRemaining[0][0] = whiteTimeRemaining;
10487     timeRemaining[1][0] = blackTimeRemaining;
10488
10489     if (first.pr == NULL) {
10490         StartChessProgram(&first);
10491     }
10492     if (init) {
10493             InitChessProgram(&first, startedFromSetupPosition);
10494     }
10495     DisplayTitle("");
10496     DisplayMessage("", "");
10497     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10498     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10499 }
10500
10501 void
10502 AutoPlayGameLoop()
10503 {
10504     for (;;) {
10505         if (!AutoPlayOneMove())
10506           return;
10507         if (matchMode || appData.timeDelay == 0)
10508           continue;
10509         if (appData.timeDelay < 0)
10510           return;
10511         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10512         break;
10513     }
10514 }
10515
10516
10517 int
10518 AutoPlayOneMove()
10519 {
10520     int fromX, fromY, toX, toY;
10521
10522     if (appData.debugMode) {
10523       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10524     }
10525
10526     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10527       return FALSE;
10528
10529     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10530       pvInfoList[currentMove].depth = programStats.depth;
10531       pvInfoList[currentMove].score = programStats.score;
10532       pvInfoList[currentMove].time  = 0;
10533       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10534     }
10535
10536     if (currentMove >= forwardMostMove) {
10537       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10538       gameMode = EditGame;
10539       ModeHighlight();
10540
10541       /* [AS] Clear current move marker at the end of a game */
10542       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10543
10544       return FALSE;
10545     }
10546
10547     toX = moveList[currentMove][2] - AAA;
10548     toY = moveList[currentMove][3] - ONE;
10549
10550     if (moveList[currentMove][1] == '@') {
10551         if (appData.highlightLastMove) {
10552             SetHighlights(-1, -1, toX, toY);
10553         }
10554     } else {
10555         fromX = moveList[currentMove][0] - AAA;
10556         fromY = moveList[currentMove][1] - ONE;
10557
10558         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10559
10560         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10561
10562         if (appData.highlightLastMove) {
10563             SetHighlights(fromX, fromY, toX, toY);
10564         }
10565     }
10566     DisplayMove(currentMove);
10567     SendMoveToProgram(currentMove++, &first);
10568     DisplayBothClocks();
10569     DrawPosition(FALSE, boards[currentMove]);
10570     // [HGM] PV info: always display, routine tests if empty
10571     DisplayComment(currentMove - 1, commentList[currentMove]);
10572     return TRUE;
10573 }
10574
10575
10576 int
10577 LoadGameOneMove(readAhead)
10578      ChessMove readAhead;
10579 {
10580     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10581     char promoChar = NULLCHAR;
10582     ChessMove moveType;
10583     char move[MSG_SIZ];
10584     char *p, *q;
10585
10586     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10587         gameMode != AnalyzeMode && gameMode != Training) {
10588         gameFileFP = NULL;
10589         return FALSE;
10590     }
10591
10592     yyboardindex = forwardMostMove;
10593     if (readAhead != EndOfFile) {
10594       moveType = readAhead;
10595     } else {
10596       if (gameFileFP == NULL)
10597           return FALSE;
10598       moveType = (ChessMove) Myylex();
10599     }
10600
10601     done = FALSE;
10602     switch (moveType) {
10603       case Comment:
10604         if (appData.debugMode)
10605           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10606         p = yy_text;
10607
10608         /* append the comment but don't display it */
10609         AppendComment(currentMove, p, FALSE);
10610         return TRUE;
10611
10612       case WhiteCapturesEnPassant:
10613       case BlackCapturesEnPassant:
10614       case WhitePromotion:
10615       case BlackPromotion:
10616       case WhiteNonPromotion:
10617       case BlackNonPromotion:
10618       case NormalMove:
10619       case WhiteKingSideCastle:
10620       case WhiteQueenSideCastle:
10621       case BlackKingSideCastle:
10622       case BlackQueenSideCastle:
10623       case WhiteKingSideCastleWild:
10624       case WhiteQueenSideCastleWild:
10625       case BlackKingSideCastleWild:
10626       case BlackQueenSideCastleWild:
10627       /* PUSH Fabien */
10628       case WhiteHSideCastleFR:
10629       case WhiteASideCastleFR:
10630       case BlackHSideCastleFR:
10631       case BlackASideCastleFR:
10632       /* POP Fabien */
10633         if (appData.debugMode)
10634           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10635         fromX = currentMoveString[0] - AAA;
10636         fromY = currentMoveString[1] - ONE;
10637         toX = currentMoveString[2] - AAA;
10638         toY = currentMoveString[3] - ONE;
10639         promoChar = currentMoveString[4];
10640         break;
10641
10642       case WhiteDrop:
10643       case BlackDrop:
10644         if (appData.debugMode)
10645           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10646         fromX = moveType == WhiteDrop ?
10647           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10648         (int) CharToPiece(ToLower(currentMoveString[0]));
10649         fromY = DROP_RANK;
10650         toX = currentMoveString[2] - AAA;
10651         toY = currentMoveString[3] - ONE;
10652         break;
10653
10654       case WhiteWins:
10655       case BlackWins:
10656       case GameIsDrawn:
10657       case GameUnfinished:
10658         if (appData.debugMode)
10659           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10660         p = strchr(yy_text, '{');
10661         if (p == NULL) p = strchr(yy_text, '(');
10662         if (p == NULL) {
10663             p = yy_text;
10664             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10665         } else {
10666             q = strchr(p, *p == '{' ? '}' : ')');
10667             if (q != NULL) *q = NULLCHAR;
10668             p++;
10669         }
10670         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10671         GameEnds(moveType, p, GE_FILE);
10672         done = TRUE;
10673         if (cmailMsgLoaded) {
10674             ClearHighlights();
10675             flipView = WhiteOnMove(currentMove);
10676             if (moveType == GameUnfinished) flipView = !flipView;
10677             if (appData.debugMode)
10678               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10679         }
10680         break;
10681
10682       case EndOfFile:
10683         if (appData.debugMode)
10684           fprintf(debugFP, "Parser hit end of file\n");
10685         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10686           case MT_NONE:
10687           case MT_CHECK:
10688             break;
10689           case MT_CHECKMATE:
10690           case MT_STAINMATE:
10691             if (WhiteOnMove(currentMove)) {
10692                 GameEnds(BlackWins, "Black mates", GE_FILE);
10693             } else {
10694                 GameEnds(WhiteWins, "White mates", GE_FILE);
10695             }
10696             break;
10697           case MT_STALEMATE:
10698             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10699             break;
10700         }
10701         done = TRUE;
10702         break;
10703
10704       case MoveNumberOne:
10705         if (lastLoadGameStart == GNUChessGame) {
10706             /* GNUChessGames have numbers, but they aren't move numbers */
10707             if (appData.debugMode)
10708               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10709                       yy_text, (int) moveType);
10710             return LoadGameOneMove(EndOfFile); /* tail recursion */
10711         }
10712         /* else fall thru */
10713
10714       case XBoardGame:
10715       case GNUChessGame:
10716       case PGNTag:
10717         /* Reached start of next game in file */
10718         if (appData.debugMode)
10719           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10720         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10721           case MT_NONE:
10722           case MT_CHECK:
10723             break;
10724           case MT_CHECKMATE:
10725           case MT_STAINMATE:
10726             if (WhiteOnMove(currentMove)) {
10727                 GameEnds(BlackWins, "Black mates", GE_FILE);
10728             } else {
10729                 GameEnds(WhiteWins, "White mates", GE_FILE);
10730             }
10731             break;
10732           case MT_STALEMATE:
10733             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10734             break;
10735         }
10736         done = TRUE;
10737         break;
10738
10739       case PositionDiagram:     /* should not happen; ignore */
10740       case ElapsedTime:         /* ignore */
10741       case NAG:                 /* ignore */
10742         if (appData.debugMode)
10743           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10744                   yy_text, (int) moveType);
10745         return LoadGameOneMove(EndOfFile); /* tail recursion */
10746
10747       case IllegalMove:
10748         if (appData.testLegality) {
10749             if (appData.debugMode)
10750               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10751             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10752                     (forwardMostMove / 2) + 1,
10753                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10754             DisplayError(move, 0);
10755             done = TRUE;
10756         } else {
10757             if (appData.debugMode)
10758               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10759                       yy_text, currentMoveString);
10760             fromX = currentMoveString[0] - AAA;
10761             fromY = currentMoveString[1] - ONE;
10762             toX = currentMoveString[2] - AAA;
10763             toY = currentMoveString[3] - ONE;
10764             promoChar = currentMoveString[4];
10765         }
10766         break;
10767
10768       case AmbiguousMove:
10769         if (appData.debugMode)
10770           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10771         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10772                 (forwardMostMove / 2) + 1,
10773                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10774         DisplayError(move, 0);
10775         done = TRUE;
10776         break;
10777
10778       default:
10779       case ImpossibleMove:
10780         if (appData.debugMode)
10781           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10782         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10783                 (forwardMostMove / 2) + 1,
10784                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10785         DisplayError(move, 0);
10786         done = TRUE;
10787         break;
10788     }
10789
10790     if (done) {
10791         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10792             DrawPosition(FALSE, boards[currentMove]);
10793             DisplayBothClocks();
10794             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10795               DisplayComment(currentMove - 1, commentList[currentMove]);
10796         }
10797         (void) StopLoadGameTimer();
10798         gameFileFP = NULL;
10799         cmailOldMove = forwardMostMove;
10800         return FALSE;
10801     } else {
10802         /* currentMoveString is set as a side-effect of yylex */
10803
10804         thinkOutput[0] = NULLCHAR;
10805         MakeMove(fromX, fromY, toX, toY, promoChar);
10806         currentMove = forwardMostMove;
10807         return TRUE;
10808     }
10809 }
10810
10811 /* Load the nth game from the given file */
10812 int
10813 LoadGameFromFile(filename, n, title, useList)
10814      char *filename;
10815      int n;
10816      char *title;
10817      /*Boolean*/ int useList;
10818 {
10819     FILE *f;
10820     char buf[MSG_SIZ];
10821
10822     if (strcmp(filename, "-") == 0) {
10823         f = stdin;
10824         title = "stdin";
10825     } else {
10826         f = fopen(filename, "rb");
10827         if (f == NULL) {
10828           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10829             DisplayError(buf, errno);
10830             return FALSE;
10831         }
10832     }
10833     if (fseek(f, 0, 0) == -1) {
10834         /* f is not seekable; probably a pipe */
10835         useList = FALSE;
10836     }
10837     if (useList && n == 0) {
10838         int error = GameListBuild(f);
10839         if (error) {
10840             DisplayError(_("Cannot build game list"), error);
10841         } else if (!ListEmpty(&gameList) &&
10842                    ((ListGame *) gameList.tailPred)->number > 1) {
10843             GameListPopUp(f, title);
10844             return TRUE;
10845         }
10846         GameListDestroy();
10847         n = 1;
10848     }
10849     if (n == 0) n = 1;
10850     return LoadGame(f, n, title, FALSE);
10851 }
10852
10853
10854 void
10855 MakeRegisteredMove()
10856 {
10857     int fromX, fromY, toX, toY;
10858     char promoChar;
10859     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10860         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10861           case CMAIL_MOVE:
10862           case CMAIL_DRAW:
10863             if (appData.debugMode)
10864               fprintf(debugFP, "Restoring %s for game %d\n",
10865                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10866
10867             thinkOutput[0] = NULLCHAR;
10868             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10869             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10870             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10871             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10872             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10873             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10874             MakeMove(fromX, fromY, toX, toY, promoChar);
10875             ShowMove(fromX, fromY, toX, toY);
10876
10877             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10878               case MT_NONE:
10879               case MT_CHECK:
10880                 break;
10881
10882               case MT_CHECKMATE:
10883               case MT_STAINMATE:
10884                 if (WhiteOnMove(currentMove)) {
10885                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10886                 } else {
10887                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10888                 }
10889                 break;
10890
10891               case MT_STALEMATE:
10892                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10893                 break;
10894             }
10895
10896             break;
10897
10898           case CMAIL_RESIGN:
10899             if (WhiteOnMove(currentMove)) {
10900                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10901             } else {
10902                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10903             }
10904             break;
10905
10906           case CMAIL_ACCEPT:
10907             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10908             break;
10909
10910           default:
10911             break;
10912         }
10913     }
10914
10915     return;
10916 }
10917
10918 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10919 int
10920 CmailLoadGame(f, gameNumber, title, useList)
10921      FILE *f;
10922      int gameNumber;
10923      char *title;
10924      int useList;
10925 {
10926     int retVal;
10927
10928     if (gameNumber > nCmailGames) {
10929         DisplayError(_("No more games in this message"), 0);
10930         return FALSE;
10931     }
10932     if (f == lastLoadGameFP) {
10933         int offset = gameNumber - lastLoadGameNumber;
10934         if (offset == 0) {
10935             cmailMsg[0] = NULLCHAR;
10936             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10937                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10938                 nCmailMovesRegistered--;
10939             }
10940             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10941             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10942                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10943             }
10944         } else {
10945             if (! RegisterMove()) return FALSE;
10946         }
10947     }
10948
10949     retVal = LoadGame(f, gameNumber, title, useList);
10950
10951     /* Make move registered during previous look at this game, if any */
10952     MakeRegisteredMove();
10953
10954     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10955         commentList[currentMove]
10956           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10957         DisplayComment(currentMove - 1, commentList[currentMove]);
10958     }
10959
10960     return retVal;
10961 }
10962
10963 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10964 int
10965 ReloadGame(offset)
10966      int offset;
10967 {
10968     int gameNumber = lastLoadGameNumber + offset;
10969     if (lastLoadGameFP == NULL) {
10970         DisplayError(_("No game has been loaded yet"), 0);
10971         return FALSE;
10972     }
10973     if (gameNumber <= 0) {
10974         DisplayError(_("Can't back up any further"), 0);
10975         return FALSE;
10976     }
10977     if (cmailMsgLoaded) {
10978         return CmailLoadGame(lastLoadGameFP, gameNumber,
10979                              lastLoadGameTitle, lastLoadGameUseList);
10980     } else {
10981         return LoadGame(lastLoadGameFP, gameNumber,
10982                         lastLoadGameTitle, lastLoadGameUseList);
10983     }
10984 }
10985
10986
10987
10988 /* Load the nth game from open file f */
10989 int
10990 LoadGame(f, gameNumber, title, useList)
10991      FILE *f;
10992      int gameNumber;
10993      char *title;
10994      int useList;
10995 {
10996     ChessMove cm;
10997     char buf[MSG_SIZ];
10998     int gn = gameNumber;
10999     ListGame *lg = NULL;
11000     int numPGNTags = 0;
11001     int err;
11002     GameMode oldGameMode;
11003     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11004
11005     if (appData.debugMode)
11006         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11007
11008     if (gameMode == Training )
11009         SetTrainingModeOff();
11010
11011     oldGameMode = gameMode;
11012     if (gameMode != BeginningOfGame) {
11013       Reset(FALSE, TRUE);
11014     }
11015
11016     gameFileFP = f;
11017     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11018         fclose(lastLoadGameFP);
11019     }
11020
11021     if (useList) {
11022         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11023
11024         if (lg) {
11025             fseek(f, lg->offset, 0);
11026             GameListHighlight(gameNumber);
11027             gn = 1;
11028         }
11029         else {
11030             DisplayError(_("Game number out of range"), 0);
11031             return FALSE;
11032         }
11033     } else {
11034         GameListDestroy();
11035         if (fseek(f, 0, 0) == -1) {
11036             if (f == lastLoadGameFP ?
11037                 gameNumber == lastLoadGameNumber + 1 :
11038                 gameNumber == 1) {
11039                 gn = 1;
11040             } else {
11041                 DisplayError(_("Can't seek on game file"), 0);
11042                 return FALSE;
11043             }
11044         }
11045     }
11046     lastLoadGameFP = f;
11047     lastLoadGameNumber = gameNumber;
11048     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11049     lastLoadGameUseList = useList;
11050
11051     yynewfile(f);
11052
11053     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11054       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11055                 lg->gameInfo.black);
11056             DisplayTitle(buf);
11057     } else if (*title != NULLCHAR) {
11058         if (gameNumber > 1) {
11059           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11060             DisplayTitle(buf);
11061         } else {
11062             DisplayTitle(title);
11063         }
11064     }
11065
11066     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11067         gameMode = PlayFromGameFile;
11068         ModeHighlight();
11069     }
11070
11071     currentMove = forwardMostMove = backwardMostMove = 0;
11072     CopyBoard(boards[0], initialPosition);
11073     StopClocks();
11074
11075     /*
11076      * Skip the first gn-1 games in the file.
11077      * Also skip over anything that precedes an identifiable
11078      * start of game marker, to avoid being confused by
11079      * garbage at the start of the file.  Currently
11080      * recognized start of game markers are the move number "1",
11081      * the pattern "gnuchess .* game", the pattern
11082      * "^[#;%] [^ ]* game file", and a PGN tag block.
11083      * A game that starts with one of the latter two patterns
11084      * will also have a move number 1, possibly
11085      * following a position diagram.
11086      * 5-4-02: Let's try being more lenient and allowing a game to
11087      * start with an unnumbered move.  Does that break anything?
11088      */
11089     cm = lastLoadGameStart = EndOfFile;
11090     while (gn > 0) {
11091         yyboardindex = forwardMostMove;
11092         cm = (ChessMove) Myylex();
11093         switch (cm) {
11094           case EndOfFile:
11095             if (cmailMsgLoaded) {
11096                 nCmailGames = CMAIL_MAX_GAMES - gn;
11097             } else {
11098                 Reset(TRUE, TRUE);
11099                 DisplayError(_("Game not found in file"), 0);
11100             }
11101             return FALSE;
11102
11103           case GNUChessGame:
11104           case XBoardGame:
11105             gn--;
11106             lastLoadGameStart = cm;
11107             break;
11108
11109           case MoveNumberOne:
11110             switch (lastLoadGameStart) {
11111               case GNUChessGame:
11112               case XBoardGame:
11113               case PGNTag:
11114                 break;
11115               case MoveNumberOne:
11116               case EndOfFile:
11117                 gn--;           /* count this game */
11118                 lastLoadGameStart = cm;
11119                 break;
11120               default:
11121                 /* impossible */
11122                 break;
11123             }
11124             break;
11125
11126           case PGNTag:
11127             switch (lastLoadGameStart) {
11128               case GNUChessGame:
11129               case PGNTag:
11130               case MoveNumberOne:
11131               case EndOfFile:
11132                 gn--;           /* count this game */
11133                 lastLoadGameStart = cm;
11134                 break;
11135               case XBoardGame:
11136                 lastLoadGameStart = cm; /* game counted already */
11137                 break;
11138               default:
11139                 /* impossible */
11140                 break;
11141             }
11142             if (gn > 0) {
11143                 do {
11144                     yyboardindex = forwardMostMove;
11145                     cm = (ChessMove) Myylex();
11146                 } while (cm == PGNTag || cm == Comment);
11147             }
11148             break;
11149
11150           case WhiteWins:
11151           case BlackWins:
11152           case GameIsDrawn:
11153             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11154                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11155                     != CMAIL_OLD_RESULT) {
11156                     nCmailResults ++ ;
11157                     cmailResult[  CMAIL_MAX_GAMES
11158                                 - gn - 1] = CMAIL_OLD_RESULT;
11159                 }
11160             }
11161             break;
11162
11163           case NormalMove:
11164             /* Only a NormalMove can be at the start of a game
11165              * without a position diagram. */
11166             if (lastLoadGameStart == EndOfFile ) {
11167               gn--;
11168               lastLoadGameStart = MoveNumberOne;
11169             }
11170             break;
11171
11172           default:
11173             break;
11174         }
11175     }
11176
11177     if (appData.debugMode)
11178       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11179
11180     if (cm == XBoardGame) {
11181         /* Skip any header junk before position diagram and/or move 1 */
11182         for (;;) {
11183             yyboardindex = forwardMostMove;
11184             cm = (ChessMove) Myylex();
11185
11186             if (cm == EndOfFile ||
11187                 cm == GNUChessGame || cm == XBoardGame) {
11188                 /* Empty game; pretend end-of-file and handle later */
11189                 cm = EndOfFile;
11190                 break;
11191             }
11192
11193             if (cm == MoveNumberOne || cm == PositionDiagram ||
11194                 cm == PGNTag || cm == Comment)
11195               break;
11196         }
11197     } else if (cm == GNUChessGame) {
11198         if (gameInfo.event != NULL) {
11199             free(gameInfo.event);
11200         }
11201         gameInfo.event = StrSave(yy_text);
11202     }
11203
11204     startedFromSetupPosition = FALSE;
11205     while (cm == PGNTag) {
11206         if (appData.debugMode)
11207           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11208         err = ParsePGNTag(yy_text, &gameInfo);
11209         if (!err) numPGNTags++;
11210
11211         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11212         if(gameInfo.variant != oldVariant) {
11213             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11214             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11215             InitPosition(TRUE);
11216             oldVariant = gameInfo.variant;
11217             if (appData.debugMode)
11218               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11219         }
11220
11221
11222         if (gameInfo.fen != NULL) {
11223           Board initial_position;
11224           startedFromSetupPosition = TRUE;
11225           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11226             Reset(TRUE, TRUE);
11227             DisplayError(_("Bad FEN position in file"), 0);
11228             return FALSE;
11229           }
11230           CopyBoard(boards[0], initial_position);
11231           if (blackPlaysFirst) {
11232             currentMove = forwardMostMove = backwardMostMove = 1;
11233             CopyBoard(boards[1], initial_position);
11234             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11235             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11236             timeRemaining[0][1] = whiteTimeRemaining;
11237             timeRemaining[1][1] = blackTimeRemaining;
11238             if (commentList[0] != NULL) {
11239               commentList[1] = commentList[0];
11240               commentList[0] = NULL;
11241             }
11242           } else {
11243             currentMove = forwardMostMove = backwardMostMove = 0;
11244           }
11245           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11246           {   int i;
11247               initialRulePlies = FENrulePlies;
11248               for( i=0; i< nrCastlingRights; i++ )
11249                   initialRights[i] = initial_position[CASTLING][i];
11250           }
11251           yyboardindex = forwardMostMove;
11252           free(gameInfo.fen);
11253           gameInfo.fen = NULL;
11254         }
11255
11256         yyboardindex = forwardMostMove;
11257         cm = (ChessMove) Myylex();
11258
11259         /* Handle comments interspersed among the tags */
11260         while (cm == Comment) {
11261             char *p;
11262             if (appData.debugMode)
11263               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11264             p = yy_text;
11265             AppendComment(currentMove, p, FALSE);
11266             yyboardindex = forwardMostMove;
11267             cm = (ChessMove) Myylex();
11268         }
11269     }
11270
11271     /* don't rely on existence of Event tag since if game was
11272      * pasted from clipboard the Event tag may not exist
11273      */
11274     if (numPGNTags > 0){
11275         char *tags;
11276         if (gameInfo.variant == VariantNormal) {
11277           VariantClass v = StringToVariant(gameInfo.event);
11278           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11279           if(v < VariantShogi) gameInfo.variant = v;
11280         }
11281         if (!matchMode) {
11282           if( appData.autoDisplayTags ) {
11283             tags = PGNTags(&gameInfo);
11284             TagsPopUp(tags, CmailMsg());
11285             free(tags);
11286           }
11287         }
11288     } else {
11289         /* Make something up, but don't display it now */
11290         SetGameInfo();
11291         TagsPopDown();
11292     }
11293
11294     if (cm == PositionDiagram) {
11295         int i, j;
11296         char *p;
11297         Board initial_position;
11298
11299         if (appData.debugMode)
11300           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11301
11302         if (!startedFromSetupPosition) {
11303             p = yy_text;
11304             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11305               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11306                 switch (*p) {
11307                   case '{':
11308                   case '[':
11309                   case '-':
11310                   case ' ':
11311                   case '\t':
11312                   case '\n':
11313                   case '\r':
11314                     break;
11315                   default:
11316                     initial_position[i][j++] = CharToPiece(*p);
11317                     break;
11318                 }
11319             while (*p == ' ' || *p == '\t' ||
11320                    *p == '\n' || *p == '\r') p++;
11321
11322             if (strncmp(p, "black", strlen("black"))==0)
11323               blackPlaysFirst = TRUE;
11324             else
11325               blackPlaysFirst = FALSE;
11326             startedFromSetupPosition = TRUE;
11327
11328             CopyBoard(boards[0], initial_position);
11329             if (blackPlaysFirst) {
11330                 currentMove = forwardMostMove = backwardMostMove = 1;
11331                 CopyBoard(boards[1], initial_position);
11332                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11333                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11334                 timeRemaining[0][1] = whiteTimeRemaining;
11335                 timeRemaining[1][1] = blackTimeRemaining;
11336                 if (commentList[0] != NULL) {
11337                     commentList[1] = commentList[0];
11338                     commentList[0] = NULL;
11339                 }
11340             } else {
11341                 currentMove = forwardMostMove = backwardMostMove = 0;
11342             }
11343         }
11344         yyboardindex = forwardMostMove;
11345         cm = (ChessMove) Myylex();
11346     }
11347
11348     if (first.pr == NoProc) {
11349         StartChessProgram(&first);
11350     }
11351     InitChessProgram(&first, FALSE);
11352     SendToProgram("force\n", &first);
11353     if (startedFromSetupPosition) {
11354         SendBoard(&first, forwardMostMove);
11355     if (appData.debugMode) {
11356         fprintf(debugFP, "Load Game\n");
11357     }
11358         DisplayBothClocks();
11359     }
11360
11361     /* [HGM] server: flag to write setup moves in broadcast file as one */
11362     loadFlag = appData.suppressLoadMoves;
11363
11364     while (cm == Comment) {
11365         char *p;
11366         if (appData.debugMode)
11367           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11368         p = yy_text;
11369         AppendComment(currentMove, p, FALSE);
11370         yyboardindex = forwardMostMove;
11371         cm = (ChessMove) Myylex();
11372     }
11373
11374     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11375         cm == WhiteWins || cm == BlackWins ||
11376         cm == GameIsDrawn || cm == GameUnfinished) {
11377         DisplayMessage("", _("No moves in game"));
11378         if (cmailMsgLoaded) {
11379             if (appData.debugMode)
11380               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11381             ClearHighlights();
11382             flipView = FALSE;
11383         }
11384         DrawPosition(FALSE, boards[currentMove]);
11385         DisplayBothClocks();
11386         gameMode = EditGame;
11387         ModeHighlight();
11388         gameFileFP = NULL;
11389         cmailOldMove = 0;
11390         return TRUE;
11391     }
11392
11393     // [HGM] PV info: routine tests if comment empty
11394     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11395         DisplayComment(currentMove - 1, commentList[currentMove]);
11396     }
11397     if (!matchMode && appData.timeDelay != 0)
11398       DrawPosition(FALSE, boards[currentMove]);
11399
11400     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11401       programStats.ok_to_send = 1;
11402     }
11403
11404     /* if the first token after the PGN tags is a move
11405      * and not move number 1, retrieve it from the parser
11406      */
11407     if (cm != MoveNumberOne)
11408         LoadGameOneMove(cm);
11409
11410     /* load the remaining moves from the file */
11411     while (LoadGameOneMove(EndOfFile)) {
11412       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11413       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11414     }
11415
11416     /* rewind to the start of the game */
11417     currentMove = backwardMostMove;
11418
11419     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11420
11421     if (oldGameMode == AnalyzeFile ||
11422         oldGameMode == AnalyzeMode) {
11423       AnalyzeFileEvent();
11424     }
11425
11426     if (matchMode || appData.timeDelay == 0) {
11427       ToEndEvent();
11428       gameMode = EditGame;
11429       ModeHighlight();
11430     } else if (appData.timeDelay > 0) {
11431       AutoPlayGameLoop();
11432     }
11433
11434     if (appData.debugMode)
11435         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11436
11437     loadFlag = 0; /* [HGM] true game starts */
11438     return TRUE;
11439 }
11440
11441 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11442 int
11443 ReloadPosition(offset)
11444      int offset;
11445 {
11446     int positionNumber = lastLoadPositionNumber + offset;
11447     if (lastLoadPositionFP == NULL) {
11448         DisplayError(_("No position has been loaded yet"), 0);
11449         return FALSE;
11450     }
11451     if (positionNumber <= 0) {
11452         DisplayError(_("Can't back up any further"), 0);
11453         return FALSE;
11454     }
11455     return LoadPosition(lastLoadPositionFP, positionNumber,
11456                         lastLoadPositionTitle);
11457 }
11458
11459 /* Load the nth position from the given file */
11460 int
11461 LoadPositionFromFile(filename, n, title)
11462      char *filename;
11463      int n;
11464      char *title;
11465 {
11466     FILE *f;
11467     char buf[MSG_SIZ];
11468
11469     if (strcmp(filename, "-") == 0) {
11470         return LoadPosition(stdin, n, "stdin");
11471     } else {
11472         f = fopen(filename, "rb");
11473         if (f == NULL) {
11474             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11475             DisplayError(buf, errno);
11476             return FALSE;
11477         } else {
11478             return LoadPosition(f, n, title);
11479         }
11480     }
11481 }
11482
11483 /* Load the nth position from the given open file, and close it */
11484 int
11485 LoadPosition(f, positionNumber, title)
11486      FILE *f;
11487      int positionNumber;
11488      char *title;
11489 {
11490     char *p, line[MSG_SIZ];
11491     Board initial_position;
11492     int i, j, fenMode, pn;
11493
11494     if (gameMode == Training )
11495         SetTrainingModeOff();
11496
11497     if (gameMode != BeginningOfGame) {
11498         Reset(FALSE, TRUE);
11499     }
11500     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11501         fclose(lastLoadPositionFP);
11502     }
11503     if (positionNumber == 0) positionNumber = 1;
11504     lastLoadPositionFP = f;
11505     lastLoadPositionNumber = positionNumber;
11506     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11507     if (first.pr == NoProc) {
11508       StartChessProgram(&first);
11509       InitChessProgram(&first, FALSE);
11510     }
11511     pn = positionNumber;
11512     if (positionNumber < 0) {
11513         /* Negative position number means to seek to that byte offset */
11514         if (fseek(f, -positionNumber, 0) == -1) {
11515             DisplayError(_("Can't seek on position file"), 0);
11516             return FALSE;
11517         };
11518         pn = 1;
11519     } else {
11520         if (fseek(f, 0, 0) == -1) {
11521             if (f == lastLoadPositionFP ?
11522                 positionNumber == lastLoadPositionNumber + 1 :
11523                 positionNumber == 1) {
11524                 pn = 1;
11525             } else {
11526                 DisplayError(_("Can't seek on position file"), 0);
11527                 return FALSE;
11528             }
11529         }
11530     }
11531     /* See if this file is FEN or old-style xboard */
11532     if (fgets(line, MSG_SIZ, f) == NULL) {
11533         DisplayError(_("Position not found in file"), 0);
11534         return FALSE;
11535     }
11536     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11537     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11538
11539     if (pn >= 2) {
11540         if (fenMode || line[0] == '#') pn--;
11541         while (pn > 0) {
11542             /* skip positions before number pn */
11543             if (fgets(line, MSG_SIZ, f) == NULL) {
11544                 Reset(TRUE, TRUE);
11545                 DisplayError(_("Position not found in file"), 0);
11546                 return FALSE;
11547             }
11548             if (fenMode || line[0] == '#') pn--;
11549         }
11550     }
11551
11552     if (fenMode) {
11553         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11554             DisplayError(_("Bad FEN position in file"), 0);
11555             return FALSE;
11556         }
11557     } else {
11558         (void) fgets(line, MSG_SIZ, f);
11559         (void) fgets(line, MSG_SIZ, f);
11560
11561         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11562             (void) fgets(line, MSG_SIZ, f);
11563             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11564                 if (*p == ' ')
11565                   continue;
11566                 initial_position[i][j++] = CharToPiece(*p);
11567             }
11568         }
11569
11570         blackPlaysFirst = FALSE;
11571         if (!feof(f)) {
11572             (void) fgets(line, MSG_SIZ, f);
11573             if (strncmp(line, "black", strlen("black"))==0)
11574               blackPlaysFirst = TRUE;
11575         }
11576     }
11577     startedFromSetupPosition = TRUE;
11578
11579     SendToProgram("force\n", &first);
11580     CopyBoard(boards[0], initial_position);
11581     if (blackPlaysFirst) {
11582         currentMove = forwardMostMove = backwardMostMove = 1;
11583         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11584         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11585         CopyBoard(boards[1], initial_position);
11586         DisplayMessage("", _("Black to play"));
11587     } else {
11588         currentMove = forwardMostMove = backwardMostMove = 0;
11589         DisplayMessage("", _("White to play"));
11590     }
11591     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11592     SendBoard(&first, forwardMostMove);
11593     if (appData.debugMode) {
11594 int i, j;
11595   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11596   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11597         fprintf(debugFP, "Load Position\n");
11598     }
11599
11600     if (positionNumber > 1) {
11601       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11602         DisplayTitle(line);
11603     } else {
11604         DisplayTitle(title);
11605     }
11606     gameMode = EditGame;
11607     ModeHighlight();
11608     ResetClocks();
11609     timeRemaining[0][1] = whiteTimeRemaining;
11610     timeRemaining[1][1] = blackTimeRemaining;
11611     DrawPosition(FALSE, boards[currentMove]);
11612
11613     return TRUE;
11614 }
11615
11616
11617 void
11618 CopyPlayerNameIntoFileName(dest, src)
11619      char **dest, *src;
11620 {
11621     while (*src != NULLCHAR && *src != ',') {
11622         if (*src == ' ') {
11623             *(*dest)++ = '_';
11624             src++;
11625         } else {
11626             *(*dest)++ = *src++;
11627         }
11628     }
11629 }
11630
11631 char *DefaultFileName(ext)
11632      char *ext;
11633 {
11634     static char def[MSG_SIZ];
11635     char *p;
11636
11637     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11638         p = def;
11639         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11640         *p++ = '-';
11641         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11642         *p++ = '.';
11643         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11644     } else {
11645         def[0] = NULLCHAR;
11646     }
11647     return def;
11648 }
11649
11650 /* Save the current game to the given file */
11651 int
11652 SaveGameToFile(filename, append)
11653      char *filename;
11654      int append;
11655 {
11656     FILE *f;
11657     char buf[MSG_SIZ];
11658     int result;
11659
11660     if (strcmp(filename, "-") == 0) {
11661         return SaveGame(stdout, 0, NULL);
11662     } else {
11663         f = fopen(filename, append ? "a" : "w");
11664         if (f == NULL) {
11665             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11666             DisplayError(buf, errno);
11667             return FALSE;
11668         } else {
11669             safeStrCpy(buf, lastMsg, MSG_SIZ);
11670             DisplayMessage(_("Waiting for access to save file"), "");
11671             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11672             DisplayMessage(_("Saving game"), "");
11673             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11674             result = SaveGame(f, 0, NULL);
11675             DisplayMessage(buf, "");
11676             return result;
11677         }
11678     }
11679 }
11680
11681 char *
11682 SavePart(str)
11683      char *str;
11684 {
11685     static char buf[MSG_SIZ];
11686     char *p;
11687
11688     p = strchr(str, ' ');
11689     if (p == NULL) return str;
11690     strncpy(buf, str, p - str);
11691     buf[p - str] = NULLCHAR;
11692     return buf;
11693 }
11694
11695 #define PGN_MAX_LINE 75
11696
11697 #define PGN_SIDE_WHITE  0
11698 #define PGN_SIDE_BLACK  1
11699
11700 /* [AS] */
11701 static int FindFirstMoveOutOfBook( int side )
11702 {
11703     int result = -1;
11704
11705     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11706         int index = backwardMostMove;
11707         int has_book_hit = 0;
11708
11709         if( (index % 2) != side ) {
11710             index++;
11711         }
11712
11713         while( index < forwardMostMove ) {
11714             /* Check to see if engine is in book */
11715             int depth = pvInfoList[index].depth;
11716             int score = pvInfoList[index].score;
11717             int in_book = 0;
11718
11719             if( depth <= 2 ) {
11720                 in_book = 1;
11721             }
11722             else if( score == 0 && depth == 63 ) {
11723                 in_book = 1; /* Zappa */
11724             }
11725             else if( score == 2 && depth == 99 ) {
11726                 in_book = 1; /* Abrok */
11727             }
11728
11729             has_book_hit += in_book;
11730
11731             if( ! in_book ) {
11732                 result = index;
11733
11734                 break;
11735             }
11736
11737             index += 2;
11738         }
11739     }
11740
11741     return result;
11742 }
11743
11744 /* [AS] */
11745 void GetOutOfBookInfo( char * buf )
11746 {
11747     int oob[2];
11748     int i;
11749     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11750
11751     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11752     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11753
11754     *buf = '\0';
11755
11756     if( oob[0] >= 0 || oob[1] >= 0 ) {
11757         for( i=0; i<2; i++ ) {
11758             int idx = oob[i];
11759
11760             if( idx >= 0 ) {
11761                 if( i > 0 && oob[0] >= 0 ) {
11762                     strcat( buf, "   " );
11763                 }
11764
11765                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11766                 sprintf( buf+strlen(buf), "%s%.2f",
11767                     pvInfoList[idx].score >= 0 ? "+" : "",
11768                     pvInfoList[idx].score / 100.0 );
11769             }
11770         }
11771     }
11772 }
11773
11774 /* Save game in PGN style and close the file */
11775 int
11776 SaveGamePGN(f)
11777      FILE *f;
11778 {
11779     int i, offset, linelen, newblock;
11780     time_t tm;
11781 //    char *movetext;
11782     char numtext[32];
11783     int movelen, numlen, blank;
11784     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11785
11786     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11787
11788     tm = time((time_t *) NULL);
11789
11790     PrintPGNTags(f, &gameInfo);
11791
11792     if (backwardMostMove > 0 || startedFromSetupPosition) {
11793         char *fen = PositionToFEN(backwardMostMove, NULL);
11794         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11795         fprintf(f, "\n{--------------\n");
11796         PrintPosition(f, backwardMostMove);
11797         fprintf(f, "--------------}\n");
11798         free(fen);
11799     }
11800     else {
11801         /* [AS] Out of book annotation */
11802         if( appData.saveOutOfBookInfo ) {
11803             char buf[64];
11804
11805             GetOutOfBookInfo( buf );
11806
11807             if( buf[0] != '\0' ) {
11808                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11809             }
11810         }
11811
11812         fprintf(f, "\n");
11813     }
11814
11815     i = backwardMostMove;
11816     linelen = 0;
11817     newblock = TRUE;
11818
11819     while (i < forwardMostMove) {
11820         /* Print comments preceding this move */
11821         if (commentList[i] != NULL) {
11822             if (linelen > 0) fprintf(f, "\n");
11823             fprintf(f, "%s", commentList[i]);
11824             linelen = 0;
11825             newblock = TRUE;
11826         }
11827
11828         /* Format move number */
11829         if ((i % 2) == 0)
11830           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11831         else
11832           if (newblock)
11833             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11834           else
11835             numtext[0] = NULLCHAR;
11836
11837         numlen = strlen(numtext);
11838         newblock = FALSE;
11839
11840         /* Print move number */
11841         blank = linelen > 0 && numlen > 0;
11842         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11843             fprintf(f, "\n");
11844             linelen = 0;
11845             blank = 0;
11846         }
11847         if (blank) {
11848             fprintf(f, " ");
11849             linelen++;
11850         }
11851         fprintf(f, "%s", numtext);
11852         linelen += numlen;
11853
11854         /* Get move */
11855         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11856         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11857
11858         /* Print move */
11859         blank = linelen > 0 && movelen > 0;
11860         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11861             fprintf(f, "\n");
11862             linelen = 0;
11863             blank = 0;
11864         }
11865         if (blank) {
11866             fprintf(f, " ");
11867             linelen++;
11868         }
11869         fprintf(f, "%s", move_buffer);
11870         linelen += movelen;
11871
11872         /* [AS] Add PV info if present */
11873         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11874             /* [HGM] add time */
11875             char buf[MSG_SIZ]; int seconds;
11876
11877             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11878
11879             if( seconds <= 0)
11880               buf[0] = 0;
11881             else
11882               if( seconds < 30 )
11883                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11884               else
11885                 {
11886                   seconds = (seconds + 4)/10; // round to full seconds
11887                   if( seconds < 60 )
11888                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11889                   else
11890                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11891                 }
11892
11893             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11894                       pvInfoList[i].score >= 0 ? "+" : "",
11895                       pvInfoList[i].score / 100.0,
11896                       pvInfoList[i].depth,
11897                       buf );
11898
11899             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11900
11901             /* Print score/depth */
11902             blank = linelen > 0 && movelen > 0;
11903             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11904                 fprintf(f, "\n");
11905                 linelen = 0;
11906                 blank = 0;
11907             }
11908             if (blank) {
11909                 fprintf(f, " ");
11910                 linelen++;
11911             }
11912             fprintf(f, "%s", move_buffer);
11913             linelen += movelen;
11914         }
11915
11916         i++;
11917     }
11918
11919     /* Start a new line */
11920     if (linelen > 0) fprintf(f, "\n");
11921
11922     /* Print comments after last move */
11923     if (commentList[i] != NULL) {
11924         fprintf(f, "%s\n", commentList[i]);
11925     }
11926
11927     /* Print result */
11928     if (gameInfo.resultDetails != NULL &&
11929         gameInfo.resultDetails[0] != NULLCHAR) {
11930         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11931                 PGNResult(gameInfo.result));
11932     } else {
11933         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11934     }
11935
11936     fclose(f);
11937     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11938     return TRUE;
11939 }
11940
11941 /* Save game in old style and close the file */
11942 int
11943 SaveGameOldStyle(f)
11944      FILE *f;
11945 {
11946     int i, offset;
11947     time_t tm;
11948
11949     tm = time((time_t *) NULL);
11950
11951     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11952     PrintOpponents(f);
11953
11954     if (backwardMostMove > 0 || startedFromSetupPosition) {
11955         fprintf(f, "\n[--------------\n");
11956         PrintPosition(f, backwardMostMove);
11957         fprintf(f, "--------------]\n");
11958     } else {
11959         fprintf(f, "\n");
11960     }
11961
11962     i = backwardMostMove;
11963     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11964
11965     while (i < forwardMostMove) {
11966         if (commentList[i] != NULL) {
11967             fprintf(f, "[%s]\n", commentList[i]);
11968         }
11969
11970         if ((i % 2) == 1) {
11971             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11972             i++;
11973         } else {
11974             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11975             i++;
11976             if (commentList[i] != NULL) {
11977                 fprintf(f, "\n");
11978                 continue;
11979             }
11980             if (i >= forwardMostMove) {
11981                 fprintf(f, "\n");
11982                 break;
11983             }
11984             fprintf(f, "%s\n", parseList[i]);
11985             i++;
11986         }
11987     }
11988
11989     if (commentList[i] != NULL) {
11990         fprintf(f, "[%s]\n", commentList[i]);
11991     }
11992
11993     /* This isn't really the old style, but it's close enough */
11994     if (gameInfo.resultDetails != NULL &&
11995         gameInfo.resultDetails[0] != NULLCHAR) {
11996         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11997                 gameInfo.resultDetails);
11998     } else {
11999         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12000     }
12001
12002     fclose(f);
12003     return TRUE;
12004 }
12005
12006 /* Save the current game to open file f and close the file */
12007 int
12008 SaveGame(f, dummy, dummy2)
12009      FILE *f;
12010      int dummy;
12011      char *dummy2;
12012 {
12013     if (gameMode == EditPosition) EditPositionDone(TRUE);
12014     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12015     if (appData.oldSaveStyle)
12016       return SaveGameOldStyle(f);
12017     else
12018       return SaveGamePGN(f);
12019 }
12020
12021 /* Save the current position to the given file */
12022 int
12023 SavePositionToFile(filename)
12024      char *filename;
12025 {
12026     FILE *f;
12027     char buf[MSG_SIZ];
12028
12029     if (strcmp(filename, "-") == 0) {
12030         return SavePosition(stdout, 0, NULL);
12031     } else {
12032         f = fopen(filename, "a");
12033         if (f == NULL) {
12034             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12035             DisplayError(buf, errno);
12036             return FALSE;
12037         } else {
12038             safeStrCpy(buf, lastMsg, MSG_SIZ);
12039             DisplayMessage(_("Waiting for access to save file"), "");
12040             flock(fileno(f), LOCK_EX); // [HGM] lock
12041             DisplayMessage(_("Saving position"), "");
12042             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12043             SavePosition(f, 0, NULL);
12044             DisplayMessage(buf, "");
12045             return TRUE;
12046         }
12047     }
12048 }
12049
12050 /* Save the current position to the given open file and close the file */
12051 int
12052 SavePosition(f, dummy, dummy2)
12053      FILE *f;
12054      int dummy;
12055      char *dummy2;
12056 {
12057     time_t tm;
12058     char *fen;
12059
12060     if (gameMode == EditPosition) EditPositionDone(TRUE);
12061     if (appData.oldSaveStyle) {
12062         tm = time((time_t *) NULL);
12063
12064         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12065         PrintOpponents(f);
12066         fprintf(f, "[--------------\n");
12067         PrintPosition(f, currentMove);
12068         fprintf(f, "--------------]\n");
12069     } else {
12070         fen = PositionToFEN(currentMove, NULL);
12071         fprintf(f, "%s\n", fen);
12072         free(fen);
12073     }
12074     fclose(f);
12075     return TRUE;
12076 }
12077
12078 void
12079 ReloadCmailMsgEvent(unregister)
12080      int unregister;
12081 {
12082 #if !WIN32
12083     static char *inFilename = NULL;
12084     static char *outFilename;
12085     int i;
12086     struct stat inbuf, outbuf;
12087     int status;
12088
12089     /* Any registered moves are unregistered if unregister is set, */
12090     /* i.e. invoked by the signal handler */
12091     if (unregister) {
12092         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12093             cmailMoveRegistered[i] = FALSE;
12094             if (cmailCommentList[i] != NULL) {
12095                 free(cmailCommentList[i]);
12096                 cmailCommentList[i] = NULL;
12097             }
12098         }
12099         nCmailMovesRegistered = 0;
12100     }
12101
12102     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12103         cmailResult[i] = CMAIL_NOT_RESULT;
12104     }
12105     nCmailResults = 0;
12106
12107     if (inFilename == NULL) {
12108         /* Because the filenames are static they only get malloced once  */
12109         /* and they never get freed                                      */
12110         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12111         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12112
12113         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12114         sprintf(outFilename, "%s.out", appData.cmailGameName);
12115     }
12116
12117     status = stat(outFilename, &outbuf);
12118     if (status < 0) {
12119         cmailMailedMove = FALSE;
12120     } else {
12121         status = stat(inFilename, &inbuf);
12122         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12123     }
12124
12125     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12126        counts the games, notes how each one terminated, etc.
12127
12128        It would be nice to remove this kludge and instead gather all
12129        the information while building the game list.  (And to keep it
12130        in the game list nodes instead of having a bunch of fixed-size
12131        parallel arrays.)  Note this will require getting each game's
12132        termination from the PGN tags, as the game list builder does
12133        not process the game moves.  --mann
12134        */
12135     cmailMsgLoaded = TRUE;
12136     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12137
12138     /* Load first game in the file or popup game menu */
12139     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12140
12141 #endif /* !WIN32 */
12142     return;
12143 }
12144
12145 int
12146 RegisterMove()
12147 {
12148     FILE *f;
12149     char string[MSG_SIZ];
12150
12151     if (   cmailMailedMove
12152         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12153         return TRUE;            /* Allow free viewing  */
12154     }
12155
12156     /* Unregister move to ensure that we don't leave RegisterMove        */
12157     /* with the move registered when the conditions for registering no   */
12158     /* longer hold                                                       */
12159     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12160         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12161         nCmailMovesRegistered --;
12162
12163         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12164           {
12165               free(cmailCommentList[lastLoadGameNumber - 1]);
12166               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12167           }
12168     }
12169
12170     if (cmailOldMove == -1) {
12171         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12172         return FALSE;
12173     }
12174
12175     if (currentMove > cmailOldMove + 1) {
12176         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12177         return FALSE;
12178     }
12179
12180     if (currentMove < cmailOldMove) {
12181         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12182         return FALSE;
12183     }
12184
12185     if (forwardMostMove > currentMove) {
12186         /* Silently truncate extra moves */
12187         TruncateGame();
12188     }
12189
12190     if (   (currentMove == cmailOldMove + 1)
12191         || (   (currentMove == cmailOldMove)
12192             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12193                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12194         if (gameInfo.result != GameUnfinished) {
12195             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12196         }
12197
12198         if (commentList[currentMove] != NULL) {
12199             cmailCommentList[lastLoadGameNumber - 1]
12200               = StrSave(commentList[currentMove]);
12201         }
12202         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12203
12204         if (appData.debugMode)
12205           fprintf(debugFP, "Saving %s for game %d\n",
12206                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12207
12208         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12209
12210         f = fopen(string, "w");
12211         if (appData.oldSaveStyle) {
12212             SaveGameOldStyle(f); /* also closes the file */
12213
12214             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12215             f = fopen(string, "w");
12216             SavePosition(f, 0, NULL); /* also closes the file */
12217         } else {
12218             fprintf(f, "{--------------\n");
12219             PrintPosition(f, currentMove);
12220             fprintf(f, "--------------}\n\n");
12221
12222             SaveGame(f, 0, NULL); /* also closes the file*/
12223         }
12224
12225         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12226         nCmailMovesRegistered ++;
12227     } else if (nCmailGames == 1) {
12228         DisplayError(_("You have not made a move yet"), 0);
12229         return FALSE;
12230     }
12231
12232     return TRUE;
12233 }
12234
12235 void
12236 MailMoveEvent()
12237 {
12238 #if !WIN32
12239     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12240     FILE *commandOutput;
12241     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12242     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12243     int nBuffers;
12244     int i;
12245     int archived;
12246     char *arcDir;
12247
12248     if (! cmailMsgLoaded) {
12249         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12250         return;
12251     }
12252
12253     if (nCmailGames == nCmailResults) {
12254         DisplayError(_("No unfinished games"), 0);
12255         return;
12256     }
12257
12258 #if CMAIL_PROHIBIT_REMAIL
12259     if (cmailMailedMove) {
12260       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);
12261         DisplayError(msg, 0);
12262         return;
12263     }
12264 #endif
12265
12266     if (! (cmailMailedMove || RegisterMove())) return;
12267
12268     if (   cmailMailedMove
12269         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12270       snprintf(string, MSG_SIZ, partCommandString,
12271                appData.debugMode ? " -v" : "", appData.cmailGameName);
12272         commandOutput = popen(string, "r");
12273
12274         if (commandOutput == NULL) {
12275             DisplayError(_("Failed to invoke cmail"), 0);
12276         } else {
12277             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12278                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12279             }
12280             if (nBuffers > 1) {
12281                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12282                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12283                 nBytes = MSG_SIZ - 1;
12284             } else {
12285                 (void) memcpy(msg, buffer, nBytes);
12286             }
12287             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12288
12289             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12290                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12291
12292                 archived = TRUE;
12293                 for (i = 0; i < nCmailGames; i ++) {
12294                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12295                         archived = FALSE;
12296                     }
12297                 }
12298                 if (   archived
12299                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12300                         != NULL)) {
12301                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12302                            arcDir,
12303                            appData.cmailGameName,
12304                            gameInfo.date);
12305                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12306                     cmailMsgLoaded = FALSE;
12307                 }
12308             }
12309
12310             DisplayInformation(msg);
12311             pclose(commandOutput);
12312         }
12313     } else {
12314         if ((*cmailMsg) != '\0') {
12315             DisplayInformation(cmailMsg);
12316         }
12317     }
12318
12319     return;
12320 #endif /* !WIN32 */
12321 }
12322
12323 char *
12324 CmailMsg()
12325 {
12326 #if WIN32
12327     return NULL;
12328 #else
12329     int  prependComma = 0;
12330     char number[5];
12331     char string[MSG_SIZ];       /* Space for game-list */
12332     int  i;
12333
12334     if (!cmailMsgLoaded) return "";
12335
12336     if (cmailMailedMove) {
12337       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12338     } else {
12339         /* Create a list of games left */
12340       snprintf(string, MSG_SIZ, "[");
12341         for (i = 0; i < nCmailGames; i ++) {
12342             if (! (   cmailMoveRegistered[i]
12343                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12344                 if (prependComma) {
12345                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12346                 } else {
12347                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12348                     prependComma = 1;
12349                 }
12350
12351                 strcat(string, number);
12352             }
12353         }
12354         strcat(string, "]");
12355
12356         if (nCmailMovesRegistered + nCmailResults == 0) {
12357             switch (nCmailGames) {
12358               case 1:
12359                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12360                 break;
12361
12362               case 2:
12363                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12364                 break;
12365
12366               default:
12367                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12368                          nCmailGames);
12369                 break;
12370             }
12371         } else {
12372             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12373               case 1:
12374                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12375                          string);
12376                 break;
12377
12378               case 0:
12379                 if (nCmailResults == nCmailGames) {
12380                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12381                 } else {
12382                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12383                 }
12384                 break;
12385
12386               default:
12387                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12388                          string);
12389             }
12390         }
12391     }
12392     return cmailMsg;
12393 #endif /* WIN32 */
12394 }
12395
12396 void
12397 ResetGameEvent()
12398 {
12399     if (gameMode == Training)
12400       SetTrainingModeOff();
12401
12402     Reset(TRUE, TRUE);
12403     cmailMsgLoaded = FALSE;
12404     if (appData.icsActive) {
12405       SendToICS(ics_prefix);
12406       SendToICS("refresh\n");
12407     }
12408 }
12409
12410 void
12411 ExitEvent(status)
12412      int status;
12413 {
12414     exiting++;
12415     if (exiting > 2) {
12416       /* Give up on clean exit */
12417       exit(status);
12418     }
12419     if (exiting > 1) {
12420       /* Keep trying for clean exit */
12421       return;
12422     }
12423
12424     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12425
12426     if (telnetISR != NULL) {
12427       RemoveInputSource(telnetISR);
12428     }
12429     if (icsPR != NoProc) {
12430       DestroyChildProcess(icsPR, TRUE);
12431     }
12432
12433     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12434     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12435
12436     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12437     /* make sure this other one finishes before killing it!                  */
12438     if(endingGame) { int count = 0;
12439         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12440         while(endingGame && count++ < 10) DoSleep(1);
12441         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12442     }
12443
12444     /* Kill off chess programs */
12445     if (first.pr != NoProc) {
12446         ExitAnalyzeMode();
12447
12448         DoSleep( appData.delayBeforeQuit );
12449         SendToProgram("quit\n", &first);
12450         DoSleep( appData.delayAfterQuit );
12451         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12452     }
12453     if (second.pr != NoProc) {
12454         DoSleep( appData.delayBeforeQuit );
12455         SendToProgram("quit\n", &second);
12456         DoSleep( appData.delayAfterQuit );
12457         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12458     }
12459     if (first.isr != NULL) {
12460         RemoveInputSource(first.isr);
12461     }
12462     if (second.isr != NULL) {
12463         RemoveInputSource(second.isr);
12464     }
12465
12466     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12467     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12468
12469     ShutDownFrontEnd();
12470     exit(status);
12471 }
12472
12473 void
12474 PauseEvent()
12475 {
12476     if (appData.debugMode)
12477         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12478     if (pausing) {
12479         pausing = FALSE;
12480         ModeHighlight();
12481         if (gameMode == MachinePlaysWhite ||
12482             gameMode == MachinePlaysBlack) {
12483             StartClocks();
12484         } else {
12485             DisplayBothClocks();
12486         }
12487         if (gameMode == PlayFromGameFile) {
12488             if (appData.timeDelay >= 0)
12489                 AutoPlayGameLoop();
12490         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12491             Reset(FALSE, TRUE);
12492             SendToICS(ics_prefix);
12493             SendToICS("refresh\n");
12494         } else if (currentMove < forwardMostMove) {
12495             ForwardInner(forwardMostMove);
12496         }
12497         pauseExamInvalid = FALSE;
12498     } else {
12499         switch (gameMode) {
12500           default:
12501             return;
12502           case IcsExamining:
12503             pauseExamForwardMostMove = forwardMostMove;
12504             pauseExamInvalid = FALSE;
12505             /* fall through */
12506           case IcsObserving:
12507           case IcsPlayingWhite:
12508           case IcsPlayingBlack:
12509             pausing = TRUE;
12510             ModeHighlight();
12511             return;
12512           case PlayFromGameFile:
12513             (void) StopLoadGameTimer();
12514             pausing = TRUE;
12515             ModeHighlight();
12516             break;
12517           case BeginningOfGame:
12518             if (appData.icsActive) return;
12519             /* else fall through */
12520           case MachinePlaysWhite:
12521           case MachinePlaysBlack:
12522           case TwoMachinesPlay:
12523             if (forwardMostMove == 0)
12524               return;           /* don't pause if no one has moved */
12525             if ((gameMode == MachinePlaysWhite &&
12526                  !WhiteOnMove(forwardMostMove)) ||
12527                 (gameMode == MachinePlaysBlack &&
12528                  WhiteOnMove(forwardMostMove))) {
12529                 StopClocks();
12530             }
12531             pausing = TRUE;
12532             ModeHighlight();
12533             break;
12534         }
12535     }
12536 }
12537
12538 void
12539 EditCommentEvent()
12540 {
12541     char title[MSG_SIZ];
12542
12543     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12544       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12545     } else {
12546       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12547                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12548                parseList[currentMove - 1]);
12549     }
12550
12551     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12552 }
12553
12554
12555 void
12556 EditTagsEvent()
12557 {
12558     char *tags = PGNTags(&gameInfo);
12559     bookUp = FALSE;
12560     EditTagsPopUp(tags, NULL);
12561     free(tags);
12562 }
12563
12564 void
12565 AnalyzeModeEvent()
12566 {
12567     if (appData.noChessProgram || gameMode == AnalyzeMode)
12568       return;
12569
12570     if (gameMode != AnalyzeFile) {
12571         if (!appData.icsEngineAnalyze) {
12572                EditGameEvent();
12573                if (gameMode != EditGame) return;
12574         }
12575         ResurrectChessProgram();
12576         SendToProgram("analyze\n", &first);
12577         first.analyzing = TRUE;
12578         /*first.maybeThinking = TRUE;*/
12579         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12580         EngineOutputPopUp();
12581     }
12582     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12583     pausing = FALSE;
12584     ModeHighlight();
12585     SetGameInfo();
12586
12587     StartAnalysisClock();
12588     GetTimeMark(&lastNodeCountTime);
12589     lastNodeCount = 0;
12590 }
12591
12592 void
12593 AnalyzeFileEvent()
12594 {
12595     if (appData.noChessProgram || gameMode == AnalyzeFile)
12596       return;
12597
12598     if (gameMode != AnalyzeMode) {
12599         EditGameEvent();
12600         if (gameMode != EditGame) return;
12601         ResurrectChessProgram();
12602         SendToProgram("analyze\n", &first);
12603         first.analyzing = TRUE;
12604         /*first.maybeThinking = TRUE;*/
12605         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12606         EngineOutputPopUp();
12607     }
12608     gameMode = AnalyzeFile;
12609     pausing = FALSE;
12610     ModeHighlight();
12611     SetGameInfo();
12612
12613     StartAnalysisClock();
12614     GetTimeMark(&lastNodeCountTime);
12615     lastNodeCount = 0;
12616 }
12617
12618 void
12619 MachineWhiteEvent()
12620 {
12621     char buf[MSG_SIZ];
12622     char *bookHit = NULL;
12623
12624     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12625       return;
12626
12627
12628     if (gameMode == PlayFromGameFile ||
12629         gameMode == TwoMachinesPlay  ||
12630         gameMode == Training         ||
12631         gameMode == AnalyzeMode      ||
12632         gameMode == EndOfGame)
12633         EditGameEvent();
12634
12635     if (gameMode == EditPosition)
12636         EditPositionDone(TRUE);
12637
12638     if (!WhiteOnMove(currentMove)) {
12639         DisplayError(_("It is not White's turn"), 0);
12640         return;
12641     }
12642
12643     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12644       ExitAnalyzeMode();
12645
12646     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12647         gameMode == AnalyzeFile)
12648         TruncateGame();
12649
12650     ResurrectChessProgram();    /* in case it isn't running */
12651     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12652         gameMode = MachinePlaysWhite;
12653         ResetClocks();
12654     } else
12655     gameMode = MachinePlaysWhite;
12656     pausing = FALSE;
12657     ModeHighlight();
12658     SetGameInfo();
12659     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12660     DisplayTitle(buf);
12661     if (first.sendName) {
12662       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12663       SendToProgram(buf, &first);
12664     }
12665     if (first.sendTime) {
12666       if (first.useColors) {
12667         SendToProgram("black\n", &first); /*gnu kludge*/
12668       }
12669       SendTimeRemaining(&first, TRUE);
12670     }
12671     if (first.useColors) {
12672       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12673     }
12674     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12675     SetMachineThinkingEnables();
12676     first.maybeThinking = TRUE;
12677     StartClocks();
12678     firstMove = FALSE;
12679
12680     if (appData.autoFlipView && !flipView) {
12681       flipView = !flipView;
12682       DrawPosition(FALSE, NULL);
12683       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12684     }
12685
12686     if(bookHit) { // [HGM] book: simulate book reply
12687         static char bookMove[MSG_SIZ]; // a bit generous?
12688
12689         programStats.nodes = programStats.depth = programStats.time =
12690         programStats.score = programStats.got_only_move = 0;
12691         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12692
12693         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12694         strcat(bookMove, bookHit);
12695         HandleMachineMove(bookMove, &first);
12696     }
12697 }
12698
12699 void
12700 MachineBlackEvent()
12701 {
12702   char buf[MSG_SIZ];
12703   char *bookHit = NULL;
12704
12705     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12706         return;
12707
12708
12709     if (gameMode == PlayFromGameFile ||
12710         gameMode == TwoMachinesPlay  ||
12711         gameMode == Training         ||
12712         gameMode == AnalyzeMode      ||
12713         gameMode == EndOfGame)
12714         EditGameEvent();
12715
12716     if (gameMode == EditPosition)
12717         EditPositionDone(TRUE);
12718
12719     if (WhiteOnMove(currentMove)) {
12720         DisplayError(_("It is not Black's turn"), 0);
12721         return;
12722     }
12723
12724     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12725       ExitAnalyzeMode();
12726
12727     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12728         gameMode == AnalyzeFile)
12729         TruncateGame();
12730
12731     ResurrectChessProgram();    /* in case it isn't running */
12732     gameMode = MachinePlaysBlack;
12733     pausing = FALSE;
12734     ModeHighlight();
12735     SetGameInfo();
12736     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12737     DisplayTitle(buf);
12738     if (first.sendName) {
12739       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12740       SendToProgram(buf, &first);
12741     }
12742     if (first.sendTime) {
12743       if (first.useColors) {
12744         SendToProgram("white\n", &first); /*gnu kludge*/
12745       }
12746       SendTimeRemaining(&first, FALSE);
12747     }
12748     if (first.useColors) {
12749       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12750     }
12751     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12752     SetMachineThinkingEnables();
12753     first.maybeThinking = TRUE;
12754     StartClocks();
12755
12756     if (appData.autoFlipView && flipView) {
12757       flipView = !flipView;
12758       DrawPosition(FALSE, NULL);
12759       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12760     }
12761     if(bookHit) { // [HGM] book: simulate book reply
12762         static char bookMove[MSG_SIZ]; // a bit generous?
12763
12764         programStats.nodes = programStats.depth = programStats.time =
12765         programStats.score = programStats.got_only_move = 0;
12766         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12767
12768         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12769         strcat(bookMove, bookHit);
12770         HandleMachineMove(bookMove, &first);
12771     }
12772 }
12773
12774
12775 void
12776 DisplayTwoMachinesTitle()
12777 {
12778     char buf[MSG_SIZ];
12779     if (appData.matchGames > 0) {
12780         if(appData.tourneyFile[0]) {
12781           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12782                    gameInfo.white, gameInfo.black,
12783                    nextGame+1, appData.matchGames+1,
12784                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12785         } else 
12786         if (first.twoMachinesColor[0] == 'w') {
12787           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12788                    gameInfo.white, gameInfo.black,
12789                    first.matchWins, second.matchWins,
12790                    matchGame - 1 - (first.matchWins + second.matchWins));
12791         } else {
12792           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12793                    gameInfo.white, gameInfo.black,
12794                    second.matchWins, first.matchWins,
12795                    matchGame - 1 - (first.matchWins + second.matchWins));
12796         }
12797     } else {
12798       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12799     }
12800     DisplayTitle(buf);
12801 }
12802
12803 void
12804 SettingsMenuIfReady()
12805 {
12806   if (second.lastPing != second.lastPong) {
12807     DisplayMessage("", _("Waiting for second chess program"));
12808     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12809     return;
12810   }
12811   ThawUI();
12812   DisplayMessage("", "");
12813   SettingsPopUp(&second);
12814 }
12815
12816 int
12817 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12818 {
12819     char buf[MSG_SIZ];
12820     if (cps->pr == NULL) {
12821         StartChessProgram(cps);
12822         if (cps->protocolVersion == 1) {
12823           retry();
12824         } else {
12825           /* kludge: allow timeout for initial "feature" command */
12826           FreezeUI();
12827           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12828           DisplayMessage("", buf);
12829           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12830         }
12831         return 1;
12832     }
12833     return 0;
12834 }
12835
12836 void
12837 TwoMachinesEvent P((void))
12838 {
12839     int i;
12840     char buf[MSG_SIZ];
12841     ChessProgramState *onmove;
12842     char *bookHit = NULL;
12843     static int stalling = 0;
12844     TimeMark now;
12845     long wait;
12846
12847     if (appData.noChessProgram) return;
12848
12849     switch (gameMode) {
12850       case TwoMachinesPlay:
12851         return;
12852       case MachinePlaysWhite:
12853       case MachinePlaysBlack:
12854         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12855             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12856             return;
12857         }
12858         /* fall through */
12859       case BeginningOfGame:
12860       case PlayFromGameFile:
12861       case EndOfGame:
12862         EditGameEvent();
12863         if (gameMode != EditGame) return;
12864         break;
12865       case EditPosition:
12866         EditPositionDone(TRUE);
12867         break;
12868       case AnalyzeMode:
12869       case AnalyzeFile:
12870         ExitAnalyzeMode();
12871         break;
12872       case EditGame:
12873       default:
12874         break;
12875     }
12876
12877 //    forwardMostMove = currentMove;
12878     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12879
12880     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12881
12882     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12883     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12884       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12885       return;
12886     }
12887     if(!stalling) {
12888       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12889       SendToProgram("force\n", &second);
12890       stalling = 1;
12891       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12892       return;
12893     }
12894     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12895     if(appData.matchPause>10000 || appData.matchPause<10)
12896                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12897     wait = SubtractTimeMarks(&now, &pauseStart);
12898     if(wait < appData.matchPause) {
12899         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12900         return;
12901     }
12902     stalling = 0;
12903     DisplayMessage("", "");
12904     if (startedFromSetupPosition) {
12905         SendBoard(&second, backwardMostMove);
12906     if (appData.debugMode) {
12907         fprintf(debugFP, "Two Machines\n");
12908     }
12909     }
12910     for (i = backwardMostMove; i < forwardMostMove; i++) {
12911         SendMoveToProgram(i, &second);
12912     }
12913
12914     gameMode = TwoMachinesPlay;
12915     pausing = FALSE;
12916     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12917     SetGameInfo();
12918     DisplayTwoMachinesTitle();
12919     firstMove = TRUE;
12920     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12921         onmove = &first;
12922     } else {
12923         onmove = &second;
12924     }
12925     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12926     SendToProgram(first.computerString, &first);
12927     if (first.sendName) {
12928       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12929       SendToProgram(buf, &first);
12930     }
12931     SendToProgram(second.computerString, &second);
12932     if (second.sendName) {
12933       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12934       SendToProgram(buf, &second);
12935     }
12936
12937     ResetClocks();
12938     if (!first.sendTime || !second.sendTime) {
12939         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12940         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12941     }
12942     if (onmove->sendTime) {
12943       if (onmove->useColors) {
12944         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12945       }
12946       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12947     }
12948     if (onmove->useColors) {
12949       SendToProgram(onmove->twoMachinesColor, onmove);
12950     }
12951     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12952 //    SendToProgram("go\n", onmove);
12953     onmove->maybeThinking = TRUE;
12954     SetMachineThinkingEnables();
12955
12956     StartClocks();
12957
12958     if(bookHit) { // [HGM] book: simulate book reply
12959         static char bookMove[MSG_SIZ]; // a bit generous?
12960
12961         programStats.nodes = programStats.depth = programStats.time =
12962         programStats.score = programStats.got_only_move = 0;
12963         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12964
12965         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12966         strcat(bookMove, bookHit);
12967         savedMessage = bookMove; // args for deferred call
12968         savedState = onmove;
12969         ScheduleDelayedEvent(DeferredBookMove, 1);
12970     }
12971 }
12972
12973 void
12974 TrainingEvent()
12975 {
12976     if (gameMode == Training) {
12977       SetTrainingModeOff();
12978       gameMode = PlayFromGameFile;
12979       DisplayMessage("", _("Training mode off"));
12980     } else {
12981       gameMode = Training;
12982       animateTraining = appData.animate;
12983
12984       /* make sure we are not already at the end of the game */
12985       if (currentMove < forwardMostMove) {
12986         SetTrainingModeOn();
12987         DisplayMessage("", _("Training mode on"));
12988       } else {
12989         gameMode = PlayFromGameFile;
12990         DisplayError(_("Already at end of game"), 0);
12991       }
12992     }
12993     ModeHighlight();
12994 }
12995
12996 void
12997 IcsClientEvent()
12998 {
12999     if (!appData.icsActive) return;
13000     switch (gameMode) {
13001       case IcsPlayingWhite:
13002       case IcsPlayingBlack:
13003       case IcsObserving:
13004       case IcsIdle:
13005       case BeginningOfGame:
13006       case IcsExamining:
13007         return;
13008
13009       case EditGame:
13010         break;
13011
13012       case EditPosition:
13013         EditPositionDone(TRUE);
13014         break;
13015
13016       case AnalyzeMode:
13017       case AnalyzeFile:
13018         ExitAnalyzeMode();
13019         break;
13020
13021       default:
13022         EditGameEvent();
13023         break;
13024     }
13025
13026     gameMode = IcsIdle;
13027     ModeHighlight();
13028     return;
13029 }
13030
13031
13032 void
13033 EditGameEvent()
13034 {
13035     int i;
13036
13037     switch (gameMode) {
13038       case Training:
13039         SetTrainingModeOff();
13040         break;
13041       case MachinePlaysWhite:
13042       case MachinePlaysBlack:
13043       case BeginningOfGame:
13044         SendToProgram("force\n", &first);
13045         SetUserThinkingEnables();
13046         break;
13047       case PlayFromGameFile:
13048         (void) StopLoadGameTimer();
13049         if (gameFileFP != NULL) {
13050             gameFileFP = NULL;
13051         }
13052         break;
13053       case EditPosition:
13054         EditPositionDone(TRUE);
13055         break;
13056       case AnalyzeMode:
13057       case AnalyzeFile:
13058         ExitAnalyzeMode();
13059         SendToProgram("force\n", &first);
13060         break;
13061       case TwoMachinesPlay:
13062         GameEnds(EndOfFile, NULL, GE_PLAYER);
13063         ResurrectChessProgram();
13064         SetUserThinkingEnables();
13065         break;
13066       case EndOfGame:
13067         ResurrectChessProgram();
13068         break;
13069       case IcsPlayingBlack:
13070       case IcsPlayingWhite:
13071         DisplayError(_("Warning: You are still playing a game"), 0);
13072         break;
13073       case IcsObserving:
13074         DisplayError(_("Warning: You are still observing a game"), 0);
13075         break;
13076       case IcsExamining:
13077         DisplayError(_("Warning: You are still examining a game"), 0);
13078         break;
13079       case IcsIdle:
13080         break;
13081       case EditGame:
13082       default:
13083         return;
13084     }
13085
13086     pausing = FALSE;
13087     StopClocks();
13088     first.offeredDraw = second.offeredDraw = 0;
13089
13090     if (gameMode == PlayFromGameFile) {
13091         whiteTimeRemaining = timeRemaining[0][currentMove];
13092         blackTimeRemaining = timeRemaining[1][currentMove];
13093         DisplayTitle("");
13094     }
13095
13096     if (gameMode == MachinePlaysWhite ||
13097         gameMode == MachinePlaysBlack ||
13098         gameMode == TwoMachinesPlay ||
13099         gameMode == EndOfGame) {
13100         i = forwardMostMove;
13101         while (i > currentMove) {
13102             SendToProgram("undo\n", &first);
13103             i--;
13104         }
13105         whiteTimeRemaining = timeRemaining[0][currentMove];
13106         blackTimeRemaining = timeRemaining[1][currentMove];
13107         DisplayBothClocks();
13108         if (whiteFlag || blackFlag) {
13109             whiteFlag = blackFlag = 0;
13110         }
13111         DisplayTitle("");
13112     }
13113
13114     gameMode = EditGame;
13115     ModeHighlight();
13116     SetGameInfo();
13117 }
13118
13119
13120 void
13121 EditPositionEvent()
13122 {
13123     if (gameMode == EditPosition) {
13124         EditGameEvent();
13125         return;
13126     }
13127
13128     EditGameEvent();
13129     if (gameMode != EditGame) return;
13130
13131     gameMode = EditPosition;
13132     ModeHighlight();
13133     SetGameInfo();
13134     if (currentMove > 0)
13135       CopyBoard(boards[0], boards[currentMove]);
13136
13137     blackPlaysFirst = !WhiteOnMove(currentMove);
13138     ResetClocks();
13139     currentMove = forwardMostMove = backwardMostMove = 0;
13140     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13141     DisplayMove(-1);
13142 }
13143
13144 void
13145 ExitAnalyzeMode()
13146 {
13147     /* [DM] icsEngineAnalyze - possible call from other functions */
13148     if (appData.icsEngineAnalyze) {
13149         appData.icsEngineAnalyze = FALSE;
13150
13151         DisplayMessage("",_("Close ICS engine analyze..."));
13152     }
13153     if (first.analysisSupport && first.analyzing) {
13154       SendToProgram("exit\n", &first);
13155       first.analyzing = FALSE;
13156     }
13157     thinkOutput[0] = NULLCHAR;
13158 }
13159
13160 void
13161 EditPositionDone(Boolean fakeRights)
13162 {
13163     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13164
13165     startedFromSetupPosition = TRUE;
13166     InitChessProgram(&first, FALSE);
13167     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13168       boards[0][EP_STATUS] = EP_NONE;
13169       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13170     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13171         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13172         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13173       } else boards[0][CASTLING][2] = NoRights;
13174     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13175         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13176         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13177       } else boards[0][CASTLING][5] = NoRights;
13178     }
13179     SendToProgram("force\n", &first);
13180     if (blackPlaysFirst) {
13181         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13182         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13183         currentMove = forwardMostMove = backwardMostMove = 1;
13184         CopyBoard(boards[1], boards[0]);
13185     } else {
13186         currentMove = forwardMostMove = backwardMostMove = 0;
13187     }
13188     SendBoard(&first, forwardMostMove);
13189     if (appData.debugMode) {
13190         fprintf(debugFP, "EditPosDone\n");
13191     }
13192     DisplayTitle("");
13193     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13194     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13195     gameMode = EditGame;
13196     ModeHighlight();
13197     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13198     ClearHighlights(); /* [AS] */
13199 }
13200
13201 /* Pause for `ms' milliseconds */
13202 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13203 void
13204 TimeDelay(ms)
13205      long ms;
13206 {
13207     TimeMark m1, m2;
13208
13209     GetTimeMark(&m1);
13210     do {
13211         GetTimeMark(&m2);
13212     } while (SubtractTimeMarks(&m2, &m1) < ms);
13213 }
13214
13215 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13216 void
13217 SendMultiLineToICS(buf)
13218      char *buf;
13219 {
13220     char temp[MSG_SIZ+1], *p;
13221     int len;
13222
13223     len = strlen(buf);
13224     if (len > MSG_SIZ)
13225       len = MSG_SIZ;
13226
13227     strncpy(temp, buf, len);
13228     temp[len] = 0;
13229
13230     p = temp;
13231     while (*p) {
13232         if (*p == '\n' || *p == '\r')
13233           *p = ' ';
13234         ++p;
13235     }
13236
13237     strcat(temp, "\n");
13238     SendToICS(temp);
13239     SendToPlayer(temp, strlen(temp));
13240 }
13241
13242 void
13243 SetWhiteToPlayEvent()
13244 {
13245     if (gameMode == EditPosition) {
13246         blackPlaysFirst = FALSE;
13247         DisplayBothClocks();    /* works because currentMove is 0 */
13248     } else if (gameMode == IcsExamining) {
13249         SendToICS(ics_prefix);
13250         SendToICS("tomove white\n");
13251     }
13252 }
13253
13254 void
13255 SetBlackToPlayEvent()
13256 {
13257     if (gameMode == EditPosition) {
13258         blackPlaysFirst = TRUE;
13259         currentMove = 1;        /* kludge */
13260         DisplayBothClocks();
13261         currentMove = 0;
13262     } else if (gameMode == IcsExamining) {
13263         SendToICS(ics_prefix);
13264         SendToICS("tomove black\n");
13265     }
13266 }
13267
13268 void
13269 EditPositionMenuEvent(selection, x, y)
13270      ChessSquare selection;
13271      int x, y;
13272 {
13273     char buf[MSG_SIZ];
13274     ChessSquare piece = boards[0][y][x];
13275
13276     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13277
13278     switch (selection) {
13279       case ClearBoard:
13280         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13281             SendToICS(ics_prefix);
13282             SendToICS("bsetup clear\n");
13283         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13284             SendToICS(ics_prefix);
13285             SendToICS("clearboard\n");
13286         } else {
13287             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13288                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13289                 for (y = 0; y < BOARD_HEIGHT; y++) {
13290                     if (gameMode == IcsExamining) {
13291                         if (boards[currentMove][y][x] != EmptySquare) {
13292                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13293                                     AAA + x, ONE + y);
13294                             SendToICS(buf);
13295                         }
13296                     } else {
13297                         boards[0][y][x] = p;
13298                     }
13299                 }
13300             }
13301         }
13302         if (gameMode == EditPosition) {
13303             DrawPosition(FALSE, boards[0]);
13304         }
13305         break;
13306
13307       case WhitePlay:
13308         SetWhiteToPlayEvent();
13309         break;
13310
13311       case BlackPlay:
13312         SetBlackToPlayEvent();
13313         break;
13314
13315       case EmptySquare:
13316         if (gameMode == IcsExamining) {
13317             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13318             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13319             SendToICS(buf);
13320         } else {
13321             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13322                 if(x == BOARD_LEFT-2) {
13323                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13324                     boards[0][y][1] = 0;
13325                 } else
13326                 if(x == BOARD_RGHT+1) {
13327                     if(y >= gameInfo.holdingsSize) break;
13328                     boards[0][y][BOARD_WIDTH-2] = 0;
13329                 } else break;
13330             }
13331             boards[0][y][x] = EmptySquare;
13332             DrawPosition(FALSE, boards[0]);
13333         }
13334         break;
13335
13336       case PromotePiece:
13337         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13338            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13339             selection = (ChessSquare) (PROMOTED piece);
13340         } else if(piece == EmptySquare) selection = WhiteSilver;
13341         else selection = (ChessSquare)((int)piece - 1);
13342         goto defaultlabel;
13343
13344       case DemotePiece:
13345         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13346            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13347             selection = (ChessSquare) (DEMOTED piece);
13348         } else if(piece == EmptySquare) selection = BlackSilver;
13349         else selection = (ChessSquare)((int)piece + 1);
13350         goto defaultlabel;
13351
13352       case WhiteQueen:
13353       case BlackQueen:
13354         if(gameInfo.variant == VariantShatranj ||
13355            gameInfo.variant == VariantXiangqi  ||
13356            gameInfo.variant == VariantCourier  ||
13357            gameInfo.variant == VariantMakruk     )
13358             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13359         goto defaultlabel;
13360
13361       case WhiteKing:
13362       case BlackKing:
13363         if(gameInfo.variant == VariantXiangqi)
13364             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13365         if(gameInfo.variant == VariantKnightmate)
13366             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13367       default:
13368         defaultlabel:
13369         if (gameMode == IcsExamining) {
13370             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13371             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13372                      PieceToChar(selection), AAA + x, ONE + y);
13373             SendToICS(buf);
13374         } else {
13375             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13376                 int n;
13377                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13378                     n = PieceToNumber(selection - BlackPawn);
13379                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13380                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13381                     boards[0][BOARD_HEIGHT-1-n][1]++;
13382                 } else
13383                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13384                     n = PieceToNumber(selection);
13385                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13386                     boards[0][n][BOARD_WIDTH-1] = selection;
13387                     boards[0][n][BOARD_WIDTH-2]++;
13388                 }
13389             } else
13390             boards[0][y][x] = selection;
13391             DrawPosition(TRUE, boards[0]);
13392         }
13393         break;
13394     }
13395 }
13396
13397
13398 void
13399 DropMenuEvent(selection, x, y)
13400      ChessSquare selection;
13401      int x, y;
13402 {
13403     ChessMove moveType;
13404
13405     switch (gameMode) {
13406       case IcsPlayingWhite:
13407       case MachinePlaysBlack:
13408         if (!WhiteOnMove(currentMove)) {
13409             DisplayMoveError(_("It is Black's turn"));
13410             return;
13411         }
13412         moveType = WhiteDrop;
13413         break;
13414       case IcsPlayingBlack:
13415       case MachinePlaysWhite:
13416         if (WhiteOnMove(currentMove)) {
13417             DisplayMoveError(_("It is White's turn"));
13418             return;
13419         }
13420         moveType = BlackDrop;
13421         break;
13422       case EditGame:
13423         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13424         break;
13425       default:
13426         return;
13427     }
13428
13429     if (moveType == BlackDrop && selection < BlackPawn) {
13430       selection = (ChessSquare) ((int) selection
13431                                  + (int) BlackPawn - (int) WhitePawn);
13432     }
13433     if (boards[currentMove][y][x] != EmptySquare) {
13434         DisplayMoveError(_("That square is occupied"));
13435         return;
13436     }
13437
13438     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13439 }
13440
13441 void
13442 AcceptEvent()
13443 {
13444     /* Accept a pending offer of any kind from opponent */
13445
13446     if (appData.icsActive) {
13447         SendToICS(ics_prefix);
13448         SendToICS("accept\n");
13449     } else if (cmailMsgLoaded) {
13450         if (currentMove == cmailOldMove &&
13451             commentList[cmailOldMove] != NULL &&
13452             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13453                    "Black offers a draw" : "White offers a draw")) {
13454             TruncateGame();
13455             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13456             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13457         } else {
13458             DisplayError(_("There is no pending offer on this move"), 0);
13459             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13460         }
13461     } else {
13462         /* Not used for offers from chess program */
13463     }
13464 }
13465
13466 void
13467 DeclineEvent()
13468 {
13469     /* Decline a pending offer of any kind from opponent */
13470
13471     if (appData.icsActive) {
13472         SendToICS(ics_prefix);
13473         SendToICS("decline\n");
13474     } else if (cmailMsgLoaded) {
13475         if (currentMove == cmailOldMove &&
13476             commentList[cmailOldMove] != NULL &&
13477             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13478                    "Black offers a draw" : "White offers a draw")) {
13479 #ifdef NOTDEF
13480             AppendComment(cmailOldMove, "Draw declined", TRUE);
13481             DisplayComment(cmailOldMove - 1, "Draw declined");
13482 #endif /*NOTDEF*/
13483         } else {
13484             DisplayError(_("There is no pending offer on this move"), 0);
13485         }
13486     } else {
13487         /* Not used for offers from chess program */
13488     }
13489 }
13490
13491 void
13492 RematchEvent()
13493 {
13494     /* Issue ICS rematch command */
13495     if (appData.icsActive) {
13496         SendToICS(ics_prefix);
13497         SendToICS("rematch\n");
13498     }
13499 }
13500
13501 void
13502 CallFlagEvent()
13503 {
13504     /* Call your opponent's flag (claim a win on time) */
13505     if (appData.icsActive) {
13506         SendToICS(ics_prefix);
13507         SendToICS("flag\n");
13508     } else {
13509         switch (gameMode) {
13510           default:
13511             return;
13512           case MachinePlaysWhite:
13513             if (whiteFlag) {
13514                 if (blackFlag)
13515                   GameEnds(GameIsDrawn, "Both players ran out of time",
13516                            GE_PLAYER);
13517                 else
13518                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13519             } else {
13520                 DisplayError(_("Your opponent is not out of time"), 0);
13521             }
13522             break;
13523           case MachinePlaysBlack:
13524             if (blackFlag) {
13525                 if (whiteFlag)
13526                   GameEnds(GameIsDrawn, "Both players ran out of time",
13527                            GE_PLAYER);
13528                 else
13529                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13530             } else {
13531                 DisplayError(_("Your opponent is not out of time"), 0);
13532             }
13533             break;
13534         }
13535     }
13536 }
13537
13538 void
13539 ClockClick(int which)
13540 {       // [HGM] code moved to back-end from winboard.c
13541         if(which) { // black clock
13542           if (gameMode == EditPosition || gameMode == IcsExamining) {
13543             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13544             SetBlackToPlayEvent();
13545           } else if (gameMode == EditGame || shiftKey) {
13546             AdjustClock(which, -1);
13547           } else if (gameMode == IcsPlayingWhite ||
13548                      gameMode == MachinePlaysBlack) {
13549             CallFlagEvent();
13550           }
13551         } else { // white clock
13552           if (gameMode == EditPosition || gameMode == IcsExamining) {
13553             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13554             SetWhiteToPlayEvent();
13555           } else if (gameMode == EditGame || shiftKey) {
13556             AdjustClock(which, -1);
13557           } else if (gameMode == IcsPlayingBlack ||
13558                    gameMode == MachinePlaysWhite) {
13559             CallFlagEvent();
13560           }
13561         }
13562 }
13563
13564 void
13565 DrawEvent()
13566 {
13567     /* Offer draw or accept pending draw offer from opponent */
13568
13569     if (appData.icsActive) {
13570         /* Note: tournament rules require draw offers to be
13571            made after you make your move but before you punch
13572            your clock.  Currently ICS doesn't let you do that;
13573            instead, you immediately punch your clock after making
13574            a move, but you can offer a draw at any time. */
13575
13576         SendToICS(ics_prefix);
13577         SendToICS("draw\n");
13578         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13579     } else if (cmailMsgLoaded) {
13580         if (currentMove == cmailOldMove &&
13581             commentList[cmailOldMove] != NULL &&
13582             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13583                    "Black offers a draw" : "White offers a draw")) {
13584             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13585             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13586         } else if (currentMove == cmailOldMove + 1) {
13587             char *offer = WhiteOnMove(cmailOldMove) ?
13588               "White offers a draw" : "Black offers a draw";
13589             AppendComment(currentMove, offer, TRUE);
13590             DisplayComment(currentMove - 1, offer);
13591             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13592         } else {
13593             DisplayError(_("You must make your move before offering a draw"), 0);
13594             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13595         }
13596     } else if (first.offeredDraw) {
13597         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13598     } else {
13599         if (first.sendDrawOffers) {
13600             SendToProgram("draw\n", &first);
13601             userOfferedDraw = TRUE;
13602         }
13603     }
13604 }
13605
13606 void
13607 AdjournEvent()
13608 {
13609     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13610
13611     if (appData.icsActive) {
13612         SendToICS(ics_prefix);
13613         SendToICS("adjourn\n");
13614     } else {
13615         /* Currently GNU Chess doesn't offer or accept Adjourns */
13616     }
13617 }
13618
13619
13620 void
13621 AbortEvent()
13622 {
13623     /* Offer Abort or accept pending Abort offer from opponent */
13624
13625     if (appData.icsActive) {
13626         SendToICS(ics_prefix);
13627         SendToICS("abort\n");
13628     } else {
13629         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13630     }
13631 }
13632
13633 void
13634 ResignEvent()
13635 {
13636     /* Resign.  You can do this even if it's not your turn. */
13637
13638     if (appData.icsActive) {
13639         SendToICS(ics_prefix);
13640         SendToICS("resign\n");
13641     } else {
13642         switch (gameMode) {
13643           case MachinePlaysWhite:
13644             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13645             break;
13646           case MachinePlaysBlack:
13647             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13648             break;
13649           case EditGame:
13650             if (cmailMsgLoaded) {
13651                 TruncateGame();
13652                 if (WhiteOnMove(cmailOldMove)) {
13653                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13654                 } else {
13655                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13656                 }
13657                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13658             }
13659             break;
13660           default:
13661             break;
13662         }
13663     }
13664 }
13665
13666
13667 void
13668 StopObservingEvent()
13669 {
13670     /* Stop observing current games */
13671     SendToICS(ics_prefix);
13672     SendToICS("unobserve\n");
13673 }
13674
13675 void
13676 StopExaminingEvent()
13677 {
13678     /* Stop observing current game */
13679     SendToICS(ics_prefix);
13680     SendToICS("unexamine\n");
13681 }
13682
13683 void
13684 ForwardInner(target)
13685      int target;
13686 {
13687     int limit;
13688
13689     if (appData.debugMode)
13690         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13691                 target, currentMove, forwardMostMove);
13692
13693     if (gameMode == EditPosition)
13694       return;
13695
13696     if (gameMode == PlayFromGameFile && !pausing)
13697       PauseEvent();
13698
13699     if (gameMode == IcsExamining && pausing)
13700       limit = pauseExamForwardMostMove;
13701     else
13702       limit = forwardMostMove;
13703
13704     if (target > limit) target = limit;
13705
13706     if (target > 0 && moveList[target - 1][0]) {
13707         int fromX, fromY, toX, toY;
13708         toX = moveList[target - 1][2] - AAA;
13709         toY = moveList[target - 1][3] - ONE;
13710         if (moveList[target - 1][1] == '@') {
13711             if (appData.highlightLastMove) {
13712                 SetHighlights(-1, -1, toX, toY);
13713             }
13714         } else {
13715             fromX = moveList[target - 1][0] - AAA;
13716             fromY = moveList[target - 1][1] - ONE;
13717             if (target == currentMove + 1) {
13718                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13719             }
13720             if (appData.highlightLastMove) {
13721                 SetHighlights(fromX, fromY, toX, toY);
13722             }
13723         }
13724     }
13725     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13726         gameMode == Training || gameMode == PlayFromGameFile ||
13727         gameMode == AnalyzeFile) {
13728         while (currentMove < target) {
13729             SendMoveToProgram(currentMove++, &first);
13730         }
13731     } else {
13732         currentMove = target;
13733     }
13734
13735     if (gameMode == EditGame || gameMode == EndOfGame) {
13736         whiteTimeRemaining = timeRemaining[0][currentMove];
13737         blackTimeRemaining = timeRemaining[1][currentMove];
13738     }
13739     DisplayBothClocks();
13740     DisplayMove(currentMove - 1);
13741     DrawPosition(FALSE, boards[currentMove]);
13742     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13743     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13744         DisplayComment(currentMove - 1, commentList[currentMove]);
13745     }
13746     DisplayBook(currentMove);
13747 }
13748
13749
13750 void
13751 ForwardEvent()
13752 {
13753     if (gameMode == IcsExamining && !pausing) {
13754         SendToICS(ics_prefix);
13755         SendToICS("forward\n");
13756     } else {
13757         ForwardInner(currentMove + 1);
13758     }
13759 }
13760
13761 void
13762 ToEndEvent()
13763 {
13764     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13765         /* to optimze, we temporarily turn off analysis mode while we feed
13766          * the remaining moves to the engine. Otherwise we get analysis output
13767          * after each move.
13768          */
13769         if (first.analysisSupport) {
13770           SendToProgram("exit\nforce\n", &first);
13771           first.analyzing = FALSE;
13772         }
13773     }
13774
13775     if (gameMode == IcsExamining && !pausing) {
13776         SendToICS(ics_prefix);
13777         SendToICS("forward 999999\n");
13778     } else {
13779         ForwardInner(forwardMostMove);
13780     }
13781
13782     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13783         /* we have fed all the moves, so reactivate analysis mode */
13784         SendToProgram("analyze\n", &first);
13785         first.analyzing = TRUE;
13786         /*first.maybeThinking = TRUE;*/
13787         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13788     }
13789 }
13790
13791 void
13792 BackwardInner(target)
13793      int target;
13794 {
13795     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13796
13797     if (appData.debugMode)
13798         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13799                 target, currentMove, forwardMostMove);
13800
13801     if (gameMode == EditPosition) return;
13802     if (currentMove <= backwardMostMove) {
13803         ClearHighlights();
13804         DrawPosition(full_redraw, boards[currentMove]);
13805         return;
13806     }
13807     if (gameMode == PlayFromGameFile && !pausing)
13808       PauseEvent();
13809
13810     if (moveList[target][0]) {
13811         int fromX, fromY, toX, toY;
13812         toX = moveList[target][2] - AAA;
13813         toY = moveList[target][3] - ONE;
13814         if (moveList[target][1] == '@') {
13815             if (appData.highlightLastMove) {
13816                 SetHighlights(-1, -1, toX, toY);
13817             }
13818         } else {
13819             fromX = moveList[target][0] - AAA;
13820             fromY = moveList[target][1] - ONE;
13821             if (target == currentMove - 1) {
13822                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13823             }
13824             if (appData.highlightLastMove) {
13825                 SetHighlights(fromX, fromY, toX, toY);
13826             }
13827         }
13828     }
13829     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13830         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13831         while (currentMove > target) {
13832             SendToProgram("undo\n", &first);
13833             currentMove--;
13834         }
13835     } else {
13836         currentMove = target;
13837     }
13838
13839     if (gameMode == EditGame || gameMode == EndOfGame) {
13840         whiteTimeRemaining = timeRemaining[0][currentMove];
13841         blackTimeRemaining = timeRemaining[1][currentMove];
13842     }
13843     DisplayBothClocks();
13844     DisplayMove(currentMove - 1);
13845     DrawPosition(full_redraw, boards[currentMove]);
13846     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13847     // [HGM] PV info: routine tests if comment empty
13848     DisplayComment(currentMove - 1, commentList[currentMove]);
13849     DisplayBook(currentMove);
13850 }
13851
13852 void
13853 BackwardEvent()
13854 {
13855     if (gameMode == IcsExamining && !pausing) {
13856         SendToICS(ics_prefix);
13857         SendToICS("backward\n");
13858     } else {
13859         BackwardInner(currentMove - 1);
13860     }
13861 }
13862
13863 void
13864 ToStartEvent()
13865 {
13866     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13867         /* to optimize, we temporarily turn off analysis mode while we undo
13868          * all the moves. Otherwise we get analysis output after each undo.
13869          */
13870         if (first.analysisSupport) {
13871           SendToProgram("exit\nforce\n", &first);
13872           first.analyzing = FALSE;
13873         }
13874     }
13875
13876     if (gameMode == IcsExamining && !pausing) {
13877         SendToICS(ics_prefix);
13878         SendToICS("backward 999999\n");
13879     } else {
13880         BackwardInner(backwardMostMove);
13881     }
13882
13883     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13884         /* we have fed all the moves, so reactivate analysis mode */
13885         SendToProgram("analyze\n", &first);
13886         first.analyzing = TRUE;
13887         /*first.maybeThinking = TRUE;*/
13888         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13889     }
13890 }
13891
13892 void
13893 ToNrEvent(int to)
13894 {
13895   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13896   if (to >= forwardMostMove) to = forwardMostMove;
13897   if (to <= backwardMostMove) to = backwardMostMove;
13898   if (to < currentMove) {
13899     BackwardInner(to);
13900   } else {
13901     ForwardInner(to);
13902   }
13903 }
13904
13905 void
13906 RevertEvent(Boolean annotate)
13907 {
13908     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13909         return;
13910     }
13911     if (gameMode != IcsExamining) {
13912         DisplayError(_("You are not examining a game"), 0);
13913         return;
13914     }
13915     if (pausing) {
13916         DisplayError(_("You can't revert while pausing"), 0);
13917         return;
13918     }
13919     SendToICS(ics_prefix);
13920     SendToICS("revert\n");
13921 }
13922
13923 void
13924 RetractMoveEvent()
13925 {
13926     switch (gameMode) {
13927       case MachinePlaysWhite:
13928       case MachinePlaysBlack:
13929         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13930             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13931             return;
13932         }
13933         if (forwardMostMove < 2) return;
13934         currentMove = forwardMostMove = forwardMostMove - 2;
13935         whiteTimeRemaining = timeRemaining[0][currentMove];
13936         blackTimeRemaining = timeRemaining[1][currentMove];
13937         DisplayBothClocks();
13938         DisplayMove(currentMove - 1);
13939         ClearHighlights();/*!! could figure this out*/
13940         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13941         SendToProgram("remove\n", &first);
13942         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13943         break;
13944
13945       case BeginningOfGame:
13946       default:
13947         break;
13948
13949       case IcsPlayingWhite:
13950       case IcsPlayingBlack:
13951         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13952             SendToICS(ics_prefix);
13953             SendToICS("takeback 2\n");
13954         } else {
13955             SendToICS(ics_prefix);
13956             SendToICS("takeback 1\n");
13957         }
13958         break;
13959     }
13960 }
13961
13962 void
13963 MoveNowEvent()
13964 {
13965     ChessProgramState *cps;
13966
13967     switch (gameMode) {
13968       case MachinePlaysWhite:
13969         if (!WhiteOnMove(forwardMostMove)) {
13970             DisplayError(_("It is your turn"), 0);
13971             return;
13972         }
13973         cps = &first;
13974         break;
13975       case MachinePlaysBlack:
13976         if (WhiteOnMove(forwardMostMove)) {
13977             DisplayError(_("It is your turn"), 0);
13978             return;
13979         }
13980         cps = &first;
13981         break;
13982       case TwoMachinesPlay:
13983         if (WhiteOnMove(forwardMostMove) ==
13984             (first.twoMachinesColor[0] == 'w')) {
13985             cps = &first;
13986         } else {
13987             cps = &second;
13988         }
13989         break;
13990       case BeginningOfGame:
13991       default:
13992         return;
13993     }
13994     SendToProgram("?\n", cps);
13995 }
13996
13997 void
13998 TruncateGameEvent()
13999 {
14000     EditGameEvent();
14001     if (gameMode != EditGame) return;
14002     TruncateGame();
14003 }
14004
14005 void
14006 TruncateGame()
14007 {
14008     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14009     if (forwardMostMove > currentMove) {
14010         if (gameInfo.resultDetails != NULL) {
14011             free(gameInfo.resultDetails);
14012             gameInfo.resultDetails = NULL;
14013             gameInfo.result = GameUnfinished;
14014         }
14015         forwardMostMove = currentMove;
14016         HistorySet(parseList, backwardMostMove, forwardMostMove,
14017                    currentMove-1);
14018     }
14019 }
14020
14021 void
14022 HintEvent()
14023 {
14024     if (appData.noChessProgram) return;
14025     switch (gameMode) {
14026       case MachinePlaysWhite:
14027         if (WhiteOnMove(forwardMostMove)) {
14028             DisplayError(_("Wait until your turn"), 0);
14029             return;
14030         }
14031         break;
14032       case BeginningOfGame:
14033       case MachinePlaysBlack:
14034         if (!WhiteOnMove(forwardMostMove)) {
14035             DisplayError(_("Wait until your turn"), 0);
14036             return;
14037         }
14038         break;
14039       default:
14040         DisplayError(_("No hint available"), 0);
14041         return;
14042     }
14043     SendToProgram("hint\n", &first);
14044     hintRequested = TRUE;
14045 }
14046
14047 void
14048 BookEvent()
14049 {
14050     if (appData.noChessProgram) return;
14051     switch (gameMode) {
14052       case MachinePlaysWhite:
14053         if (WhiteOnMove(forwardMostMove)) {
14054             DisplayError(_("Wait until your turn"), 0);
14055             return;
14056         }
14057         break;
14058       case BeginningOfGame:
14059       case MachinePlaysBlack:
14060         if (!WhiteOnMove(forwardMostMove)) {
14061             DisplayError(_("Wait until your turn"), 0);
14062             return;
14063         }
14064         break;
14065       case EditPosition:
14066         EditPositionDone(TRUE);
14067         break;
14068       case TwoMachinesPlay:
14069         return;
14070       default:
14071         break;
14072     }
14073     SendToProgram("bk\n", &first);
14074     bookOutput[0] = NULLCHAR;
14075     bookRequested = TRUE;
14076 }
14077
14078 void
14079 AboutGameEvent()
14080 {
14081     char *tags = PGNTags(&gameInfo);
14082     TagsPopUp(tags, CmailMsg());
14083     free(tags);
14084 }
14085
14086 /* end button procedures */
14087
14088 void
14089 PrintPosition(fp, move)
14090      FILE *fp;
14091      int move;
14092 {
14093     int i, j;
14094
14095     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14096         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14097             char c = PieceToChar(boards[move][i][j]);
14098             fputc(c == 'x' ? '.' : c, fp);
14099             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14100         }
14101     }
14102     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14103       fprintf(fp, "white to play\n");
14104     else
14105       fprintf(fp, "black to play\n");
14106 }
14107
14108 void
14109 PrintOpponents(fp)
14110      FILE *fp;
14111 {
14112     if (gameInfo.white != NULL) {
14113         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14114     } else {
14115         fprintf(fp, "\n");
14116     }
14117 }
14118
14119 /* Find last component of program's own name, using some heuristics */
14120 void
14121 TidyProgramName(prog, host, buf)
14122      char *prog, *host, buf[MSG_SIZ];
14123 {
14124     char *p, *q;
14125     int local = (strcmp(host, "localhost") == 0);
14126     while (!local && (p = strchr(prog, ';')) != NULL) {
14127         p++;
14128         while (*p == ' ') p++;
14129         prog = p;
14130     }
14131     if (*prog == '"' || *prog == '\'') {
14132         q = strchr(prog + 1, *prog);
14133     } else {
14134         q = strchr(prog, ' ');
14135     }
14136     if (q == NULL) q = prog + strlen(prog);
14137     p = q;
14138     while (p >= prog && *p != '/' && *p != '\\') p--;
14139     p++;
14140     if(p == prog && *p == '"') p++;
14141     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14142     memcpy(buf, p, q - p);
14143     buf[q - p] = NULLCHAR;
14144     if (!local) {
14145         strcat(buf, "@");
14146         strcat(buf, host);
14147     }
14148 }
14149
14150 char *
14151 TimeControlTagValue()
14152 {
14153     char buf[MSG_SIZ];
14154     if (!appData.clockMode) {
14155       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14156     } else if (movesPerSession > 0) {
14157       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14158     } else if (timeIncrement == 0) {
14159       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14160     } else {
14161       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14162     }
14163     return StrSave(buf);
14164 }
14165
14166 void
14167 SetGameInfo()
14168 {
14169     /* This routine is used only for certain modes */
14170     VariantClass v = gameInfo.variant;
14171     ChessMove r = GameUnfinished;
14172     char *p = NULL;
14173
14174     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14175         r = gameInfo.result;
14176         p = gameInfo.resultDetails;
14177         gameInfo.resultDetails = NULL;
14178     }
14179     ClearGameInfo(&gameInfo);
14180     gameInfo.variant = v;
14181
14182     switch (gameMode) {
14183       case MachinePlaysWhite:
14184         gameInfo.event = StrSave( appData.pgnEventHeader );
14185         gameInfo.site = StrSave(HostName());
14186         gameInfo.date = PGNDate();
14187         gameInfo.round = StrSave("-");
14188         gameInfo.white = StrSave(first.tidy);
14189         gameInfo.black = StrSave(UserName());
14190         gameInfo.timeControl = TimeControlTagValue();
14191         break;
14192
14193       case MachinePlaysBlack:
14194         gameInfo.event = StrSave( appData.pgnEventHeader );
14195         gameInfo.site = StrSave(HostName());
14196         gameInfo.date = PGNDate();
14197         gameInfo.round = StrSave("-");
14198         gameInfo.white = StrSave(UserName());
14199         gameInfo.black = StrSave(first.tidy);
14200         gameInfo.timeControl = TimeControlTagValue();
14201         break;
14202
14203       case TwoMachinesPlay:
14204         gameInfo.event = StrSave( appData.pgnEventHeader );
14205         gameInfo.site = StrSave(HostName());
14206         gameInfo.date = PGNDate();
14207         if (roundNr > 0) {
14208             char buf[MSG_SIZ];
14209             snprintf(buf, MSG_SIZ, "%d", roundNr);
14210             gameInfo.round = StrSave(buf);
14211         } else {
14212             gameInfo.round = StrSave("-");
14213         }
14214         if (first.twoMachinesColor[0] == 'w') {
14215             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14216             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14217         } else {
14218             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14219             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14220         }
14221         gameInfo.timeControl = TimeControlTagValue();
14222         break;
14223
14224       case EditGame:
14225         gameInfo.event = StrSave("Edited game");
14226         gameInfo.site = StrSave(HostName());
14227         gameInfo.date = PGNDate();
14228         gameInfo.round = StrSave("-");
14229         gameInfo.white = StrSave("-");
14230         gameInfo.black = StrSave("-");
14231         gameInfo.result = r;
14232         gameInfo.resultDetails = p;
14233         break;
14234
14235       case EditPosition:
14236         gameInfo.event = StrSave("Edited position");
14237         gameInfo.site = StrSave(HostName());
14238         gameInfo.date = PGNDate();
14239         gameInfo.round = StrSave("-");
14240         gameInfo.white = StrSave("-");
14241         gameInfo.black = StrSave("-");
14242         break;
14243
14244       case IcsPlayingWhite:
14245       case IcsPlayingBlack:
14246       case IcsObserving:
14247       case IcsExamining:
14248         break;
14249
14250       case PlayFromGameFile:
14251         gameInfo.event = StrSave("Game from non-PGN file");
14252         gameInfo.site = StrSave(HostName());
14253         gameInfo.date = PGNDate();
14254         gameInfo.round = StrSave("-");
14255         gameInfo.white = StrSave("?");
14256         gameInfo.black = StrSave("?");
14257         break;
14258
14259       default:
14260         break;
14261     }
14262 }
14263
14264 void
14265 ReplaceComment(index, text)
14266      int index;
14267      char *text;
14268 {
14269     int len;
14270     char *p;
14271     float score;
14272
14273     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14274        pvInfoList[index-1].depth == len &&
14275        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14276        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14277     while (*text == '\n') text++;
14278     len = strlen(text);
14279     while (len > 0 && text[len - 1] == '\n') len--;
14280
14281     if (commentList[index] != NULL)
14282       free(commentList[index]);
14283
14284     if (len == 0) {
14285         commentList[index] = NULL;
14286         return;
14287     }
14288   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14289       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14290       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14291     commentList[index] = (char *) malloc(len + 2);
14292     strncpy(commentList[index], text, len);
14293     commentList[index][len] = '\n';
14294     commentList[index][len + 1] = NULLCHAR;
14295   } else {
14296     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14297     char *p;
14298     commentList[index] = (char *) malloc(len + 7);
14299     safeStrCpy(commentList[index], "{\n", 3);
14300     safeStrCpy(commentList[index]+2, text, len+1);
14301     commentList[index][len+2] = NULLCHAR;
14302     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14303     strcat(commentList[index], "\n}\n");
14304   }
14305 }
14306
14307 void
14308 CrushCRs(text)
14309      char *text;
14310 {
14311   char *p = text;
14312   char *q = text;
14313   char ch;
14314
14315   do {
14316     ch = *p++;
14317     if (ch == '\r') continue;
14318     *q++ = ch;
14319   } while (ch != '\0');
14320 }
14321
14322 void
14323 AppendComment(index, text, addBraces)
14324      int index;
14325      char *text;
14326      Boolean addBraces; // [HGM] braces: tells if we should add {}
14327 {
14328     int oldlen, len;
14329     char *old;
14330
14331 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14332     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14333
14334     CrushCRs(text);
14335     while (*text == '\n') text++;
14336     len = strlen(text);
14337     while (len > 0 && text[len - 1] == '\n') len--;
14338
14339     if (len == 0) return;
14340
14341     if (commentList[index] != NULL) {
14342         old = commentList[index];
14343         oldlen = strlen(old);
14344         while(commentList[index][oldlen-1] ==  '\n')
14345           commentList[index][--oldlen] = NULLCHAR;
14346         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14347         safeStrCpy(commentList[index], old, oldlen + len + 6);
14348         free(old);
14349         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14350         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14351           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14352           while (*text == '\n') { text++; len--; }
14353           commentList[index][--oldlen] = NULLCHAR;
14354       }
14355         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14356         else          strcat(commentList[index], "\n");
14357         strcat(commentList[index], text);
14358         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14359         else          strcat(commentList[index], "\n");
14360     } else {
14361         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14362         if(addBraces)
14363           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14364         else commentList[index][0] = NULLCHAR;
14365         strcat(commentList[index], text);
14366         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14367         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14368     }
14369 }
14370
14371 static char * FindStr( char * text, char * sub_text )
14372 {
14373     char * result = strstr( text, sub_text );
14374
14375     if( result != NULL ) {
14376         result += strlen( sub_text );
14377     }
14378
14379     return result;
14380 }
14381
14382 /* [AS] Try to extract PV info from PGN comment */
14383 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14384 char *GetInfoFromComment( int index, char * text )
14385 {
14386     char * sep = text, *p;
14387
14388     if( text != NULL && index > 0 ) {
14389         int score = 0;
14390         int depth = 0;
14391         int time = -1, sec = 0, deci;
14392         char * s_eval = FindStr( text, "[%eval " );
14393         char * s_emt = FindStr( text, "[%emt " );
14394
14395         if( s_eval != NULL || s_emt != NULL ) {
14396             /* New style */
14397             char delim;
14398
14399             if( s_eval != NULL ) {
14400                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14401                     return text;
14402                 }
14403
14404                 if( delim != ']' ) {
14405                     return text;
14406                 }
14407             }
14408
14409             if( s_emt != NULL ) {
14410             }
14411                 return text;
14412         }
14413         else {
14414             /* We expect something like: [+|-]nnn.nn/dd */
14415             int score_lo = 0;
14416
14417             if(*text != '{') return text; // [HGM] braces: must be normal comment
14418
14419             sep = strchr( text, '/' );
14420             if( sep == NULL || sep < (text+4) ) {
14421                 return text;
14422             }
14423
14424             p = text;
14425             if(p[1] == '(') { // comment starts with PV
14426                p = strchr(p, ')'); // locate end of PV
14427                if(p == NULL || sep < p+5) return text;
14428                // at this point we have something like "{(.*) +0.23/6 ..."
14429                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14430                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14431                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14432             }
14433             time = -1; sec = -1; deci = -1;
14434             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14435                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14436                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14437                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14438                 return text;
14439             }
14440
14441             if( score_lo < 0 || score_lo >= 100 ) {
14442                 return text;
14443             }
14444
14445             if(sec >= 0) time = 600*time + 10*sec; else
14446             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14447
14448             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14449
14450             /* [HGM] PV time: now locate end of PV info */
14451             while( *++sep >= '0' && *sep <= '9'); // strip depth
14452             if(time >= 0)
14453             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14454             if(sec >= 0)
14455             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14456             if(deci >= 0)
14457             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14458             while(*sep == ' ') sep++;
14459         }
14460
14461         if( depth <= 0 ) {
14462             return text;
14463         }
14464
14465         if( time < 0 ) {
14466             time = -1;
14467         }
14468
14469         pvInfoList[index-1].depth = depth;
14470         pvInfoList[index-1].score = score;
14471         pvInfoList[index-1].time  = 10*time; // centi-sec
14472         if(*sep == '}') *sep = 0; else *--sep = '{';
14473         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14474     }
14475     return sep;
14476 }
14477
14478 void
14479 SendToProgram(message, cps)
14480      char *message;
14481      ChessProgramState *cps;
14482 {
14483     int count, outCount, error;
14484     char buf[MSG_SIZ];
14485
14486     if (cps->pr == NULL) return;
14487     Attention(cps);
14488
14489     if (appData.debugMode) {
14490         TimeMark now;
14491         GetTimeMark(&now);
14492         fprintf(debugFP, "%ld >%-6s: %s",
14493                 SubtractTimeMarks(&now, &programStartTime),
14494                 cps->which, message);
14495     }
14496
14497     count = strlen(message);
14498     outCount = OutputToProcess(cps->pr, message, count, &error);
14499     if (outCount < count && !exiting
14500                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14501       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14502       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14503         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14504             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14505                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14506                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14507                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14508             } else {
14509                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14510                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14511                 gameInfo.result = res;
14512             }
14513             gameInfo.resultDetails = StrSave(buf);
14514         }
14515         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14516         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14517     }
14518 }
14519
14520 void
14521 ReceiveFromProgram(isr, closure, message, count, error)
14522      InputSourceRef isr;
14523      VOIDSTAR closure;
14524      char *message;
14525      int count;
14526      int error;
14527 {
14528     char *end_str;
14529     char buf[MSG_SIZ];
14530     ChessProgramState *cps = (ChessProgramState *)closure;
14531
14532     if (isr != cps->isr) return; /* Killed intentionally */
14533     if (count <= 0) {
14534         if (count == 0) {
14535             RemoveInputSource(cps->isr);
14536             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14537             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14538                     _(cps->which), cps->program);
14539         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14540                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14541                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14542                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14543                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14544                 } else {
14545                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14546                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14547                     gameInfo.result = res;
14548                 }
14549                 gameInfo.resultDetails = StrSave(buf);
14550             }
14551             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14552             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14553         } else {
14554             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14555                     _(cps->which), cps->program);
14556             RemoveInputSource(cps->isr);
14557
14558             /* [AS] Program is misbehaving badly... kill it */
14559             if( count == -2 ) {
14560                 DestroyChildProcess( cps->pr, 9 );
14561                 cps->pr = NoProc;
14562             }
14563
14564             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14565         }
14566         return;
14567     }
14568
14569     if ((end_str = strchr(message, '\r')) != NULL)
14570       *end_str = NULLCHAR;
14571     if ((end_str = strchr(message, '\n')) != NULL)
14572       *end_str = NULLCHAR;
14573
14574     if (appData.debugMode) {
14575         TimeMark now; int print = 1;
14576         char *quote = ""; char c; int i;
14577
14578         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14579                 char start = message[0];
14580                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14581                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14582                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14583                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14584                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14585                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14586                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14587                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14588                    sscanf(message, "hint: %c", &c)!=1 && 
14589                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14590                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14591                     print = (appData.engineComments >= 2);
14592                 }
14593                 message[0] = start; // restore original message
14594         }
14595         if(print) {
14596                 GetTimeMark(&now);
14597                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14598                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14599                         quote,
14600                         message);
14601         }
14602     }
14603
14604     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14605     if (appData.icsEngineAnalyze) {
14606         if (strstr(message, "whisper") != NULL ||
14607              strstr(message, "kibitz") != NULL ||
14608             strstr(message, "tellics") != NULL) return;
14609     }
14610
14611     HandleMachineMove(message, cps);
14612 }
14613
14614
14615 void
14616 SendTimeControl(cps, mps, tc, inc, sd, st)
14617      ChessProgramState *cps;
14618      int mps, inc, sd, st;
14619      long tc;
14620 {
14621     char buf[MSG_SIZ];
14622     int seconds;
14623
14624     if( timeControl_2 > 0 ) {
14625         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14626             tc = timeControl_2;
14627         }
14628     }
14629     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14630     inc /= cps->timeOdds;
14631     st  /= cps->timeOdds;
14632
14633     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14634
14635     if (st > 0) {
14636       /* Set exact time per move, normally using st command */
14637       if (cps->stKludge) {
14638         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14639         seconds = st % 60;
14640         if (seconds == 0) {
14641           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14642         } else {
14643           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14644         }
14645       } else {
14646         snprintf(buf, MSG_SIZ, "st %d\n", st);
14647       }
14648     } else {
14649       /* Set conventional or incremental time control, using level command */
14650       if (seconds == 0) {
14651         /* Note old gnuchess bug -- minutes:seconds used to not work.
14652            Fixed in later versions, but still avoid :seconds
14653            when seconds is 0. */
14654         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14655       } else {
14656         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14657                  seconds, inc/1000.);
14658       }
14659     }
14660     SendToProgram(buf, cps);
14661
14662     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14663     /* Orthogonally, limit search to given depth */
14664     if (sd > 0) {
14665       if (cps->sdKludge) {
14666         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14667       } else {
14668         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14669       }
14670       SendToProgram(buf, cps);
14671     }
14672
14673     if(cps->nps >= 0) { /* [HGM] nps */
14674         if(cps->supportsNPS == FALSE)
14675           cps->nps = -1; // don't use if engine explicitly says not supported!
14676         else {
14677           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14678           SendToProgram(buf, cps);
14679         }
14680     }
14681 }
14682
14683 ChessProgramState *WhitePlayer()
14684 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14685 {
14686     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14687        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14688         return &second;
14689     return &first;
14690 }
14691
14692 void
14693 SendTimeRemaining(cps, machineWhite)
14694      ChessProgramState *cps;
14695      int /*boolean*/ machineWhite;
14696 {
14697     char message[MSG_SIZ];
14698     long time, otime;
14699
14700     /* Note: this routine must be called when the clocks are stopped
14701        or when they have *just* been set or switched; otherwise
14702        it will be off by the time since the current tick started.
14703     */
14704     if (machineWhite) {
14705         time = whiteTimeRemaining / 10;
14706         otime = blackTimeRemaining / 10;
14707     } else {
14708         time = blackTimeRemaining / 10;
14709         otime = whiteTimeRemaining / 10;
14710     }
14711     /* [HGM] translate opponent's time by time-odds factor */
14712     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14713     if (appData.debugMode) {
14714         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14715     }
14716
14717     if (time <= 0) time = 1;
14718     if (otime <= 0) otime = 1;
14719
14720     snprintf(message, MSG_SIZ, "time %ld\n", time);
14721     SendToProgram(message, cps);
14722
14723     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14724     SendToProgram(message, cps);
14725 }
14726
14727 int
14728 BoolFeature(p, name, loc, cps)
14729      char **p;
14730      char *name;
14731      int *loc;
14732      ChessProgramState *cps;
14733 {
14734   char buf[MSG_SIZ];
14735   int len = strlen(name);
14736   int val;
14737
14738   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14739     (*p) += len + 1;
14740     sscanf(*p, "%d", &val);
14741     *loc = (val != 0);
14742     while (**p && **p != ' ')
14743       (*p)++;
14744     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14745     SendToProgram(buf, cps);
14746     return TRUE;
14747   }
14748   return FALSE;
14749 }
14750
14751 int
14752 IntFeature(p, name, loc, cps)
14753      char **p;
14754      char *name;
14755      int *loc;
14756      ChessProgramState *cps;
14757 {
14758   char buf[MSG_SIZ];
14759   int len = strlen(name);
14760   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14761     (*p) += len + 1;
14762     sscanf(*p, "%d", loc);
14763     while (**p && **p != ' ') (*p)++;
14764     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14765     SendToProgram(buf, cps);
14766     return TRUE;
14767   }
14768   return FALSE;
14769 }
14770
14771 int
14772 StringFeature(p, name, loc, cps)
14773      char **p;
14774      char *name;
14775      char loc[];
14776      ChessProgramState *cps;
14777 {
14778   char buf[MSG_SIZ];
14779   int len = strlen(name);
14780   if (strncmp((*p), name, len) == 0
14781       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14782     (*p) += len + 2;
14783     sscanf(*p, "%[^\"]", loc);
14784     while (**p && **p != '\"') (*p)++;
14785     if (**p == '\"') (*p)++;
14786     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14787     SendToProgram(buf, cps);
14788     return TRUE;
14789   }
14790   return FALSE;
14791 }
14792
14793 int
14794 ParseOption(Option *opt, ChessProgramState *cps)
14795 // [HGM] options: process the string that defines an engine option, and determine
14796 // name, type, default value, and allowed value range
14797 {
14798         char *p, *q, buf[MSG_SIZ];
14799         int n, min = (-1)<<31, max = 1<<31, def;
14800
14801         if(p = strstr(opt->name, " -spin ")) {
14802             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14803             if(max < min) max = min; // enforce consistency
14804             if(def < min) def = min;
14805             if(def > max) def = max;
14806             opt->value = def;
14807             opt->min = min;
14808             opt->max = max;
14809             opt->type = Spin;
14810         } else if((p = strstr(opt->name, " -slider "))) {
14811             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14812             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14813             if(max < min) max = min; // enforce consistency
14814             if(def < min) def = min;
14815             if(def > max) def = max;
14816             opt->value = def;
14817             opt->min = min;
14818             opt->max = max;
14819             opt->type = Spin; // Slider;
14820         } else if((p = strstr(opt->name, " -string "))) {
14821             opt->textValue = p+9;
14822             opt->type = TextBox;
14823         } else if((p = strstr(opt->name, " -file "))) {
14824             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14825             opt->textValue = p+7;
14826             opt->type = FileName; // FileName;
14827         } else if((p = strstr(opt->name, " -path "))) {
14828             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14829             opt->textValue = p+7;
14830             opt->type = PathName; // PathName;
14831         } else if(p = strstr(opt->name, " -check ")) {
14832             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14833             opt->value = (def != 0);
14834             opt->type = CheckBox;
14835         } else if(p = strstr(opt->name, " -combo ")) {
14836             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14837             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14838             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14839             opt->value = n = 0;
14840             while(q = StrStr(q, " /// ")) {
14841                 n++; *q = 0;    // count choices, and null-terminate each of them
14842                 q += 5;
14843                 if(*q == '*') { // remember default, which is marked with * prefix
14844                     q++;
14845                     opt->value = n;
14846                 }
14847                 cps->comboList[cps->comboCnt++] = q;
14848             }
14849             cps->comboList[cps->comboCnt++] = NULL;
14850             opt->max = n + 1;
14851             opt->type = ComboBox;
14852         } else if(p = strstr(opt->name, " -button")) {
14853             opt->type = Button;
14854         } else if(p = strstr(opt->name, " -save")) {
14855             opt->type = SaveButton;
14856         } else return FALSE;
14857         *p = 0; // terminate option name
14858         // now look if the command-line options define a setting for this engine option.
14859         if(cps->optionSettings && cps->optionSettings[0])
14860             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14861         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14862           snprintf(buf, MSG_SIZ, "option %s", p);
14863                 if(p = strstr(buf, ",")) *p = 0;
14864                 if(q = strchr(buf, '=')) switch(opt->type) {
14865                     case ComboBox:
14866                         for(n=0; n<opt->max; n++)
14867                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14868                         break;
14869                     case TextBox:
14870                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14871                         break;
14872                     case Spin:
14873                     case CheckBox:
14874                         opt->value = atoi(q+1);
14875                     default:
14876                         break;
14877                 }
14878                 strcat(buf, "\n");
14879                 SendToProgram(buf, cps);
14880         }
14881         return TRUE;
14882 }
14883
14884 void
14885 FeatureDone(cps, val)
14886      ChessProgramState* cps;
14887      int val;
14888 {
14889   DelayedEventCallback cb = GetDelayedEvent();
14890   if ((cb == InitBackEnd3 && cps == &first) ||
14891       (cb == SettingsMenuIfReady && cps == &second) ||
14892       (cb == LoadEngine) ||
14893       (cb == TwoMachinesEventIfReady)) {
14894     CancelDelayedEvent();
14895     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14896   }
14897   cps->initDone = val;
14898 }
14899
14900 /* Parse feature command from engine */
14901 void
14902 ParseFeatures(args, cps)
14903      char* args;
14904      ChessProgramState *cps;
14905 {
14906   char *p = args;
14907   char *q;
14908   int val;
14909   char buf[MSG_SIZ];
14910
14911   for (;;) {
14912     while (*p == ' ') p++;
14913     if (*p == NULLCHAR) return;
14914
14915     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14916     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14917     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14918     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14919     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14920     if (BoolFeature(&p, "reuse", &val, cps)) {
14921       /* Engine can disable reuse, but can't enable it if user said no */
14922       if (!val) cps->reuse = FALSE;
14923       continue;
14924     }
14925     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14926     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14927       if (gameMode == TwoMachinesPlay) {
14928         DisplayTwoMachinesTitle();
14929       } else {
14930         DisplayTitle("");
14931       }
14932       continue;
14933     }
14934     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14935     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14936     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14937     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14938     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14939     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14940     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14941     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14942     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14943     if (IntFeature(&p, "done", &val, cps)) {
14944       FeatureDone(cps, val);
14945       continue;
14946     }
14947     /* Added by Tord: */
14948     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14949     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14950     /* End of additions by Tord */
14951
14952     /* [HGM] added features: */
14953     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14954     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14955     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14956     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14957     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14958     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14959     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14960         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14961           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14962             SendToProgram(buf, cps);
14963             continue;
14964         }
14965         if(cps->nrOptions >= MAX_OPTIONS) {
14966             cps->nrOptions--;
14967             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14968             DisplayError(buf, 0);
14969         }
14970         continue;
14971     }
14972     /* End of additions by HGM */
14973
14974     /* unknown feature: complain and skip */
14975     q = p;
14976     while (*q && *q != '=') q++;
14977     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14978     SendToProgram(buf, cps);
14979     p = q;
14980     if (*p == '=') {
14981       p++;
14982       if (*p == '\"') {
14983         p++;
14984         while (*p && *p != '\"') p++;
14985         if (*p == '\"') p++;
14986       } else {
14987         while (*p && *p != ' ') p++;
14988       }
14989     }
14990   }
14991
14992 }
14993
14994 void
14995 PeriodicUpdatesEvent(newState)
14996      int newState;
14997 {
14998     if (newState == appData.periodicUpdates)
14999       return;
15000
15001     appData.periodicUpdates=newState;
15002
15003     /* Display type changes, so update it now */
15004 //    DisplayAnalysis();
15005
15006     /* Get the ball rolling again... */
15007     if (newState) {
15008         AnalysisPeriodicEvent(1);
15009         StartAnalysisClock();
15010     }
15011 }
15012
15013 void
15014 PonderNextMoveEvent(newState)
15015      int newState;
15016 {
15017     if (newState == appData.ponderNextMove) return;
15018     if (gameMode == EditPosition) EditPositionDone(TRUE);
15019     if (newState) {
15020         SendToProgram("hard\n", &first);
15021         if (gameMode == TwoMachinesPlay) {
15022             SendToProgram("hard\n", &second);
15023         }
15024     } else {
15025         SendToProgram("easy\n", &first);
15026         thinkOutput[0] = NULLCHAR;
15027         if (gameMode == TwoMachinesPlay) {
15028             SendToProgram("easy\n", &second);
15029         }
15030     }
15031     appData.ponderNextMove = newState;
15032 }
15033
15034 void
15035 NewSettingEvent(option, feature, command, value)
15036      char *command;
15037      int option, value, *feature;
15038 {
15039     char buf[MSG_SIZ];
15040
15041     if (gameMode == EditPosition) EditPositionDone(TRUE);
15042     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15043     if(feature == NULL || *feature) SendToProgram(buf, &first);
15044     if (gameMode == TwoMachinesPlay) {
15045         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15046     }
15047 }
15048
15049 void
15050 ShowThinkingEvent()
15051 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15052 {
15053     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15054     int newState = appData.showThinking
15055         // [HGM] thinking: other features now need thinking output as well
15056         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15057
15058     if (oldState == newState) return;
15059     oldState = newState;
15060     if (gameMode == EditPosition) EditPositionDone(TRUE);
15061     if (oldState) {
15062         SendToProgram("post\n", &first);
15063         if (gameMode == TwoMachinesPlay) {
15064             SendToProgram("post\n", &second);
15065         }
15066     } else {
15067         SendToProgram("nopost\n", &first);
15068         thinkOutput[0] = NULLCHAR;
15069         if (gameMode == TwoMachinesPlay) {
15070             SendToProgram("nopost\n", &second);
15071         }
15072     }
15073 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15074 }
15075
15076 void
15077 AskQuestionEvent(title, question, replyPrefix, which)
15078      char *title; char *question; char *replyPrefix; char *which;
15079 {
15080   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15081   if (pr == NoProc) return;
15082   AskQuestion(title, question, replyPrefix, pr);
15083 }
15084
15085 void
15086 TypeInEvent(char firstChar)
15087 {
15088     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15089         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15090         gameMode == AnalyzeMode || gameMode == EditGame || 
15091         gameMode == EditPosition || gameMode == IcsExamining ||
15092         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15093         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15094                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15095                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15096         gameMode == Training) PopUpMoveDialog(firstChar);
15097 }
15098
15099 void
15100 TypeInDoneEvent(char *move)
15101 {
15102         Board board;
15103         int n, fromX, fromY, toX, toY;
15104         char promoChar;
15105         ChessMove moveType;
15106
15107         // [HGM] FENedit
15108         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15109                 EditPositionPasteFEN(move);
15110                 return;
15111         }
15112         // [HGM] movenum: allow move number to be typed in any mode
15113         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15114           ToNrEvent(2*n-1);
15115           return;
15116         }
15117
15118       if (gameMode != EditGame && currentMove != forwardMostMove && 
15119         gameMode != Training) {
15120         DisplayMoveError(_("Displayed move is not current"));
15121       } else {
15122         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15123           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15124         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15125         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15126           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15127           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15128         } else {
15129           DisplayMoveError(_("Could not parse move"));
15130         }
15131       }
15132 }
15133
15134 void
15135 DisplayMove(moveNumber)
15136      int moveNumber;
15137 {
15138     char message[MSG_SIZ];
15139     char res[MSG_SIZ];
15140     char cpThinkOutput[MSG_SIZ];
15141
15142     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15143
15144     if (moveNumber == forwardMostMove - 1 ||
15145         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15146
15147         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15148
15149         if (strchr(cpThinkOutput, '\n')) {
15150             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15151         }
15152     } else {
15153         *cpThinkOutput = NULLCHAR;
15154     }
15155
15156     /* [AS] Hide thinking from human user */
15157     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15158         *cpThinkOutput = NULLCHAR;
15159         if( thinkOutput[0] != NULLCHAR ) {
15160             int i;
15161
15162             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15163                 cpThinkOutput[i] = '.';
15164             }
15165             cpThinkOutput[i] = NULLCHAR;
15166             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15167         }
15168     }
15169
15170     if (moveNumber == forwardMostMove - 1 &&
15171         gameInfo.resultDetails != NULL) {
15172         if (gameInfo.resultDetails[0] == NULLCHAR) {
15173           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15174         } else {
15175           snprintf(res, MSG_SIZ, " {%s} %s",
15176                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15177         }
15178     } else {
15179         res[0] = NULLCHAR;
15180     }
15181
15182     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15183         DisplayMessage(res, cpThinkOutput);
15184     } else {
15185       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15186                 WhiteOnMove(moveNumber) ? " " : ".. ",
15187                 parseList[moveNumber], res);
15188         DisplayMessage(message, cpThinkOutput);
15189     }
15190 }
15191
15192 void
15193 DisplayComment(moveNumber, text)
15194      int moveNumber;
15195      char *text;
15196 {
15197     char title[MSG_SIZ];
15198     char buf[8000]; // comment can be long!
15199     int score, depth;
15200
15201     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15202       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15203     } else {
15204       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15205               WhiteOnMove(moveNumber) ? " " : ".. ",
15206               parseList[moveNumber]);
15207     }
15208     // [HGM] PV info: display PV info together with (or as) comment
15209     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15210       if(text == NULL) text = "";
15211       score = pvInfoList[moveNumber].score;
15212       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15213               depth, (pvInfoList[moveNumber].time+50)/100, text);
15214       text = buf;
15215     }
15216     if (text != NULL && (appData.autoDisplayComment || commentUp))
15217         CommentPopUp(title, text);
15218 }
15219
15220 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15221  * might be busy thinking or pondering.  It can be omitted if your
15222  * gnuchess is configured to stop thinking immediately on any user
15223  * input.  However, that gnuchess feature depends on the FIONREAD
15224  * ioctl, which does not work properly on some flavors of Unix.
15225  */
15226 void
15227 Attention(cps)
15228      ChessProgramState *cps;
15229 {
15230 #if ATTENTION
15231     if (!cps->useSigint) return;
15232     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15233     switch (gameMode) {
15234       case MachinePlaysWhite:
15235       case MachinePlaysBlack:
15236       case TwoMachinesPlay:
15237       case IcsPlayingWhite:
15238       case IcsPlayingBlack:
15239       case AnalyzeMode:
15240       case AnalyzeFile:
15241         /* Skip if we know it isn't thinking */
15242         if (!cps->maybeThinking) return;
15243         if (appData.debugMode)
15244           fprintf(debugFP, "Interrupting %s\n", cps->which);
15245         InterruptChildProcess(cps->pr);
15246         cps->maybeThinking = FALSE;
15247         break;
15248       default:
15249         break;
15250     }
15251 #endif /*ATTENTION*/
15252 }
15253
15254 int
15255 CheckFlags()
15256 {
15257     if (whiteTimeRemaining <= 0) {
15258         if (!whiteFlag) {
15259             whiteFlag = TRUE;
15260             if (appData.icsActive) {
15261                 if (appData.autoCallFlag &&
15262                     gameMode == IcsPlayingBlack && !blackFlag) {
15263                   SendToICS(ics_prefix);
15264                   SendToICS("flag\n");
15265                 }
15266             } else {
15267                 if (blackFlag) {
15268                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15269                 } else {
15270                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15271                     if (appData.autoCallFlag) {
15272                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15273                         return TRUE;
15274                     }
15275                 }
15276             }
15277         }
15278     }
15279     if (blackTimeRemaining <= 0) {
15280         if (!blackFlag) {
15281             blackFlag = TRUE;
15282             if (appData.icsActive) {
15283                 if (appData.autoCallFlag &&
15284                     gameMode == IcsPlayingWhite && !whiteFlag) {
15285                   SendToICS(ics_prefix);
15286                   SendToICS("flag\n");
15287                 }
15288             } else {
15289                 if (whiteFlag) {
15290                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15291                 } else {
15292                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15293                     if (appData.autoCallFlag) {
15294                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15295                         return TRUE;
15296                     }
15297                 }
15298             }
15299         }
15300     }
15301     return FALSE;
15302 }
15303
15304 void
15305 CheckTimeControl()
15306 {
15307     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15308         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15309
15310     /*
15311      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15312      */
15313     if ( !WhiteOnMove(forwardMostMove) ) {
15314         /* White made time control */
15315         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15316         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15317         /* [HGM] time odds: correct new time quota for time odds! */
15318                                             / WhitePlayer()->timeOdds;
15319         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15320     } else {
15321         lastBlack -= blackTimeRemaining;
15322         /* Black made time control */
15323         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15324                                             / WhitePlayer()->other->timeOdds;
15325         lastWhite = whiteTimeRemaining;
15326     }
15327 }
15328
15329 void
15330 DisplayBothClocks()
15331 {
15332     int wom = gameMode == EditPosition ?
15333       !blackPlaysFirst : WhiteOnMove(currentMove);
15334     DisplayWhiteClock(whiteTimeRemaining, wom);
15335     DisplayBlackClock(blackTimeRemaining, !wom);
15336 }
15337
15338
15339 /* Timekeeping seems to be a portability nightmare.  I think everyone
15340    has ftime(), but I'm really not sure, so I'm including some ifdefs
15341    to use other calls if you don't.  Clocks will be less accurate if
15342    you have neither ftime nor gettimeofday.
15343 */
15344
15345 /* VS 2008 requires the #include outside of the function */
15346 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15347 #include <sys/timeb.h>
15348 #endif
15349
15350 /* Get the current time as a TimeMark */
15351 void
15352 GetTimeMark(tm)
15353      TimeMark *tm;
15354 {
15355 #if HAVE_GETTIMEOFDAY
15356
15357     struct timeval timeVal;
15358     struct timezone timeZone;
15359
15360     gettimeofday(&timeVal, &timeZone);
15361     tm->sec = (long) timeVal.tv_sec;
15362     tm->ms = (int) (timeVal.tv_usec / 1000L);
15363
15364 #else /*!HAVE_GETTIMEOFDAY*/
15365 #if HAVE_FTIME
15366
15367 // include <sys/timeb.h> / moved to just above start of function
15368     struct timeb timeB;
15369
15370     ftime(&timeB);
15371     tm->sec = (long) timeB.time;
15372     tm->ms = (int) timeB.millitm;
15373
15374 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15375     tm->sec = (long) time(NULL);
15376     tm->ms = 0;
15377 #endif
15378 #endif
15379 }
15380
15381 /* Return the difference in milliseconds between two
15382    time marks.  We assume the difference will fit in a long!
15383 */
15384 long
15385 SubtractTimeMarks(tm2, tm1)
15386      TimeMark *tm2, *tm1;
15387 {
15388     return 1000L*(tm2->sec - tm1->sec) +
15389            (long) (tm2->ms - tm1->ms);
15390 }
15391
15392
15393 /*
15394  * Code to manage the game clocks.
15395  *
15396  * In tournament play, black starts the clock and then white makes a move.
15397  * We give the human user a slight advantage if he is playing white---the
15398  * clocks don't run until he makes his first move, so it takes zero time.
15399  * Also, we don't account for network lag, so we could get out of sync
15400  * with GNU Chess's clock -- but then, referees are always right.
15401  */
15402
15403 static TimeMark tickStartTM;
15404 static long intendedTickLength;
15405
15406 long
15407 NextTickLength(timeRemaining)
15408      long timeRemaining;
15409 {
15410     long nominalTickLength, nextTickLength;
15411
15412     if (timeRemaining > 0L && timeRemaining <= 10000L)
15413       nominalTickLength = 100L;
15414     else
15415       nominalTickLength = 1000L;
15416     nextTickLength = timeRemaining % nominalTickLength;
15417     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15418
15419     return nextTickLength;
15420 }
15421
15422 /* Adjust clock one minute up or down */
15423 void
15424 AdjustClock(Boolean which, int dir)
15425 {
15426     if(which) blackTimeRemaining += 60000*dir;
15427     else      whiteTimeRemaining += 60000*dir;
15428     DisplayBothClocks();
15429 }
15430
15431 /* Stop clocks and reset to a fresh time control */
15432 void
15433 ResetClocks()
15434 {
15435     (void) StopClockTimer();
15436     if (appData.icsActive) {
15437         whiteTimeRemaining = blackTimeRemaining = 0;
15438     } else if (searchTime) {
15439         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15440         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15441     } else { /* [HGM] correct new time quote for time odds */
15442         whiteTC = blackTC = fullTimeControlString;
15443         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15444         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15445     }
15446     if (whiteFlag || blackFlag) {
15447         DisplayTitle("");
15448         whiteFlag = blackFlag = FALSE;
15449     }
15450     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15451     DisplayBothClocks();
15452 }
15453
15454 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15455
15456 /* Decrement running clock by amount of time that has passed */
15457 void
15458 DecrementClocks()
15459 {
15460     long timeRemaining;
15461     long lastTickLength, fudge;
15462     TimeMark now;
15463
15464     if (!appData.clockMode) return;
15465     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15466
15467     GetTimeMark(&now);
15468
15469     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15470
15471     /* Fudge if we woke up a little too soon */
15472     fudge = intendedTickLength - lastTickLength;
15473     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15474
15475     if (WhiteOnMove(forwardMostMove)) {
15476         if(whiteNPS >= 0) lastTickLength = 0;
15477         timeRemaining = whiteTimeRemaining -= lastTickLength;
15478         if(timeRemaining < 0 && !appData.icsActive) {
15479             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15480             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15481                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15482                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15483             }
15484         }
15485         DisplayWhiteClock(whiteTimeRemaining - fudge,
15486                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15487     } else {
15488         if(blackNPS >= 0) lastTickLength = 0;
15489         timeRemaining = blackTimeRemaining -= lastTickLength;
15490         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15491             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15492             if(suddenDeath) {
15493                 blackStartMove = forwardMostMove;
15494                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15495             }
15496         }
15497         DisplayBlackClock(blackTimeRemaining - fudge,
15498                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15499     }
15500     if (CheckFlags()) return;
15501
15502     tickStartTM = now;
15503     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15504     StartClockTimer(intendedTickLength);
15505
15506     /* if the time remaining has fallen below the alarm threshold, sound the
15507      * alarm. if the alarm has sounded and (due to a takeback or time control
15508      * with increment) the time remaining has increased to a level above the
15509      * threshold, reset the alarm so it can sound again.
15510      */
15511
15512     if (appData.icsActive && appData.icsAlarm) {
15513
15514         /* make sure we are dealing with the user's clock */
15515         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15516                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15517            )) return;
15518
15519         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15520             alarmSounded = FALSE;
15521         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15522             PlayAlarmSound();
15523             alarmSounded = TRUE;
15524         }
15525     }
15526 }
15527
15528
15529 /* A player has just moved, so stop the previously running
15530    clock and (if in clock mode) start the other one.
15531    We redisplay both clocks in case we're in ICS mode, because
15532    ICS gives us an update to both clocks after every move.
15533    Note that this routine is called *after* forwardMostMove
15534    is updated, so the last fractional tick must be subtracted
15535    from the color that is *not* on move now.
15536 */
15537 void
15538 SwitchClocks(int newMoveNr)
15539 {
15540     long lastTickLength;
15541     TimeMark now;
15542     int flagged = FALSE;
15543
15544     GetTimeMark(&now);
15545
15546     if (StopClockTimer() && appData.clockMode) {
15547         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15548         if (!WhiteOnMove(forwardMostMove)) {
15549             if(blackNPS >= 0) lastTickLength = 0;
15550             blackTimeRemaining -= lastTickLength;
15551            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15552 //         if(pvInfoList[forwardMostMove].time == -1)
15553                  pvInfoList[forwardMostMove].time =               // use GUI time
15554                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15555         } else {
15556            if(whiteNPS >= 0) lastTickLength = 0;
15557            whiteTimeRemaining -= lastTickLength;
15558            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15559 //         if(pvInfoList[forwardMostMove].time == -1)
15560                  pvInfoList[forwardMostMove].time =
15561                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15562         }
15563         flagged = CheckFlags();
15564     }
15565     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15566     CheckTimeControl();
15567
15568     if (flagged || !appData.clockMode) return;
15569
15570     switch (gameMode) {
15571       case MachinePlaysBlack:
15572       case MachinePlaysWhite:
15573       case BeginningOfGame:
15574         if (pausing) return;
15575         break;
15576
15577       case EditGame:
15578       case PlayFromGameFile:
15579       case IcsExamining:
15580         return;
15581
15582       default:
15583         break;
15584     }
15585
15586     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15587         if(WhiteOnMove(forwardMostMove))
15588              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15589         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15590     }
15591
15592     tickStartTM = now;
15593     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15594       whiteTimeRemaining : blackTimeRemaining);
15595     StartClockTimer(intendedTickLength);
15596 }
15597
15598
15599 /* Stop both clocks */
15600 void
15601 StopClocks()
15602 {
15603     long lastTickLength;
15604     TimeMark now;
15605
15606     if (!StopClockTimer()) return;
15607     if (!appData.clockMode) return;
15608
15609     GetTimeMark(&now);
15610
15611     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15612     if (WhiteOnMove(forwardMostMove)) {
15613         if(whiteNPS >= 0) lastTickLength = 0;
15614         whiteTimeRemaining -= lastTickLength;
15615         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15616     } else {
15617         if(blackNPS >= 0) lastTickLength = 0;
15618         blackTimeRemaining -= lastTickLength;
15619         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15620     }
15621     CheckFlags();
15622 }
15623
15624 /* Start clock of player on move.  Time may have been reset, so
15625    if clock is already running, stop and restart it. */
15626 void
15627 StartClocks()
15628 {
15629     (void) StopClockTimer(); /* in case it was running already */
15630     DisplayBothClocks();
15631     if (CheckFlags()) return;
15632
15633     if (!appData.clockMode) return;
15634     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15635
15636     GetTimeMark(&tickStartTM);
15637     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15638       whiteTimeRemaining : blackTimeRemaining);
15639
15640    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15641     whiteNPS = blackNPS = -1;
15642     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15643        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15644         whiteNPS = first.nps;
15645     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15646        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15647         blackNPS = first.nps;
15648     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15649         whiteNPS = second.nps;
15650     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15651         blackNPS = second.nps;
15652     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15653
15654     StartClockTimer(intendedTickLength);
15655 }
15656
15657 char *
15658 TimeString(ms)
15659      long ms;
15660 {
15661     long second, minute, hour, day;
15662     char *sign = "";
15663     static char buf[32];
15664
15665     if (ms > 0 && ms <= 9900) {
15666       /* convert milliseconds to tenths, rounding up */
15667       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15668
15669       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15670       return buf;
15671     }
15672
15673     /* convert milliseconds to seconds, rounding up */
15674     /* use floating point to avoid strangeness of integer division
15675        with negative dividends on many machines */
15676     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15677
15678     if (second < 0) {
15679         sign = "-";
15680         second = -second;
15681     }
15682
15683     day = second / (60 * 60 * 24);
15684     second = second % (60 * 60 * 24);
15685     hour = second / (60 * 60);
15686     second = second % (60 * 60);
15687     minute = second / 60;
15688     second = second % 60;
15689
15690     if (day > 0)
15691       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15692               sign, day, hour, minute, second);
15693     else if (hour > 0)
15694       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15695     else
15696       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15697
15698     return buf;
15699 }
15700
15701
15702 /*
15703  * This is necessary because some C libraries aren't ANSI C compliant yet.
15704  */
15705 char *
15706 StrStr(string, match)
15707      char *string, *match;
15708 {
15709     int i, length;
15710
15711     length = strlen(match);
15712
15713     for (i = strlen(string) - length; i >= 0; i--, string++)
15714       if (!strncmp(match, string, length))
15715         return string;
15716
15717     return NULL;
15718 }
15719
15720 char *
15721 StrCaseStr(string, match)
15722      char *string, *match;
15723 {
15724     int i, j, length;
15725
15726     length = strlen(match);
15727
15728     for (i = strlen(string) - length; i >= 0; i--, string++) {
15729         for (j = 0; j < length; j++) {
15730             if (ToLower(match[j]) != ToLower(string[j]))
15731               break;
15732         }
15733         if (j == length) return string;
15734     }
15735
15736     return NULL;
15737 }
15738
15739 #ifndef _amigados
15740 int
15741 StrCaseCmp(s1, s2)
15742      char *s1, *s2;
15743 {
15744     char c1, c2;
15745
15746     for (;;) {
15747         c1 = ToLower(*s1++);
15748         c2 = ToLower(*s2++);
15749         if (c1 > c2) return 1;
15750         if (c1 < c2) return -1;
15751         if (c1 == NULLCHAR) return 0;
15752     }
15753 }
15754
15755
15756 int
15757 ToLower(c)
15758      int c;
15759 {
15760     return isupper(c) ? tolower(c) : c;
15761 }
15762
15763
15764 int
15765 ToUpper(c)
15766      int c;
15767 {
15768     return islower(c) ? toupper(c) : c;
15769 }
15770 #endif /* !_amigados    */
15771
15772 char *
15773 StrSave(s)
15774      char *s;
15775 {
15776   char *ret;
15777
15778   if ((ret = (char *) malloc(strlen(s) + 1)))
15779     {
15780       safeStrCpy(ret, s, strlen(s)+1);
15781     }
15782   return ret;
15783 }
15784
15785 char *
15786 StrSavePtr(s, savePtr)
15787      char *s, **savePtr;
15788 {
15789     if (*savePtr) {
15790         free(*savePtr);
15791     }
15792     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15793       safeStrCpy(*savePtr, s, strlen(s)+1);
15794     }
15795     return(*savePtr);
15796 }
15797
15798 char *
15799 PGNDate()
15800 {
15801     time_t clock;
15802     struct tm *tm;
15803     char buf[MSG_SIZ];
15804
15805     clock = time((time_t *)NULL);
15806     tm = localtime(&clock);
15807     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15808             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15809     return StrSave(buf);
15810 }
15811
15812
15813 char *
15814 PositionToFEN(move, overrideCastling)
15815      int move;
15816      char *overrideCastling;
15817 {
15818     int i, j, fromX, fromY, toX, toY;
15819     int whiteToPlay;
15820     char buf[128];
15821     char *p, *q;
15822     int emptycount;
15823     ChessSquare piece;
15824
15825     whiteToPlay = (gameMode == EditPosition) ?
15826       !blackPlaysFirst : (move % 2 == 0);
15827     p = buf;
15828
15829     /* Piece placement data */
15830     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15831         emptycount = 0;
15832         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15833             if (boards[move][i][j] == EmptySquare) {
15834                 emptycount++;
15835             } else { ChessSquare piece = boards[move][i][j];
15836                 if (emptycount > 0) {
15837                     if(emptycount<10) /* [HGM] can be >= 10 */
15838                         *p++ = '0' + emptycount;
15839                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15840                     emptycount = 0;
15841                 }
15842                 if(PieceToChar(piece) == '+') {
15843                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15844                     *p++ = '+';
15845                     piece = (ChessSquare)(DEMOTED piece);
15846                 }
15847                 *p++ = PieceToChar(piece);
15848                 if(p[-1] == '~') {
15849                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15850                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15851                     *p++ = '~';
15852                 }
15853             }
15854         }
15855         if (emptycount > 0) {
15856             if(emptycount<10) /* [HGM] can be >= 10 */
15857                 *p++ = '0' + emptycount;
15858             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15859             emptycount = 0;
15860         }
15861         *p++ = '/';
15862     }
15863     *(p - 1) = ' ';
15864
15865     /* [HGM] print Crazyhouse or Shogi holdings */
15866     if( gameInfo.holdingsWidth ) {
15867         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15868         q = p;
15869         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15870             piece = boards[move][i][BOARD_WIDTH-1];
15871             if( piece != EmptySquare )
15872               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15873                   *p++ = PieceToChar(piece);
15874         }
15875         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15876             piece = boards[move][BOARD_HEIGHT-i-1][0];
15877             if( piece != EmptySquare )
15878               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15879                   *p++ = PieceToChar(piece);
15880         }
15881
15882         if( q == p ) *p++ = '-';
15883         *p++ = ']';
15884         *p++ = ' ';
15885     }
15886
15887     /* Active color */
15888     *p++ = whiteToPlay ? 'w' : 'b';
15889     *p++ = ' ';
15890
15891   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15892     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15893   } else {
15894   if(nrCastlingRights) {
15895      q = p;
15896      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15897        /* [HGM] write directly from rights */
15898            if(boards[move][CASTLING][2] != NoRights &&
15899               boards[move][CASTLING][0] != NoRights   )
15900                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15901            if(boards[move][CASTLING][2] != NoRights &&
15902               boards[move][CASTLING][1] != NoRights   )
15903                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15904            if(boards[move][CASTLING][5] != NoRights &&
15905               boards[move][CASTLING][3] != NoRights   )
15906                 *p++ = boards[move][CASTLING][3] + AAA;
15907            if(boards[move][CASTLING][5] != NoRights &&
15908               boards[move][CASTLING][4] != NoRights   )
15909                 *p++ = boards[move][CASTLING][4] + AAA;
15910      } else {
15911
15912         /* [HGM] write true castling rights */
15913         if( nrCastlingRights == 6 ) {
15914             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15915                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15916             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15917                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15918             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15919                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15920             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15921                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15922         }
15923      }
15924      if (q == p) *p++ = '-'; /* No castling rights */
15925      *p++ = ' ';
15926   }
15927
15928   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15929      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15930     /* En passant target square */
15931     if (move > backwardMostMove) {
15932         fromX = moveList[move - 1][0] - AAA;
15933         fromY = moveList[move - 1][1] - ONE;
15934         toX = moveList[move - 1][2] - AAA;
15935         toY = moveList[move - 1][3] - ONE;
15936         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15937             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15938             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15939             fromX == toX) {
15940             /* 2-square pawn move just happened */
15941             *p++ = toX + AAA;
15942             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15943         } else {
15944             *p++ = '-';
15945         }
15946     } else if(move == backwardMostMove) {
15947         // [HGM] perhaps we should always do it like this, and forget the above?
15948         if((signed char)boards[move][EP_STATUS] >= 0) {
15949             *p++ = boards[move][EP_STATUS] + AAA;
15950             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15951         } else {
15952             *p++ = '-';
15953         }
15954     } else {
15955         *p++ = '-';
15956     }
15957     *p++ = ' ';
15958   }
15959   }
15960
15961     /* [HGM] find reversible plies */
15962     {   int i = 0, j=move;
15963
15964         if (appData.debugMode) { int k;
15965             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15966             for(k=backwardMostMove; k<=forwardMostMove; k++)
15967                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15968
15969         }
15970
15971         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15972         if( j == backwardMostMove ) i += initialRulePlies;
15973         sprintf(p, "%d ", i);
15974         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15975     }
15976     /* Fullmove number */
15977     sprintf(p, "%d", (move / 2) + 1);
15978
15979     return StrSave(buf);
15980 }
15981
15982 Boolean
15983 ParseFEN(board, blackPlaysFirst, fen)
15984     Board board;
15985      int *blackPlaysFirst;
15986      char *fen;
15987 {
15988     int i, j;
15989     char *p, c;
15990     int emptycount;
15991     ChessSquare piece;
15992
15993     p = fen;
15994
15995     /* [HGM] by default clear Crazyhouse holdings, if present */
15996     if(gameInfo.holdingsWidth) {
15997        for(i=0; i<BOARD_HEIGHT; i++) {
15998            board[i][0]             = EmptySquare; /* black holdings */
15999            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16000            board[i][1]             = (ChessSquare) 0; /* black counts */
16001            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16002        }
16003     }
16004
16005     /* Piece placement data */
16006     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16007         j = 0;
16008         for (;;) {
16009             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16010                 if (*p == '/') p++;
16011                 emptycount = gameInfo.boardWidth - j;
16012                 while (emptycount--)
16013                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16014                 break;
16015 #if(BOARD_FILES >= 10)
16016             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16017                 p++; emptycount=10;
16018                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16019                 while (emptycount--)
16020                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16021 #endif
16022             } else if (isdigit(*p)) {
16023                 emptycount = *p++ - '0';
16024                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16025                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16026                 while (emptycount--)
16027                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16028             } else if (*p == '+' || isalpha(*p)) {
16029                 if (j >= gameInfo.boardWidth) return FALSE;
16030                 if(*p=='+') {
16031                     piece = CharToPiece(*++p);
16032                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16033                     piece = (ChessSquare) (PROMOTED piece ); p++;
16034                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16035                 } else piece = CharToPiece(*p++);
16036
16037                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16038                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16039                     piece = (ChessSquare) (PROMOTED piece);
16040                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16041                     p++;
16042                 }
16043                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16044             } else {
16045                 return FALSE;
16046             }
16047         }
16048     }
16049     while (*p == '/' || *p == ' ') p++;
16050
16051     /* [HGM] look for Crazyhouse holdings here */
16052     while(*p==' ') p++;
16053     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16054         if(*p == '[') p++;
16055         if(*p == '-' ) p++; /* empty holdings */ else {
16056             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16057             /* if we would allow FEN reading to set board size, we would   */
16058             /* have to add holdings and shift the board read so far here   */
16059             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16060                 p++;
16061                 if((int) piece >= (int) BlackPawn ) {
16062                     i = (int)piece - (int)BlackPawn;
16063                     i = PieceToNumber((ChessSquare)i);
16064                     if( i >= gameInfo.holdingsSize ) return FALSE;
16065                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16066                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16067                 } else {
16068                     i = (int)piece - (int)WhitePawn;
16069                     i = PieceToNumber((ChessSquare)i);
16070                     if( i >= gameInfo.holdingsSize ) return FALSE;
16071                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16072                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16073                 }
16074             }
16075         }
16076         if(*p == ']') p++;
16077     }
16078
16079     while(*p == ' ') p++;
16080
16081     /* Active color */
16082     c = *p++;
16083     if(appData.colorNickNames) {
16084       if( c == appData.colorNickNames[0] ) c = 'w'; else
16085       if( c == appData.colorNickNames[1] ) c = 'b';
16086     }
16087     switch (c) {
16088       case 'w':
16089         *blackPlaysFirst = FALSE;
16090         break;
16091       case 'b':
16092         *blackPlaysFirst = TRUE;
16093         break;
16094       default:
16095         return FALSE;
16096     }
16097
16098     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16099     /* return the extra info in global variiables             */
16100
16101     /* set defaults in case FEN is incomplete */
16102     board[EP_STATUS] = EP_UNKNOWN;
16103     for(i=0; i<nrCastlingRights; i++ ) {
16104         board[CASTLING][i] =
16105             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16106     }   /* assume possible unless obviously impossible */
16107     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16108     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16109     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16110                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16111     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16112     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16113     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16114                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16115     FENrulePlies = 0;
16116
16117     while(*p==' ') p++;
16118     if(nrCastlingRights) {
16119       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16120           /* castling indicator present, so default becomes no castlings */
16121           for(i=0; i<nrCastlingRights; i++ ) {
16122                  board[CASTLING][i] = NoRights;
16123           }
16124       }
16125       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16126              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16127              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16128              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16129         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16130
16131         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16132             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16133             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16134         }
16135         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16136             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16137         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16138                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16139         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16140                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16141         switch(c) {
16142           case'K':
16143               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16144               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16145               board[CASTLING][2] = whiteKingFile;
16146               break;
16147           case'Q':
16148               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16149               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16150               board[CASTLING][2] = whiteKingFile;
16151               break;
16152           case'k':
16153               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16154               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16155               board[CASTLING][5] = blackKingFile;
16156               break;
16157           case'q':
16158               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16159               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16160               board[CASTLING][5] = blackKingFile;
16161           case '-':
16162               break;
16163           default: /* FRC castlings */
16164               if(c >= 'a') { /* black rights */
16165                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16166                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16167                   if(i == BOARD_RGHT) break;
16168                   board[CASTLING][5] = i;
16169                   c -= AAA;
16170                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16171                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16172                   if(c > i)
16173                       board[CASTLING][3] = c;
16174                   else
16175                       board[CASTLING][4] = c;
16176               } else { /* white rights */
16177                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16178                     if(board[0][i] == WhiteKing) break;
16179                   if(i == BOARD_RGHT) break;
16180                   board[CASTLING][2] = i;
16181                   c -= AAA - 'a' + 'A';
16182                   if(board[0][c] >= WhiteKing) break;
16183                   if(c > i)
16184                       board[CASTLING][0] = c;
16185                   else
16186                       board[CASTLING][1] = c;
16187               }
16188         }
16189       }
16190       for(i=0; i<nrCastlingRights; i++)
16191         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16192     if (appData.debugMode) {
16193         fprintf(debugFP, "FEN castling rights:");
16194         for(i=0; i<nrCastlingRights; i++)
16195         fprintf(debugFP, " %d", board[CASTLING][i]);
16196         fprintf(debugFP, "\n");
16197     }
16198
16199       while(*p==' ') p++;
16200     }
16201
16202     /* read e.p. field in games that know e.p. capture */
16203     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16204        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16205       if(*p=='-') {
16206         p++; board[EP_STATUS] = EP_NONE;
16207       } else {
16208          char c = *p++ - AAA;
16209
16210          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16211          if(*p >= '0' && *p <='9') p++;
16212          board[EP_STATUS] = c;
16213       }
16214     }
16215
16216
16217     if(sscanf(p, "%d", &i) == 1) {
16218         FENrulePlies = i; /* 50-move ply counter */
16219         /* (The move number is still ignored)    */
16220     }
16221
16222     return TRUE;
16223 }
16224
16225 void
16226 EditPositionPasteFEN(char *fen)
16227 {
16228   if (fen != NULL) {
16229     Board initial_position;
16230
16231     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16232       DisplayError(_("Bad FEN position in clipboard"), 0);
16233       return ;
16234     } else {
16235       int savedBlackPlaysFirst = blackPlaysFirst;
16236       EditPositionEvent();
16237       blackPlaysFirst = savedBlackPlaysFirst;
16238       CopyBoard(boards[0], initial_position);
16239       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16240       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16241       DisplayBothClocks();
16242       DrawPosition(FALSE, boards[currentMove]);
16243     }
16244   }
16245 }
16246
16247 static char cseq[12] = "\\   ";
16248
16249 Boolean set_cont_sequence(char *new_seq)
16250 {
16251     int len;
16252     Boolean ret;
16253
16254     // handle bad attempts to set the sequence
16255         if (!new_seq)
16256                 return 0; // acceptable error - no debug
16257
16258     len = strlen(new_seq);
16259     ret = (len > 0) && (len < sizeof(cseq));
16260     if (ret)
16261       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16262     else if (appData.debugMode)
16263       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16264     return ret;
16265 }
16266
16267 /*
16268     reformat a source message so words don't cross the width boundary.  internal
16269     newlines are not removed.  returns the wrapped size (no null character unless
16270     included in source message).  If dest is NULL, only calculate the size required
16271     for the dest buffer.  lp argument indicats line position upon entry, and it's
16272     passed back upon exit.
16273 */
16274 int wrap(char *dest, char *src, int count, int width, int *lp)
16275 {
16276     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16277
16278     cseq_len = strlen(cseq);
16279     old_line = line = *lp;
16280     ansi = len = clen = 0;
16281
16282     for (i=0; i < count; i++)
16283     {
16284         if (src[i] == '\033')
16285             ansi = 1;
16286
16287         // if we hit the width, back up
16288         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16289         {
16290             // store i & len in case the word is too long
16291             old_i = i, old_len = len;
16292
16293             // find the end of the last word
16294             while (i && src[i] != ' ' && src[i] != '\n')
16295             {
16296                 i--;
16297                 len--;
16298             }
16299
16300             // word too long?  restore i & len before splitting it
16301             if ((old_i-i+clen) >= width)
16302             {
16303                 i = old_i;
16304                 len = old_len;
16305             }
16306
16307             // extra space?
16308             if (i && src[i-1] == ' ')
16309                 len--;
16310
16311             if (src[i] != ' ' && src[i] != '\n')
16312             {
16313                 i--;
16314                 if (len)
16315                     len--;
16316             }
16317
16318             // now append the newline and continuation sequence
16319             if (dest)
16320                 dest[len] = '\n';
16321             len++;
16322             if (dest)
16323                 strncpy(dest+len, cseq, cseq_len);
16324             len += cseq_len;
16325             line = cseq_len;
16326             clen = cseq_len;
16327             continue;
16328         }
16329
16330         if (dest)
16331             dest[len] = src[i];
16332         len++;
16333         if (!ansi)
16334             line++;
16335         if (src[i] == '\n')
16336             line = 0;
16337         if (src[i] == 'm')
16338             ansi = 0;
16339     }
16340     if (dest && appData.debugMode)
16341     {
16342         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16343             count, width, line, len, *lp);
16344         show_bytes(debugFP, src, count);
16345         fprintf(debugFP, "\ndest: ");
16346         show_bytes(debugFP, dest, len);
16347         fprintf(debugFP, "\n");
16348     }
16349     *lp = dest ? line : old_line;
16350
16351     return len;
16352 }
16353
16354 // [HGM] vari: routines for shelving variations
16355
16356 void
16357 PushInner(int firstMove, int lastMove)
16358 {
16359         int i, j, nrMoves = lastMove - firstMove;
16360
16361         // push current tail of game on stack
16362         savedResult[storedGames] = gameInfo.result;
16363         savedDetails[storedGames] = gameInfo.resultDetails;
16364         gameInfo.resultDetails = NULL;
16365         savedFirst[storedGames] = firstMove;
16366         savedLast [storedGames] = lastMove;
16367         savedFramePtr[storedGames] = framePtr;
16368         framePtr -= nrMoves; // reserve space for the boards
16369         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16370             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16371             for(j=0; j<MOVE_LEN; j++)
16372                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16373             for(j=0; j<2*MOVE_LEN; j++)
16374                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16375             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16376             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16377             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16378             pvInfoList[firstMove+i-1].depth = 0;
16379             commentList[framePtr+i] = commentList[firstMove+i];
16380             commentList[firstMove+i] = NULL;
16381         }
16382
16383         storedGames++;
16384         forwardMostMove = firstMove; // truncate game so we can start variation
16385 }
16386
16387 void
16388 PushTail(int firstMove, int lastMove)
16389 {
16390         if(appData.icsActive) { // only in local mode
16391                 forwardMostMove = currentMove; // mimic old ICS behavior
16392                 return;
16393         }
16394         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16395
16396         PushInner(firstMove, lastMove);
16397         if(storedGames == 1) GreyRevert(FALSE);
16398 }
16399
16400 void
16401 PopInner(Boolean annotate)
16402 {
16403         int i, j, nrMoves;
16404         char buf[8000], moveBuf[20];
16405
16406         storedGames--;
16407         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16408         nrMoves = savedLast[storedGames] - currentMove;
16409         if(annotate) {
16410                 int cnt = 10;
16411                 if(!WhiteOnMove(currentMove))
16412                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16413                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16414                 for(i=currentMove; i<forwardMostMove; i++) {
16415                         if(WhiteOnMove(i))
16416                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16417                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16418                         strcat(buf, moveBuf);
16419                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16420                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16421                 }
16422                 strcat(buf, ")");
16423         }
16424         for(i=1; i<=nrMoves; i++) { // copy last variation back
16425             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16426             for(j=0; j<MOVE_LEN; j++)
16427                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16428             for(j=0; j<2*MOVE_LEN; j++)
16429                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16430             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16431             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16432             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16433             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16434             commentList[currentMove+i] = commentList[framePtr+i];
16435             commentList[framePtr+i] = NULL;
16436         }
16437         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16438         framePtr = savedFramePtr[storedGames];
16439         gameInfo.result = savedResult[storedGames];
16440         if(gameInfo.resultDetails != NULL) {
16441             free(gameInfo.resultDetails);
16442       }
16443         gameInfo.resultDetails = savedDetails[storedGames];
16444         forwardMostMove = currentMove + nrMoves;
16445 }
16446
16447 Boolean
16448 PopTail(Boolean annotate)
16449 {
16450         if(appData.icsActive) return FALSE; // only in local mode
16451         if(!storedGames) return FALSE; // sanity
16452         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16453
16454         PopInner(annotate);
16455
16456         if(storedGames == 0) GreyRevert(TRUE);
16457         return TRUE;
16458 }
16459
16460 void
16461 CleanupTail()
16462 {       // remove all shelved variations
16463         int i;
16464         for(i=0; i<storedGames; i++) {
16465             if(savedDetails[i])
16466                 free(savedDetails[i]);
16467             savedDetails[i] = NULL;
16468         }
16469         for(i=framePtr; i<MAX_MOVES; i++) {
16470                 if(commentList[i]) free(commentList[i]);
16471                 commentList[i] = NULL;
16472         }
16473         framePtr = MAX_MOVES-1;
16474         storedGames = 0;
16475 }
16476
16477 void
16478 LoadVariation(int index, char *text)
16479 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16480         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16481         int level = 0, move;
16482
16483         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16484         // first find outermost bracketing variation
16485         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16486             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16487                 if(*p == '{') wait = '}'; else
16488                 if(*p == '[') wait = ']'; else
16489                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16490                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16491             }
16492             if(*p == wait) wait = NULLCHAR; // closing ]} found
16493             p++;
16494         }
16495         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16496         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16497         end[1] = NULLCHAR; // clip off comment beyond variation
16498         ToNrEvent(currentMove-1);
16499         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16500         // kludge: use ParsePV() to append variation to game
16501         move = currentMove;
16502         ParsePV(start, TRUE, TRUE);
16503         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16504         ClearPremoveHighlights();
16505         CommentPopDown();
16506         ToNrEvent(currentMove+1);
16507 }
16508