e08e829e2eb7dd5670f0c59bb1d886a27e60071e
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 int flock(int f, int code);
61 #define LOCK_EX 2
62 #define SLASH '\\'
63
64 #else
65
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
67 #define SLASH '/'
68
69 #endif
70
71 #include "config.h"
72
73 #include <assert.h>
74 #include <stdio.h>
75 #include <ctype.h>
76 #include <errno.h>
77 #include <sys/types.h>
78 #include <sys/stat.h>
79 #include <math.h>
80 #include <ctype.h>
81
82 #if STDC_HEADERS
83 # include <stdlib.h>
84 # include <string.h>
85 # include <stdarg.h>
86 #else /* not STDC_HEADERS */
87 # if HAVE_STRING_H
88 #  include <string.h>
89 # else /* not HAVE_STRING_H */
90 #  include <strings.h>
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
93
94 #if HAVE_SYS_FCNTL_H
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
97 # if HAVE_FCNTL_H
98 #  include <fcntl.h>
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
101
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
104 # include <time.h>
105 #else
106 # if HAVE_SYS_TIME_H
107 #  include <sys/time.h>
108 # else
109 #  include <time.h>
110 # endif
111 #endif
112
113 #if defined(_amigados) && !defined(__GNUC__)
114 struct timezone {
115     int tz_minuteswest;
116     int tz_dsttime;
117 };
118 extern int gettimeofday(struct timeval *, struct timezone *);
119 #endif
120
121 #if HAVE_UNISTD_H
122 # include <unistd.h>
123 #endif
124
125 #include "common.h"
126 #include "frontend.h"
127 #include "backend.h"
128 #include "parser.h"
129 #include "moves.h"
130 #if ZIPPY
131 # include "zippy.h"
132 #endif
133 #include "backendz.h"
134 #include "gettext.h"
135
136 #ifdef ENABLE_NLS
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
140 #else
141 # ifdef WIN32
142 #   define _(s) T_(s)
143 #   define N_(s) s
144 # else
145 #   define _(s) (s)
146 #   define N_(s) s
147 #   define T_(s) s
148 # endif
149 #endif
150
151
152 /* A point in time */
153 typedef struct {
154     long sec;  /* Assuming this is >= 32 bits */
155     int ms;    /* Assuming this is >= 16 bits */
156 } TimeMark;
157
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160                          char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162                       char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
174                                                                                 Board board));
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178                    /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190                            char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192                         int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
199
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237 void DisplayTwoMachinesTitle P(());
238
239 #ifdef WIN32
240        extern void ConsoleCreate();
241 #endif
242
243 ChessProgramState *WhitePlayer();
244 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
245 int VerifyDisplayMode P(());
246
247 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
248 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
249 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
250 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
251 void ics_update_width P((int new_width));
252 extern char installDir[MSG_SIZ];
253 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 Boolean abortMatch;
255
256 extern int tinyLayout, smallLayout;
257 ChessProgramStats programStats;
258 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 int endPV = -1;
260 static int exiting = 0; /* [HGM] moved to top */
261 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
262 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
263 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
264 int partnerHighlight[2];
265 Boolean partnerBoardValid = 0;
266 char partnerStatus[MSG_SIZ];
267 Boolean partnerUp;
268 Boolean originalFlip;
269 Boolean twoBoards = 0;
270 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
271 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
272 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
273 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
274 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
275 int opponentKibitzes;
276 int lastSavedGame; /* [HGM] save: ID of game */
277 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
278 extern int chatCount;
279 int chattingPartner;
280 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
281 char lastMsg[MSG_SIZ];
282 ChessSquare pieceSweep = EmptySquare;
283 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
284 int promoDefaultAltered;
285
286 /* States for ics_getting_history */
287 #define H_FALSE 0
288 #define H_REQUESTED 1
289 #define H_GOT_REQ_HEADER 2
290 #define H_GOT_UNREQ_HEADER 3
291 #define H_GETTING_MOVES 4
292 #define H_GOT_UNWANTED_HEADER 5
293
294 /* whosays values for GameEnds */
295 #define GE_ICS 0
296 #define GE_ENGINE 1
297 #define GE_PLAYER 2
298 #define GE_FILE 3
299 #define GE_XBOARD 4
300 #define GE_ENGINE1 5
301 #define GE_ENGINE2 6
302
303 /* Maximum number of games in a cmail message */
304 #define CMAIL_MAX_GAMES 20
305
306 /* Different types of move when calling RegisterMove */
307 #define CMAIL_MOVE   0
308 #define CMAIL_RESIGN 1
309 #define CMAIL_DRAW   2
310 #define CMAIL_ACCEPT 3
311
312 /* Different types of result to remember for each game */
313 #define CMAIL_NOT_RESULT 0
314 #define CMAIL_OLD_RESULT 1
315 #define CMAIL_NEW_RESULT 2
316
317 /* Telnet protocol constants */
318 #define TN_WILL 0373
319 #define TN_WONT 0374
320 #define TN_DO   0375
321 #define TN_DONT 0376
322 #define TN_IAC  0377
323 #define TN_ECHO 0001
324 #define TN_SGA  0003
325 #define TN_PORT 23
326
327 char*
328 safeStrCpy( char *dst, const char *src, size_t count )
329 { // [HGM] made safe
330   int i;
331   assert( dst != NULL );
332   assert( src != NULL );
333   assert( count > 0 );
334
335   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
336   if(  i == count && dst[count-1] != NULLCHAR)
337     {
338       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
339       if(appData.debugMode)
340       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
341     }
342
343   return dst;
344 }
345
346 /* Some compiler can't cast u64 to double
347  * This function do the job for us:
348
349  * We use the highest bit for cast, this only
350  * works if the highest bit is not
351  * in use (This should not happen)
352  *
353  * We used this for all compiler
354  */
355 double
356 u64ToDouble(u64 value)
357 {
358   double r;
359   u64 tmp = value & u64Const(0x7fffffffffffffff);
360   r = (double)(s64)tmp;
361   if (value & u64Const(0x8000000000000000))
362        r +=  9.2233720368547758080e18; /* 2^63 */
363  return r;
364 }
365
366 /* Fake up flags for now, as we aren't keeping track of castling
367    availability yet. [HGM] Change of logic: the flag now only
368    indicates the type of castlings allowed by the rule of the game.
369    The actual rights themselves are maintained in the array
370    castlingRights, as part of the game history, and are not probed
371    by this function.
372  */
373 int
374 PosFlags(index)
375 {
376   int flags = F_ALL_CASTLE_OK;
377   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
378   switch (gameInfo.variant) {
379   case VariantSuicide:
380     flags &= ~F_ALL_CASTLE_OK;
381   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
382     flags |= F_IGNORE_CHECK;
383   case VariantLosers:
384     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385     break;
386   case VariantAtomic:
387     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388     break;
389   case VariantKriegspiel:
390     flags |= F_KRIEGSPIEL_CAPTURE;
391     break;
392   case VariantCapaRandom:
393   case VariantFischeRandom:
394     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
395   case VariantNoCastle:
396   case VariantShatranj:
397   case VariantCourier:
398   case VariantMakruk:
399   case VariantGrand:
400     flags &= ~F_ALL_CASTLE_OK;
401     break;
402   default:
403     break;
404   }
405   return flags;
406 }
407
408 FILE *gameFileFP, *debugFP;
409
410 /*
411     [AS] Note: sometimes, the sscanf() function is used to parse the input
412     into a fixed-size buffer. Because of this, we must be prepared to
413     receive strings as long as the size of the input buffer, which is currently
414     set to 4K for Windows and 8K for the rest.
415     So, we must either allocate sufficiently large buffers here, or
416     reduce the size of the input buffer in the input reading part.
417 */
418
419 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
420 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
421 char thinkOutput1[MSG_SIZ*10];
422
423 ChessProgramState first, second, pairing;
424
425 /* premove variables */
426 int premoveToX = 0;
427 int premoveToY = 0;
428 int premoveFromX = 0;
429 int premoveFromY = 0;
430 int premovePromoChar = 0;
431 int gotPremove = 0;
432 Boolean alarmSounded;
433 /* end premove variables */
434
435 char *ics_prefix = "$";
436 int ics_type = ICS_GENERIC;
437
438 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
439 int pauseExamForwardMostMove = 0;
440 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
441 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
442 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
443 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
444 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
445 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
446 int whiteFlag = FALSE, blackFlag = FALSE;
447 int userOfferedDraw = FALSE;
448 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
449 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
450 int cmailMoveType[CMAIL_MAX_GAMES];
451 long ics_clock_paused = 0;
452 ProcRef icsPR = NoProc, cmailPR = NoProc;
453 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
454 GameMode gameMode = BeginningOfGame;
455 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
456 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
457 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
458 int hiddenThinkOutputState = 0; /* [AS] */
459 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
460 int adjudicateLossPlies = 6;
461 char white_holding[64], black_holding[64];
462 TimeMark lastNodeCountTime;
463 long lastNodeCount=0;
464 int shiftKey; // [HGM] set by mouse handler
465
466 int have_sent_ICS_logon = 0;
467 int movesPerSession;
468 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
469 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
470 long timeControl_2; /* [AS] Allow separate time controls */
471 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
472 long timeRemaining[2][MAX_MOVES];
473 int matchGame = 0, nextGame = 0, roundNr = 0;
474 Boolean waitingForGame = FALSE;
475 TimeMark programStartTime, pauseStart;
476 char ics_handle[MSG_SIZ];
477 int have_set_title = 0;
478
479 /* animateTraining preserves the state of appData.animate
480  * when Training mode is activated. This allows the
481  * response to be animated when appData.animate == TRUE and
482  * appData.animateDragging == TRUE.
483  */
484 Boolean animateTraining;
485
486 GameInfo gameInfo;
487
488 AppData appData;
489
490 Board boards[MAX_MOVES];
491 /* [HGM] Following 7 needed for accurate legality tests: */
492 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
493 signed char  initialRights[BOARD_FILES];
494 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
495 int   initialRulePlies, FENrulePlies;
496 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 int loadFlag = 0;
498 Boolean shuffleOpenings;
499 int mute; // mute all sounds
500
501 // [HGM] vari: next 12 to save and restore variations
502 #define MAX_VARIATIONS 10
503 int framePtr = MAX_MOVES-1; // points to free stack entry
504 int storedGames = 0;
505 int savedFirst[MAX_VARIATIONS];
506 int savedLast[MAX_VARIATIONS];
507 int savedFramePtr[MAX_VARIATIONS];
508 char *savedDetails[MAX_VARIATIONS];
509 ChessMove savedResult[MAX_VARIATIONS];
510
511 void PushTail P((int firstMove, int lastMove));
512 Boolean PopTail P((Boolean annotate));
513 void PushInner P((int firstMove, int lastMove));
514 void PopInner P((Boolean annotate));
515 void CleanupTail P((void));
516
517 ChessSquare  FIDEArray[2][BOARD_FILES] = {
518     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
520     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521         BlackKing, BlackBishop, BlackKnight, BlackRook }
522 };
523
524 ChessSquare twoKingsArray[2][BOARD_FILES] = {
525     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
526         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
527     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
528         BlackKing, BlackKing, BlackKnight, BlackRook }
529 };
530
531 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
532     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
533         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
534     { BlackRook, BlackMan, BlackBishop, BlackQueen,
535         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
536 };
537
538 ChessSquare SpartanArray[2][BOARD_FILES] = {
539     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
542         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
543 };
544
545 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
546     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
547         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
548     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
549         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
550 };
551
552 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
554         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
556         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 };
558
559 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
560     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
561         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackMan, BlackFerz,
563         BlackKing, BlackMan, BlackKnight, BlackRook }
564 };
565
566
567 #if (BOARD_FILES>=10)
568 ChessSquare ShogiArray[2][BOARD_FILES] = {
569     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
570         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
571     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
572         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
573 };
574
575 ChessSquare XiangqiArray[2][BOARD_FILES] = {
576     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
577         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
579         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 };
581
582 ChessSquare CapablancaArray[2][BOARD_FILES] = {
583     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
584         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
585     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
586         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
587 };
588
589 ChessSquare GreatArray[2][BOARD_FILES] = {
590     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
591         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
592     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
593         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
594 };
595
596 ChessSquare JanusArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
598         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
599     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
600         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
601 };
602
603 ChessSquare GrandArray[2][BOARD_FILES] = {
604     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
605         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
606     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
607         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
608 };
609
610 #ifdef GOTHIC
611 ChessSquare GothicArray[2][BOARD_FILES] = {
612     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
613         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
614     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
615         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
616 };
617 #else // !GOTHIC
618 #define GothicArray CapablancaArray
619 #endif // !GOTHIC
620
621 #ifdef FALCON
622 ChessSquare FalconArray[2][BOARD_FILES] = {
623     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
624         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
625     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
626         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
627 };
628 #else // !FALCON
629 #define FalconArray CapablancaArray
630 #endif // !FALCON
631
632 #else // !(BOARD_FILES>=10)
633 #define XiangqiPosition FIDEArray
634 #define CapablancaArray FIDEArray
635 #define GothicArray FIDEArray
636 #define GreatArray FIDEArray
637 #endif // !(BOARD_FILES>=10)
638
639 #if (BOARD_FILES>=12)
640 ChessSquare CourierArray[2][BOARD_FILES] = {
641     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
642         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
643     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
644         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
645 };
646 #else // !(BOARD_FILES>=12)
647 #define CourierArray CapablancaArray
648 #endif // !(BOARD_FILES>=12)
649
650
651 Board initialPosition;
652
653
654 /* Convert str to a rating. Checks for special cases of "----",
655
656    "++++", etc. Also strips ()'s */
657 int
658 string_to_rating(str)
659   char *str;
660 {
661   while(*str && !isdigit(*str)) ++str;
662   if (!*str)
663     return 0;   /* One of the special "no rating" cases */
664   else
665     return atoi(str);
666 }
667
668 void
669 ClearProgramStats()
670 {
671     /* Init programStats */
672     programStats.movelist[0] = 0;
673     programStats.depth = 0;
674     programStats.nr_moves = 0;
675     programStats.moves_left = 0;
676     programStats.nodes = 0;
677     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
678     programStats.score = 0;
679     programStats.got_only_move = 0;
680     programStats.got_fail = 0;
681     programStats.line_is_book = 0;
682 }
683
684 void
685 CommonEngineInit()
686 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
687     if (appData.firstPlaysBlack) {
688         first.twoMachinesColor = "black\n";
689         second.twoMachinesColor = "white\n";
690     } else {
691         first.twoMachinesColor = "white\n";
692         second.twoMachinesColor = "black\n";
693     }
694
695     first.other = &second;
696     second.other = &first;
697
698     { float norm = 1;
699         if(appData.timeOddsMode) {
700             norm = appData.timeOdds[0];
701             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
702         }
703         first.timeOdds  = appData.timeOdds[0]/norm;
704         second.timeOdds = appData.timeOdds[1]/norm;
705     }
706
707     if(programVersion) free(programVersion);
708     if (appData.noChessProgram) {
709         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
710         sprintf(programVersion, "%s", PACKAGE_STRING);
711     } else {
712       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
713       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
714       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
715     }
716 }
717
718 void
719 UnloadEngine(ChessProgramState *cps)
720 {
721         /* Kill off first chess program */
722         if (cps->isr != NULL)
723           RemoveInputSource(cps->isr);
724         cps->isr = NULL;
725
726         if (cps->pr != NoProc) {
727             ExitAnalyzeMode();
728             DoSleep( appData.delayBeforeQuit );
729             SendToProgram("quit\n", cps);
730             DoSleep( appData.delayAfterQuit );
731             DestroyChildProcess(cps->pr, cps->useSigterm);
732         }
733         cps->pr = NoProc;
734         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
735 }
736
737 void
738 ClearOptions(ChessProgramState *cps)
739 {
740     int i;
741     cps->nrOptions = cps->comboCnt = 0;
742     for(i=0; i<MAX_OPTIONS; i++) {
743         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
744         cps->option[i].textValue = 0;
745     }
746 }
747
748 char *engineNames[] = {
749 "first",
750 "second"
751 };
752
753 void
754 InitEngine(ChessProgramState *cps, int n)
755 {   // [HGM] all engine initialiation put in a function that does one engine
756
757     ClearOptions(cps);
758
759     cps->which = engineNames[n];
760     cps->maybeThinking = FALSE;
761     cps->pr = NoProc;
762     cps->isr = NULL;
763     cps->sendTime = 2;
764     cps->sendDrawOffers = 1;
765
766     cps->program = appData.chessProgram[n];
767     cps->host = appData.host[n];
768     cps->dir = appData.directory[n];
769     cps->initString = appData.engInitString[n];
770     cps->computerString = appData.computerString[n];
771     cps->useSigint  = TRUE;
772     cps->useSigterm = TRUE;
773     cps->reuse = appData.reuse[n];
774     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
775     cps->useSetboard = FALSE;
776     cps->useSAN = FALSE;
777     cps->usePing = FALSE;
778     cps->lastPing = 0;
779     cps->lastPong = 0;
780     cps->usePlayother = FALSE;
781     cps->useColors = TRUE;
782     cps->useUsermove = FALSE;
783     cps->sendICS = FALSE;
784     cps->sendName = appData.icsActive;
785     cps->sdKludge = FALSE;
786     cps->stKludge = FALSE;
787     TidyProgramName(cps->program, cps->host, cps->tidy);
788     cps->matchWins = 0;
789     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
790     cps->analysisSupport = 2; /* detect */
791     cps->analyzing = FALSE;
792     cps->initDone = FALSE;
793
794     /* New features added by Tord: */
795     cps->useFEN960 = FALSE;
796     cps->useOOCastle = TRUE;
797     /* End of new features added by Tord. */
798     cps->fenOverride  = appData.fenOverride[n];
799
800     /* [HGM] time odds: set factor for each machine */
801     cps->timeOdds  = appData.timeOdds[n];
802
803     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
804     cps->accumulateTC = appData.accumulateTC[n];
805     cps->maxNrOfSessions = 1;
806
807     /* [HGM] debug */
808     cps->debug = FALSE;
809
810     cps->supportsNPS = UNKNOWN;
811     cps->memSize = FALSE;
812     cps->maxCores = FALSE;
813     cps->egtFormats[0] = NULLCHAR;
814
815     /* [HGM] options */
816     cps->optionSettings  = appData.engOptions[n];
817
818     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
819     cps->isUCI = appData.isUCI[n]; /* [AS] */
820     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
821
822     if (appData.protocolVersion[n] > PROTOVER
823         || appData.protocolVersion[n] < 1)
824       {
825         char buf[MSG_SIZ];
826         int len;
827
828         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
829                        appData.protocolVersion[n]);
830         if( (len > MSG_SIZ) && appData.debugMode )
831           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
832
833         DisplayFatalError(buf, 0, 2);
834       }
835     else
836       {
837         cps->protocolVersion = appData.protocolVersion[n];
838       }
839
840     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
841 }
842
843 ChessProgramState *savCps;
844
845 void
846 LoadEngine()
847 {
848     int i;
849     if(WaitForEngine(savCps, LoadEngine)) return;
850     CommonEngineInit(); // recalculate time odds
851     if(gameInfo.variant != StringToVariant(appData.variant)) {
852         // we changed variant when loading the engine; this forces us to reset
853         Reset(TRUE, savCps != &first);
854         EditGameEvent(); // for consistency with other path, as Reset changes mode
855     }
856     InitChessProgram(savCps, FALSE);
857     SendToProgram("force\n", savCps);
858     DisplayMessage("", "");
859     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
861     ThawUI();
862     SetGNUMode();
863 }
864
865 void
866 ReplaceEngine(ChessProgramState *cps, int n)
867 {
868     EditGameEvent();
869     UnloadEngine(cps);
870     appData.noChessProgram = FALSE;
871     appData.clockMode = TRUE;
872     InitEngine(cps, n);
873     UpdateLogos(TRUE);
874     if(n) return; // only startup first engine immediately; second can wait
875     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
876     LoadEngine();
877 }
878
879 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
880 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
881
882 static char resetOptions[] = 
883         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
884         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
885
886 void
887 Load(ChessProgramState *cps, int i)
888 {
889     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
890     if(engineLine[0]) { // an engine was selected from the combo box
891         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
892         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
893         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
894         ParseArgsFromString(buf);
895         SwapEngines(i);
896         ReplaceEngine(cps, i);
897         return;
898     }
899     p = engineName;
900     while(q = strchr(p, SLASH)) p = q+1;
901     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
902     if(engineDir[0] != NULLCHAR)
903         appData.directory[i] = engineDir;
904     else if(p != engineName) { // derive directory from engine path, when not given
905         p[-1] = 0;
906         appData.directory[i] = strdup(engineName);
907         p[-1] = SLASH;
908     } else appData.directory[i] = ".";
909     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
910     if(params[0]) {
911         snprintf(command, MSG_SIZ, "%s %s", p, params);
912         p = command;
913     }
914     appData.chessProgram[i] = strdup(p);
915     appData.isUCI[i] = isUCI;
916     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
917     appData.hasOwnBookUCI[i] = hasBook;
918     if(!nickName[0]) useNick = FALSE;
919     if(useNick) ASSIGN(appData.pgnName[i], nickName);
920     if(addToList) {
921         int len;
922         char quote;
923         q = firstChessProgramNames;
924         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
925         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
926         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
927                         quote, p, quote, appData.directory[i], 
928                         useNick ? " -fn \"" : "",
929                         useNick ? nickName : "",
930                         useNick ? "\"" : "",
931                         v1 ? " -firstProtocolVersion 1" : "",
932                         hasBook ? "" : " -fNoOwnBookUCI",
933                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
934                         storeVariant ? " -variant " : "",
935                         storeVariant ? VariantName(gameInfo.variant) : "");
936         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
937         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
938         if(q)   free(q);
939     }
940     ReplaceEngine(cps, i);
941 }
942
943 void
944 InitTimeControls()
945 {
946     int matched, min, sec;
947     /*
948      * Parse timeControl resource
949      */
950     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
951                           appData.movesPerSession)) {
952         char buf[MSG_SIZ];
953         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
954         DisplayFatalError(buf, 0, 2);
955     }
956
957     /*
958      * Parse searchTime resource
959      */
960     if (*appData.searchTime != NULLCHAR) {
961         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
962         if (matched == 1) {
963             searchTime = min * 60;
964         } else if (matched == 2) {
965             searchTime = min * 60 + sec;
966         } else {
967             char buf[MSG_SIZ];
968             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
969             DisplayFatalError(buf, 0, 2);
970         }
971     }
972 }
973
974 void
975 InitBackEnd1()
976 {
977
978     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
979     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
980
981     GetTimeMark(&programStartTime);
982     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
983     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
984
985     ClearProgramStats();
986     programStats.ok_to_send = 1;
987     programStats.seen_stat = 0;
988
989     /*
990      * Initialize game list
991      */
992     ListNew(&gameList);
993
994
995     /*
996      * Internet chess server status
997      */
998     if (appData.icsActive) {
999         appData.matchMode = FALSE;
1000         appData.matchGames = 0;
1001 #if ZIPPY
1002         appData.noChessProgram = !appData.zippyPlay;
1003 #else
1004         appData.zippyPlay = FALSE;
1005         appData.zippyTalk = FALSE;
1006         appData.noChessProgram = TRUE;
1007 #endif
1008         if (*appData.icsHelper != NULLCHAR) {
1009             appData.useTelnet = TRUE;
1010             appData.telnetProgram = appData.icsHelper;
1011         }
1012     } else {
1013         appData.zippyTalk = appData.zippyPlay = FALSE;
1014     }
1015
1016     /* [AS] Initialize pv info list [HGM] and game state */
1017     {
1018         int i, j;
1019
1020         for( i=0; i<=framePtr; i++ ) {
1021             pvInfoList[i].depth = -1;
1022             boards[i][EP_STATUS] = EP_NONE;
1023             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1024         }
1025     }
1026
1027     InitTimeControls();
1028
1029     /* [AS] Adjudication threshold */
1030     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1031
1032     InitEngine(&first, 0);
1033     InitEngine(&second, 1);
1034     CommonEngineInit();
1035
1036     pairing.which = "pairing"; // pairing engine
1037     pairing.pr = NoProc;
1038     pairing.isr = NULL;
1039     pairing.program = appData.pairingEngine;
1040     pairing.host = "localhost";
1041     pairing.dir = ".";
1042
1043     if (appData.icsActive) {
1044         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1045     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1046         appData.clockMode = FALSE;
1047         first.sendTime = second.sendTime = 0;
1048     }
1049
1050 #if ZIPPY
1051     /* Override some settings from environment variables, for backward
1052        compatibility.  Unfortunately it's not feasible to have the env
1053        vars just set defaults, at least in xboard.  Ugh.
1054     */
1055     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1056       ZippyInit();
1057     }
1058 #endif
1059
1060     if (!appData.icsActive) {
1061       char buf[MSG_SIZ];
1062       int len;
1063
1064       /* Check for variants that are supported only in ICS mode,
1065          or not at all.  Some that are accepted here nevertheless
1066          have bugs; see comments below.
1067       */
1068       VariantClass variant = StringToVariant(appData.variant);
1069       switch (variant) {
1070       case VariantBughouse:     /* need four players and two boards */
1071       case VariantKriegspiel:   /* need to hide pieces and move details */
1072         /* case VariantFischeRandom: (Fabien: moved below) */
1073         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1074         if( (len > MSG_SIZ) && appData.debugMode )
1075           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1076
1077         DisplayFatalError(buf, 0, 2);
1078         return;
1079
1080       case VariantUnknown:
1081       case VariantLoadable:
1082       case Variant29:
1083       case Variant30:
1084       case Variant31:
1085       case Variant32:
1086       case Variant33:
1087       case Variant34:
1088       case Variant35:
1089       case Variant36:
1090       default:
1091         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1092         if( (len > MSG_SIZ) && appData.debugMode )
1093           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094
1095         DisplayFatalError(buf, 0, 2);
1096         return;
1097
1098       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1099       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1100       case VariantGothic:     /* [HGM] should work */
1101       case VariantCapablanca: /* [HGM] should work */
1102       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1103       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1104       case VariantKnightmate: /* [HGM] should work */
1105       case VariantCylinder:   /* [HGM] untested */
1106       case VariantFalcon:     /* [HGM] untested */
1107       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1108                                  offboard interposition not understood */
1109       case VariantNormal:     /* definitely works! */
1110       case VariantWildCastle: /* pieces not automatically shuffled */
1111       case VariantNoCastle:   /* pieces not automatically shuffled */
1112       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1113       case VariantLosers:     /* should work except for win condition,
1114                                  and doesn't know captures are mandatory */
1115       case VariantSuicide:    /* should work except for win condition,
1116                                  and doesn't know captures are mandatory */
1117       case VariantGiveaway:   /* should work except for win condition,
1118                                  and doesn't know captures are mandatory */
1119       case VariantTwoKings:   /* should work */
1120       case VariantAtomic:     /* should work except for win condition */
1121       case Variant3Check:     /* should work except for win condition */
1122       case VariantShatranj:   /* should work except for all win conditions */
1123       case VariantMakruk:     /* should work except for draw countdown */
1124       case VariantBerolina:   /* might work if TestLegality is off */
1125       case VariantCapaRandom: /* should work */
1126       case VariantJanus:      /* should work */
1127       case VariantSuper:      /* experimental */
1128       case VariantGreat:      /* experimental, requires legality testing to be off */
1129       case VariantSChess:     /* S-Chess, should work */
1130       case VariantGrand:      /* should work */
1131       case VariantSpartan:    /* should work */
1132         break;
1133       }
1134     }
1135
1136 }
1137
1138 int NextIntegerFromString( char ** str, long * value )
1139 {
1140     int result = -1;
1141     char * s = *str;
1142
1143     while( *s == ' ' || *s == '\t' ) {
1144         s++;
1145     }
1146
1147     *value = 0;
1148
1149     if( *s >= '0' && *s <= '9' ) {
1150         while( *s >= '0' && *s <= '9' ) {
1151             *value = *value * 10 + (*s - '0');
1152             s++;
1153         }
1154
1155         result = 0;
1156     }
1157
1158     *str = s;
1159
1160     return result;
1161 }
1162
1163 int NextTimeControlFromString( char ** str, long * value )
1164 {
1165     long temp;
1166     int result = NextIntegerFromString( str, &temp );
1167
1168     if( result == 0 ) {
1169         *value = temp * 60; /* Minutes */
1170         if( **str == ':' ) {
1171             (*str)++;
1172             result = NextIntegerFromString( str, &temp );
1173             *value += temp; /* Seconds */
1174         }
1175     }
1176
1177     return result;
1178 }
1179
1180 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1181 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1182     int result = -1, type = 0; long temp, temp2;
1183
1184     if(**str != ':') return -1; // old params remain in force!
1185     (*str)++;
1186     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1187     if( NextIntegerFromString( str, &temp ) ) return -1;
1188     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1189
1190     if(**str != '/') {
1191         /* time only: incremental or sudden-death time control */
1192         if(**str == '+') { /* increment follows; read it */
1193             (*str)++;
1194             if(**str == '!') type = *(*str)++; // Bronstein TC
1195             if(result = NextIntegerFromString( str, &temp2)) return -1;
1196             *inc = temp2 * 1000;
1197             if(**str == '.') { // read fraction of increment
1198                 char *start = ++(*str);
1199                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1200                 temp2 *= 1000;
1201                 while(start++ < *str) temp2 /= 10;
1202                 *inc += temp2;
1203             }
1204         } else *inc = 0;
1205         *moves = 0; *tc = temp * 1000; *incType = type;
1206         return 0;
1207     }
1208
1209     (*str)++; /* classical time control */
1210     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1211
1212     if(result == 0) {
1213         *moves = temp;
1214         *tc    = temp2 * 1000;
1215         *inc   = 0;
1216         *incType = type;
1217     }
1218     return result;
1219 }
1220
1221 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1222 {   /* [HGM] get time to add from the multi-session time-control string */
1223     int incType, moves=1; /* kludge to force reading of first session */
1224     long time, increment;
1225     char *s = tcString;
1226
1227     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1228     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1229     do {
1230         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1231         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1232         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1233         if(movenr == -1) return time;    /* last move before new session     */
1234         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1235         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1236         if(!moves) return increment;     /* current session is incremental   */
1237         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1238     } while(movenr >= -1);               /* try again for next session       */
1239
1240     return 0; // no new time quota on this move
1241 }
1242
1243 int
1244 ParseTimeControl(tc, ti, mps)
1245      char *tc;
1246      float ti;
1247      int mps;
1248 {
1249   long tc1;
1250   long tc2;
1251   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1252   int min, sec=0;
1253
1254   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1255   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1256       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1257   if(ti > 0) {
1258
1259     if(mps)
1260       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1261     else 
1262       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1263   } else {
1264     if(mps)
1265       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1266     else 
1267       snprintf(buf, MSG_SIZ, ":%s", mytc);
1268   }
1269   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1270   
1271   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1272     return FALSE;
1273   }
1274
1275   if( *tc == '/' ) {
1276     /* Parse second time control */
1277     tc++;
1278
1279     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1280       return FALSE;
1281     }
1282
1283     if( tc2 == 0 ) {
1284       return FALSE;
1285     }
1286
1287     timeControl_2 = tc2 * 1000;
1288   }
1289   else {
1290     timeControl_2 = 0;
1291   }
1292
1293   if( tc1 == 0 ) {
1294     return FALSE;
1295   }
1296
1297   timeControl = tc1 * 1000;
1298
1299   if (ti >= 0) {
1300     timeIncrement = ti * 1000;  /* convert to ms */
1301     movesPerSession = 0;
1302   } else {
1303     timeIncrement = 0;
1304     movesPerSession = mps;
1305   }
1306   return TRUE;
1307 }
1308
1309 void
1310 InitBackEnd2()
1311 {
1312     if (appData.debugMode) {
1313         fprintf(debugFP, "%s\n", programVersion);
1314     }
1315
1316     set_cont_sequence(appData.wrapContSeq);
1317     if (appData.matchGames > 0) {
1318         appData.matchMode = TRUE;
1319     } else if (appData.matchMode) {
1320         appData.matchGames = 1;
1321     }
1322     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1323         appData.matchGames = appData.sameColorGames;
1324     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1325         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1326         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1327     }
1328     Reset(TRUE, FALSE);
1329     if (appData.noChessProgram || first.protocolVersion == 1) {
1330       InitBackEnd3();
1331     } else {
1332       /* kludge: allow timeout for initial "feature" commands */
1333       FreezeUI();
1334       DisplayMessage("", _("Starting chess program"));
1335       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1336     }
1337 }
1338
1339 int
1340 CalculateIndex(int index, int gameNr)
1341 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1342     int res;
1343     if(index > 0) return index; // fixed nmber
1344     if(index == 0) return 1;
1345     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1346     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1347     return res;
1348 }
1349
1350 int
1351 LoadGameOrPosition(int gameNr)
1352 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1353     if (*appData.loadGameFile != NULLCHAR) {
1354         if (!LoadGameFromFile(appData.loadGameFile,
1355                 CalculateIndex(appData.loadGameIndex, gameNr),
1356                               appData.loadGameFile, FALSE)) {
1357             DisplayFatalError(_("Bad game file"), 0, 1);
1358             return 0;
1359         }
1360     } else if (*appData.loadPositionFile != NULLCHAR) {
1361         if (!LoadPositionFromFile(appData.loadPositionFile,
1362                 CalculateIndex(appData.loadPositionIndex, gameNr),
1363                                   appData.loadPositionFile)) {
1364             DisplayFatalError(_("Bad position file"), 0, 1);
1365             return 0;
1366         }
1367     }
1368     return 1;
1369 }
1370
1371 void
1372 ReserveGame(int gameNr, char resChar)
1373 {
1374     FILE *tf = fopen(appData.tourneyFile, "r+");
1375     char *p, *q, c, buf[MSG_SIZ];
1376     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1377     safeStrCpy(buf, lastMsg, MSG_SIZ);
1378     DisplayMessage(_("Pick new game"), "");
1379     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1380     ParseArgsFromFile(tf);
1381     p = q = appData.results;
1382     if(appData.debugMode) {
1383       char *r = appData.participants;
1384       fprintf(debugFP, "results = '%s'\n", p);
1385       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1386       fprintf(debugFP, "\n");
1387     }
1388     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1389     nextGame = q - p;
1390     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1391     safeStrCpy(q, p, strlen(p) + 2);
1392     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1393     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1394     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1395         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1396         q[nextGame] = '*';
1397     }
1398     fseek(tf, -(strlen(p)+4), SEEK_END);
1399     c = fgetc(tf);
1400     if(c != '"') // depending on DOS or Unix line endings we can be one off
1401          fseek(tf, -(strlen(p)+2), SEEK_END);
1402     else fseek(tf, -(strlen(p)+3), SEEK_END);
1403     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1404     DisplayMessage(buf, "");
1405     free(p); appData.results = q;
1406     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1407        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1408         UnloadEngine(&first);  // next game belongs to other pairing;
1409         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1410     }
1411 }
1412
1413 void
1414 MatchEvent(int mode)
1415 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1416         int dummy;
1417         if(matchMode) { // already in match mode: switch it off
1418             abortMatch = TRUE;
1419             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1420             return;
1421         }
1422 //      if(gameMode != BeginningOfGame) {
1423 //          DisplayError(_("You can only start a match from the initial position."), 0);
1424 //          return;
1425 //      }
1426         abortMatch = FALSE;
1427         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1428         /* Set up machine vs. machine match */
1429         nextGame = 0;
1430         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1431         if(appData.tourneyFile[0]) {
1432             ReserveGame(-1, 0);
1433             if(nextGame > appData.matchGames) {
1434                 char buf[MSG_SIZ];
1435                 if(strchr(appData.results, '*') == NULL) {
1436                     FILE *f;
1437                     appData.tourneyCycles++;
1438                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1439                         fclose(f);
1440                         NextTourneyGame(-1, &dummy);
1441                         ReserveGame(-1, 0);
1442                         if(nextGame <= appData.matchGames) {
1443                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1444                             matchMode = mode;
1445                             ScheduleDelayedEvent(NextMatchGame, 10000);
1446                             return;
1447                         }
1448                     }
1449                 }
1450                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1451                 DisplayError(buf, 0);
1452                 appData.tourneyFile[0] = 0;
1453                 return;
1454             }
1455         } else
1456         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1457             DisplayFatalError(_("Can't have a match with no chess programs"),
1458                               0, 2);
1459             return;
1460         }
1461         matchMode = mode;
1462         matchGame = roundNr = 1;
1463         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1464         NextMatchGame();
1465 }
1466
1467 void
1468 InitBackEnd3 P((void))
1469 {
1470     GameMode initialMode;
1471     char buf[MSG_SIZ];
1472     int err, len;
1473
1474     InitChessProgram(&first, startedFromSetupPosition);
1475
1476     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1477         free(programVersion);
1478         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1479         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1480     }
1481
1482     if (appData.icsActive) {
1483 #ifdef WIN32
1484         /* [DM] Make a console window if needed [HGM] merged ifs */
1485         ConsoleCreate();
1486 #endif
1487         err = establish();
1488         if (err != 0)
1489           {
1490             if (*appData.icsCommPort != NULLCHAR)
1491               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1492                              appData.icsCommPort);
1493             else
1494               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1495                         appData.icsHost, appData.icsPort);
1496
1497             if( (len > MSG_SIZ) && appData.debugMode )
1498               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1499
1500             DisplayFatalError(buf, err, 1);
1501             return;
1502         }
1503         SetICSMode();
1504         telnetISR =
1505           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1506         fromUserISR =
1507           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1508         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1509             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1510     } else if (appData.noChessProgram) {
1511         SetNCPMode();
1512     } else {
1513         SetGNUMode();
1514     }
1515
1516     if (*appData.cmailGameName != NULLCHAR) {
1517         SetCmailMode();
1518         OpenLoopback(&cmailPR);
1519         cmailISR =
1520           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1521     }
1522
1523     ThawUI();
1524     DisplayMessage("", "");
1525     if (StrCaseCmp(appData.initialMode, "") == 0) {
1526       initialMode = BeginningOfGame;
1527       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1528         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1529         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1530         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1531         ModeHighlight();
1532       }
1533     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1534       initialMode = TwoMachinesPlay;
1535     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1536       initialMode = AnalyzeFile;
1537     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1538       initialMode = AnalyzeMode;
1539     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1540       initialMode = MachinePlaysWhite;
1541     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1542       initialMode = MachinePlaysBlack;
1543     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1544       initialMode = EditGame;
1545     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1546       initialMode = EditPosition;
1547     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1548       initialMode = Training;
1549     } else {
1550       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1551       if( (len > MSG_SIZ) && appData.debugMode )
1552         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1553
1554       DisplayFatalError(buf, 0, 2);
1555       return;
1556     }
1557
1558     if (appData.matchMode) {
1559         if(appData.tourneyFile[0]) { // start tourney from command line
1560             FILE *f;
1561             if(f = fopen(appData.tourneyFile, "r")) {
1562                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1563                 fclose(f);
1564                 appData.clockMode = TRUE;
1565                 SetGNUMode();
1566             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1567         }
1568         MatchEvent(TRUE);
1569     } else if (*appData.cmailGameName != NULLCHAR) {
1570         /* Set up cmail mode */
1571         ReloadCmailMsgEvent(TRUE);
1572     } else {
1573         /* Set up other modes */
1574         if (initialMode == AnalyzeFile) {
1575           if (*appData.loadGameFile == NULLCHAR) {
1576             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1577             return;
1578           }
1579         }
1580         if (*appData.loadGameFile != NULLCHAR) {
1581             (void) LoadGameFromFile(appData.loadGameFile,
1582                                     appData.loadGameIndex,
1583                                     appData.loadGameFile, TRUE);
1584         } else if (*appData.loadPositionFile != NULLCHAR) {
1585             (void) LoadPositionFromFile(appData.loadPositionFile,
1586                                         appData.loadPositionIndex,
1587                                         appData.loadPositionFile);
1588             /* [HGM] try to make self-starting even after FEN load */
1589             /* to allow automatic setup of fairy variants with wtm */
1590             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1591                 gameMode = BeginningOfGame;
1592                 setboardSpoiledMachineBlack = 1;
1593             }
1594             /* [HGM] loadPos: make that every new game uses the setup */
1595             /* from file as long as we do not switch variant          */
1596             if(!blackPlaysFirst) {
1597                 startedFromPositionFile = TRUE;
1598                 CopyBoard(filePosition, boards[0]);
1599             }
1600         }
1601         if (initialMode == AnalyzeMode) {
1602           if (appData.noChessProgram) {
1603             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1604             return;
1605           }
1606           if (appData.icsActive) {
1607             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1608             return;
1609           }
1610           AnalyzeModeEvent();
1611         } else if (initialMode == AnalyzeFile) {
1612           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1613           ShowThinkingEvent();
1614           AnalyzeFileEvent();
1615           AnalysisPeriodicEvent(1);
1616         } else if (initialMode == MachinePlaysWhite) {
1617           if (appData.noChessProgram) {
1618             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1619                               0, 2);
1620             return;
1621           }
1622           if (appData.icsActive) {
1623             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1624                               0, 2);
1625             return;
1626           }
1627           MachineWhiteEvent();
1628         } else if (initialMode == MachinePlaysBlack) {
1629           if (appData.noChessProgram) {
1630             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1631                               0, 2);
1632             return;
1633           }
1634           if (appData.icsActive) {
1635             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1636                               0, 2);
1637             return;
1638           }
1639           MachineBlackEvent();
1640         } else if (initialMode == TwoMachinesPlay) {
1641           if (appData.noChessProgram) {
1642             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1643                               0, 2);
1644             return;
1645           }
1646           if (appData.icsActive) {
1647             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1648                               0, 2);
1649             return;
1650           }
1651           TwoMachinesEvent();
1652         } else if (initialMode == EditGame) {
1653           EditGameEvent();
1654         } else if (initialMode == EditPosition) {
1655           EditPositionEvent();
1656         } else if (initialMode == Training) {
1657           if (*appData.loadGameFile == NULLCHAR) {
1658             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1659             return;
1660           }
1661           TrainingEvent();
1662         }
1663     }
1664 }
1665
1666 /*
1667  * Establish will establish a contact to a remote host.port.
1668  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1669  *  used to talk to the host.
1670  * Returns 0 if okay, error code if not.
1671  */
1672 int
1673 establish()
1674 {
1675     char buf[MSG_SIZ];
1676
1677     if (*appData.icsCommPort != NULLCHAR) {
1678         /* Talk to the host through a serial comm port */
1679         return OpenCommPort(appData.icsCommPort, &icsPR);
1680
1681     } else if (*appData.gateway != NULLCHAR) {
1682         if (*appData.remoteShell == NULLCHAR) {
1683             /* Use the rcmd protocol to run telnet program on a gateway host */
1684             snprintf(buf, sizeof(buf), "%s %s %s",
1685                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1686             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1687
1688         } else {
1689             /* Use the rsh program to run telnet program on a gateway host */
1690             if (*appData.remoteUser == NULLCHAR) {
1691                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1692                         appData.gateway, appData.telnetProgram,
1693                         appData.icsHost, appData.icsPort);
1694             } else {
1695                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1696                         appData.remoteShell, appData.gateway,
1697                         appData.remoteUser, appData.telnetProgram,
1698                         appData.icsHost, appData.icsPort);
1699             }
1700             return StartChildProcess(buf, "", &icsPR);
1701
1702         }
1703     } else if (appData.useTelnet) {
1704         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1705
1706     } else {
1707         /* TCP socket interface differs somewhat between
1708            Unix and NT; handle details in the front end.
1709            */
1710         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1711     }
1712 }
1713
1714 void EscapeExpand(char *p, char *q)
1715 {       // [HGM] initstring: routine to shape up string arguments
1716         while(*p++ = *q++) if(p[-1] == '\\')
1717             switch(*q++) {
1718                 case 'n': p[-1] = '\n'; break;
1719                 case 'r': p[-1] = '\r'; break;
1720                 case 't': p[-1] = '\t'; break;
1721                 case '\\': p[-1] = '\\'; break;
1722                 case 0: *p = 0; return;
1723                 default: p[-1] = q[-1]; break;
1724             }
1725 }
1726
1727 void
1728 show_bytes(fp, buf, count)
1729      FILE *fp;
1730      char *buf;
1731      int count;
1732 {
1733     while (count--) {
1734         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1735             fprintf(fp, "\\%03o", *buf & 0xff);
1736         } else {
1737             putc(*buf, fp);
1738         }
1739         buf++;
1740     }
1741     fflush(fp);
1742 }
1743
1744 /* Returns an errno value */
1745 int
1746 OutputMaybeTelnet(pr, message, count, outError)
1747      ProcRef pr;
1748      char *message;
1749      int count;
1750      int *outError;
1751 {
1752     char buf[8192], *p, *q, *buflim;
1753     int left, newcount, outcount;
1754
1755     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1756         *appData.gateway != NULLCHAR) {
1757         if (appData.debugMode) {
1758             fprintf(debugFP, ">ICS: ");
1759             show_bytes(debugFP, message, count);
1760             fprintf(debugFP, "\n");
1761         }
1762         return OutputToProcess(pr, message, count, outError);
1763     }
1764
1765     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1766     p = message;
1767     q = buf;
1768     left = count;
1769     newcount = 0;
1770     while (left) {
1771         if (q >= buflim) {
1772             if (appData.debugMode) {
1773                 fprintf(debugFP, ">ICS: ");
1774                 show_bytes(debugFP, buf, newcount);
1775                 fprintf(debugFP, "\n");
1776             }
1777             outcount = OutputToProcess(pr, buf, newcount, outError);
1778             if (outcount < newcount) return -1; /* to be sure */
1779             q = buf;
1780             newcount = 0;
1781         }
1782         if (*p == '\n') {
1783             *q++ = '\r';
1784             newcount++;
1785         } else if (((unsigned char) *p) == TN_IAC) {
1786             *q++ = (char) TN_IAC;
1787             newcount ++;
1788         }
1789         *q++ = *p++;
1790         newcount++;
1791         left--;
1792     }
1793     if (appData.debugMode) {
1794         fprintf(debugFP, ">ICS: ");
1795         show_bytes(debugFP, buf, newcount);
1796         fprintf(debugFP, "\n");
1797     }
1798     outcount = OutputToProcess(pr, buf, newcount, outError);
1799     if (outcount < newcount) return -1; /* to be sure */
1800     return count;
1801 }
1802
1803 void
1804 read_from_player(isr, closure, message, count, error)
1805      InputSourceRef isr;
1806      VOIDSTAR closure;
1807      char *message;
1808      int count;
1809      int error;
1810 {
1811     int outError, outCount;
1812     static int gotEof = 0;
1813
1814     /* Pass data read from player on to ICS */
1815     if (count > 0) {
1816         gotEof = 0;
1817         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1818         if (outCount < count) {
1819             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1820         }
1821     } else if (count < 0) {
1822         RemoveInputSource(isr);
1823         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1824     } else if (gotEof++ > 0) {
1825         RemoveInputSource(isr);
1826         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1827     }
1828 }
1829
1830 void
1831 KeepAlive()
1832 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1833     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1834     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1835     SendToICS("date\n");
1836     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1837 }
1838
1839 /* added routine for printf style output to ics */
1840 void ics_printf(char *format, ...)
1841 {
1842     char buffer[MSG_SIZ];
1843     va_list args;
1844
1845     va_start(args, format);
1846     vsnprintf(buffer, sizeof(buffer), format, args);
1847     buffer[sizeof(buffer)-1] = '\0';
1848     SendToICS(buffer);
1849     va_end(args);
1850 }
1851
1852 void
1853 SendToICS(s)
1854      char *s;
1855 {
1856     int count, outCount, outError;
1857
1858     if (icsPR == NULL) return;
1859
1860     count = strlen(s);
1861     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1862     if (outCount < count) {
1863         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1864     }
1865 }
1866
1867 /* This is used for sending logon scripts to the ICS. Sending
1868    without a delay causes problems when using timestamp on ICC
1869    (at least on my machine). */
1870 void
1871 SendToICSDelayed(s,msdelay)
1872      char *s;
1873      long msdelay;
1874 {
1875     int count, outCount, outError;
1876
1877     if (icsPR == NULL) return;
1878
1879     count = strlen(s);
1880     if (appData.debugMode) {
1881         fprintf(debugFP, ">ICS: ");
1882         show_bytes(debugFP, s, count);
1883         fprintf(debugFP, "\n");
1884     }
1885     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1886                                       msdelay);
1887     if (outCount < count) {
1888         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1889     }
1890 }
1891
1892
1893 /* Remove all highlighting escape sequences in s
1894    Also deletes any suffix starting with '('
1895    */
1896 char *
1897 StripHighlightAndTitle(s)
1898      char *s;
1899 {
1900     static char retbuf[MSG_SIZ];
1901     char *p = retbuf;
1902
1903     while (*s != NULLCHAR) {
1904         while (*s == '\033') {
1905             while (*s != NULLCHAR && !isalpha(*s)) s++;
1906             if (*s != NULLCHAR) s++;
1907         }
1908         while (*s != NULLCHAR && *s != '\033') {
1909             if (*s == '(' || *s == '[') {
1910                 *p = NULLCHAR;
1911                 return retbuf;
1912             }
1913             *p++ = *s++;
1914         }
1915     }
1916     *p = NULLCHAR;
1917     return retbuf;
1918 }
1919
1920 /* Remove all highlighting escape sequences in s */
1921 char *
1922 StripHighlight(s)
1923      char *s;
1924 {
1925     static char retbuf[MSG_SIZ];
1926     char *p = retbuf;
1927
1928     while (*s != NULLCHAR) {
1929         while (*s == '\033') {
1930             while (*s != NULLCHAR && !isalpha(*s)) s++;
1931             if (*s != NULLCHAR) s++;
1932         }
1933         while (*s != NULLCHAR && *s != '\033') {
1934             *p++ = *s++;
1935         }
1936     }
1937     *p = NULLCHAR;
1938     return retbuf;
1939 }
1940
1941 char *variantNames[] = VARIANT_NAMES;
1942 char *
1943 VariantName(v)
1944      VariantClass v;
1945 {
1946     return variantNames[v];
1947 }
1948
1949
1950 /* Identify a variant from the strings the chess servers use or the
1951    PGN Variant tag names we use. */
1952 VariantClass
1953 StringToVariant(e)
1954      char *e;
1955 {
1956     char *p;
1957     int wnum = -1;
1958     VariantClass v = VariantNormal;
1959     int i, found = FALSE;
1960     char buf[MSG_SIZ];
1961     int len;
1962
1963     if (!e) return v;
1964
1965     /* [HGM] skip over optional board-size prefixes */
1966     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1967         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1968         while( *e++ != '_');
1969     }
1970
1971     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1972         v = VariantNormal;
1973         found = TRUE;
1974     } else
1975     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1976       if (StrCaseStr(e, variantNames[i])) {
1977         v = (VariantClass) i;
1978         found = TRUE;
1979         break;
1980       }
1981     }
1982
1983     if (!found) {
1984       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1985           || StrCaseStr(e, "wild/fr")
1986           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1987         v = VariantFischeRandom;
1988       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1989                  (i = 1, p = StrCaseStr(e, "w"))) {
1990         p += i;
1991         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1992         if (isdigit(*p)) {
1993           wnum = atoi(p);
1994         } else {
1995           wnum = -1;
1996         }
1997         switch (wnum) {
1998         case 0: /* FICS only, actually */
1999         case 1:
2000           /* Castling legal even if K starts on d-file */
2001           v = VariantWildCastle;
2002           break;
2003         case 2:
2004         case 3:
2005         case 4:
2006           /* Castling illegal even if K & R happen to start in
2007              normal positions. */
2008           v = VariantNoCastle;
2009           break;
2010         case 5:
2011         case 7:
2012         case 8:
2013         case 10:
2014         case 11:
2015         case 12:
2016         case 13:
2017         case 14:
2018         case 15:
2019         case 18:
2020         case 19:
2021           /* Castling legal iff K & R start in normal positions */
2022           v = VariantNormal;
2023           break;
2024         case 6:
2025         case 20:
2026         case 21:
2027           /* Special wilds for position setup; unclear what to do here */
2028           v = VariantLoadable;
2029           break;
2030         case 9:
2031           /* Bizarre ICC game */
2032           v = VariantTwoKings;
2033           break;
2034         case 16:
2035           v = VariantKriegspiel;
2036           break;
2037         case 17:
2038           v = VariantLosers;
2039           break;
2040         case 22:
2041           v = VariantFischeRandom;
2042           break;
2043         case 23:
2044           v = VariantCrazyhouse;
2045           break;
2046         case 24:
2047           v = VariantBughouse;
2048           break;
2049         case 25:
2050           v = Variant3Check;
2051           break;
2052         case 26:
2053           /* Not quite the same as FICS suicide! */
2054           v = VariantGiveaway;
2055           break;
2056         case 27:
2057           v = VariantAtomic;
2058           break;
2059         case 28:
2060           v = VariantShatranj;
2061           break;
2062
2063         /* Temporary names for future ICC types.  The name *will* change in
2064            the next xboard/WinBoard release after ICC defines it. */
2065         case 29:
2066           v = Variant29;
2067           break;
2068         case 30:
2069           v = Variant30;
2070           break;
2071         case 31:
2072           v = Variant31;
2073           break;
2074         case 32:
2075           v = Variant32;
2076           break;
2077         case 33:
2078           v = Variant33;
2079           break;
2080         case 34:
2081           v = Variant34;
2082           break;
2083         case 35:
2084           v = Variant35;
2085           break;
2086         case 36:
2087           v = Variant36;
2088           break;
2089         case 37:
2090           v = VariantShogi;
2091           break;
2092         case 38:
2093           v = VariantXiangqi;
2094           break;
2095         case 39:
2096           v = VariantCourier;
2097           break;
2098         case 40:
2099           v = VariantGothic;
2100           break;
2101         case 41:
2102           v = VariantCapablanca;
2103           break;
2104         case 42:
2105           v = VariantKnightmate;
2106           break;
2107         case 43:
2108           v = VariantFairy;
2109           break;
2110         case 44:
2111           v = VariantCylinder;
2112           break;
2113         case 45:
2114           v = VariantFalcon;
2115           break;
2116         case 46:
2117           v = VariantCapaRandom;
2118           break;
2119         case 47:
2120           v = VariantBerolina;
2121           break;
2122         case 48:
2123           v = VariantJanus;
2124           break;
2125         case 49:
2126           v = VariantSuper;
2127           break;
2128         case 50:
2129           v = VariantGreat;
2130           break;
2131         case -1:
2132           /* Found "wild" or "w" in the string but no number;
2133              must assume it's normal chess. */
2134           v = VariantNormal;
2135           break;
2136         default:
2137           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2138           if( (len > MSG_SIZ) && appData.debugMode )
2139             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2140
2141           DisplayError(buf, 0);
2142           v = VariantUnknown;
2143           break;
2144         }
2145       }
2146     }
2147     if (appData.debugMode) {
2148       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2149               e, wnum, VariantName(v));
2150     }
2151     return v;
2152 }
2153
2154 static int leftover_start = 0, leftover_len = 0;
2155 char star_match[STAR_MATCH_N][MSG_SIZ];
2156
2157 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2158    advance *index beyond it, and set leftover_start to the new value of
2159    *index; else return FALSE.  If pattern contains the character '*', it
2160    matches any sequence of characters not containing '\r', '\n', or the
2161    character following the '*' (if any), and the matched sequence(s) are
2162    copied into star_match.
2163    */
2164 int
2165 looking_at(buf, index, pattern)
2166      char *buf;
2167      int *index;
2168      char *pattern;
2169 {
2170     char *bufp = &buf[*index], *patternp = pattern;
2171     int star_count = 0;
2172     char *matchp = star_match[0];
2173
2174     for (;;) {
2175         if (*patternp == NULLCHAR) {
2176             *index = leftover_start = bufp - buf;
2177             *matchp = NULLCHAR;
2178             return TRUE;
2179         }
2180         if (*bufp == NULLCHAR) return FALSE;
2181         if (*patternp == '*') {
2182             if (*bufp == *(patternp + 1)) {
2183                 *matchp = NULLCHAR;
2184                 matchp = star_match[++star_count];
2185                 patternp += 2;
2186                 bufp++;
2187                 continue;
2188             } else if (*bufp == '\n' || *bufp == '\r') {
2189                 patternp++;
2190                 if (*patternp == NULLCHAR)
2191                   continue;
2192                 else
2193                   return FALSE;
2194             } else {
2195                 *matchp++ = *bufp++;
2196                 continue;
2197             }
2198         }
2199         if (*patternp != *bufp) return FALSE;
2200         patternp++;
2201         bufp++;
2202     }
2203 }
2204
2205 void
2206 SendToPlayer(data, length)
2207      char *data;
2208      int length;
2209 {
2210     int error, outCount;
2211     outCount = OutputToProcess(NoProc, data, length, &error);
2212     if (outCount < length) {
2213         DisplayFatalError(_("Error writing to display"), error, 1);
2214     }
2215 }
2216
2217 void
2218 PackHolding(packed, holding)
2219      char packed[];
2220      char *holding;
2221 {
2222     char *p = holding;
2223     char *q = packed;
2224     int runlength = 0;
2225     int curr = 9999;
2226     do {
2227         if (*p == curr) {
2228             runlength++;
2229         } else {
2230             switch (runlength) {
2231               case 0:
2232                 break;
2233               case 1:
2234                 *q++ = curr;
2235                 break;
2236               case 2:
2237                 *q++ = curr;
2238                 *q++ = curr;
2239                 break;
2240               default:
2241                 sprintf(q, "%d", runlength);
2242                 while (*q) q++;
2243                 *q++ = curr;
2244                 break;
2245             }
2246             runlength = 1;
2247             curr = *p;
2248         }
2249     } while (*p++);
2250     *q = NULLCHAR;
2251 }
2252
2253 /* Telnet protocol requests from the front end */
2254 void
2255 TelnetRequest(ddww, option)
2256      unsigned char ddww, option;
2257 {
2258     unsigned char msg[3];
2259     int outCount, outError;
2260
2261     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2262
2263     if (appData.debugMode) {
2264         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2265         switch (ddww) {
2266           case TN_DO:
2267             ddwwStr = "DO";
2268             break;
2269           case TN_DONT:
2270             ddwwStr = "DONT";
2271             break;
2272           case TN_WILL:
2273             ddwwStr = "WILL";
2274             break;
2275           case TN_WONT:
2276             ddwwStr = "WONT";
2277             break;
2278           default:
2279             ddwwStr = buf1;
2280             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2281             break;
2282         }
2283         switch (option) {
2284           case TN_ECHO:
2285             optionStr = "ECHO";
2286             break;
2287           default:
2288             optionStr = buf2;
2289             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2290             break;
2291         }
2292         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2293     }
2294     msg[0] = TN_IAC;
2295     msg[1] = ddww;
2296     msg[2] = option;
2297     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2298     if (outCount < 3) {
2299         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2300     }
2301 }
2302
2303 void
2304 DoEcho()
2305 {
2306     if (!appData.icsActive) return;
2307     TelnetRequest(TN_DO, TN_ECHO);
2308 }
2309
2310 void
2311 DontEcho()
2312 {
2313     if (!appData.icsActive) return;
2314     TelnetRequest(TN_DONT, TN_ECHO);
2315 }
2316
2317 void
2318 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2319 {
2320     /* put the holdings sent to us by the server on the board holdings area */
2321     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2322     char p;
2323     ChessSquare piece;
2324
2325     if(gameInfo.holdingsWidth < 2)  return;
2326     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2327         return; // prevent overwriting by pre-board holdings
2328
2329     if( (int)lowestPiece >= BlackPawn ) {
2330         holdingsColumn = 0;
2331         countsColumn = 1;
2332         holdingsStartRow = BOARD_HEIGHT-1;
2333         direction = -1;
2334     } else {
2335         holdingsColumn = BOARD_WIDTH-1;
2336         countsColumn = BOARD_WIDTH-2;
2337         holdingsStartRow = 0;
2338         direction = 1;
2339     }
2340
2341     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2342         board[i][holdingsColumn] = EmptySquare;
2343         board[i][countsColumn]   = (ChessSquare) 0;
2344     }
2345     while( (p=*holdings++) != NULLCHAR ) {
2346         piece = CharToPiece( ToUpper(p) );
2347         if(piece == EmptySquare) continue;
2348         /*j = (int) piece - (int) WhitePawn;*/
2349         j = PieceToNumber(piece);
2350         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2351         if(j < 0) continue;               /* should not happen */
2352         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2353         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2354         board[holdingsStartRow+j*direction][countsColumn]++;
2355     }
2356 }
2357
2358
2359 void
2360 VariantSwitch(Board board, VariantClass newVariant)
2361 {
2362    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2363    static Board oldBoard;
2364
2365    startedFromPositionFile = FALSE;
2366    if(gameInfo.variant == newVariant) return;
2367
2368    /* [HGM] This routine is called each time an assignment is made to
2369     * gameInfo.variant during a game, to make sure the board sizes
2370     * are set to match the new variant. If that means adding or deleting
2371     * holdings, we shift the playing board accordingly
2372     * This kludge is needed because in ICS observe mode, we get boards
2373     * of an ongoing game without knowing the variant, and learn about the
2374     * latter only later. This can be because of the move list we requested,
2375     * in which case the game history is refilled from the beginning anyway,
2376     * but also when receiving holdings of a crazyhouse game. In the latter
2377     * case we want to add those holdings to the already received position.
2378     */
2379
2380
2381    if (appData.debugMode) {
2382      fprintf(debugFP, "Switch board from %s to %s\n",
2383              VariantName(gameInfo.variant), VariantName(newVariant));
2384      setbuf(debugFP, NULL);
2385    }
2386    shuffleOpenings = 0;       /* [HGM] shuffle */
2387    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2388    switch(newVariant)
2389      {
2390      case VariantShogi:
2391        newWidth = 9;  newHeight = 9;
2392        gameInfo.holdingsSize = 7;
2393      case VariantBughouse:
2394      case VariantCrazyhouse:
2395        newHoldingsWidth = 2; break;
2396      case VariantGreat:
2397        newWidth = 10;
2398      case VariantSuper:
2399        newHoldingsWidth = 2;
2400        gameInfo.holdingsSize = 8;
2401        break;
2402      case VariantGothic:
2403      case VariantCapablanca:
2404      case VariantCapaRandom:
2405        newWidth = 10;
2406      default:
2407        newHoldingsWidth = gameInfo.holdingsSize = 0;
2408      };
2409
2410    if(newWidth  != gameInfo.boardWidth  ||
2411       newHeight != gameInfo.boardHeight ||
2412       newHoldingsWidth != gameInfo.holdingsWidth ) {
2413
2414      /* shift position to new playing area, if needed */
2415      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2416        for(i=0; i<BOARD_HEIGHT; i++)
2417          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2418            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2419              board[i][j];
2420        for(i=0; i<newHeight; i++) {
2421          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2422          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2423        }
2424      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2425        for(i=0; i<BOARD_HEIGHT; i++)
2426          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2427            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2428              board[i][j];
2429      }
2430      gameInfo.boardWidth  = newWidth;
2431      gameInfo.boardHeight = newHeight;
2432      gameInfo.holdingsWidth = newHoldingsWidth;
2433      gameInfo.variant = newVariant;
2434      InitDrawingSizes(-2, 0);
2435    } else gameInfo.variant = newVariant;
2436    CopyBoard(oldBoard, board);   // remember correctly formatted board
2437      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2438    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2439 }
2440
2441 static int loggedOn = FALSE;
2442
2443 /*-- Game start info cache: --*/
2444 int gs_gamenum;
2445 char gs_kind[MSG_SIZ];
2446 static char player1Name[128] = "";
2447 static char player2Name[128] = "";
2448 static char cont_seq[] = "\n\\   ";
2449 static int player1Rating = -1;
2450 static int player2Rating = -1;
2451 /*----------------------------*/
2452
2453 ColorClass curColor = ColorNormal;
2454 int suppressKibitz = 0;
2455
2456 // [HGM] seekgraph
2457 Boolean soughtPending = FALSE;
2458 Boolean seekGraphUp;
2459 #define MAX_SEEK_ADS 200
2460 #define SQUARE 0x80
2461 char *seekAdList[MAX_SEEK_ADS];
2462 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2463 float tcList[MAX_SEEK_ADS];
2464 char colorList[MAX_SEEK_ADS];
2465 int nrOfSeekAds = 0;
2466 int minRating = 1010, maxRating = 2800;
2467 int hMargin = 10, vMargin = 20, h, w;
2468 extern int squareSize, lineGap;
2469
2470 void
2471 PlotSeekAd(int i)
2472 {
2473         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2474         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2475         if(r < minRating+100 && r >=0 ) r = minRating+100;
2476         if(r > maxRating) r = maxRating;
2477         if(tc < 1.) tc = 1.;
2478         if(tc > 95.) tc = 95.;
2479         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2480         y = ((double)r - minRating)/(maxRating - minRating)
2481             * (h-vMargin-squareSize/8-1) + vMargin;
2482         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2483         if(strstr(seekAdList[i], " u ")) color = 1;
2484         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2485            !strstr(seekAdList[i], "bullet") &&
2486            !strstr(seekAdList[i], "blitz") &&
2487            !strstr(seekAdList[i], "standard") ) color = 2;
2488         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2489         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2490 }
2491
2492 void
2493 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2494 {
2495         char buf[MSG_SIZ], *ext = "";
2496         VariantClass v = StringToVariant(type);
2497         if(strstr(type, "wild")) {
2498             ext = type + 4; // append wild number
2499             if(v == VariantFischeRandom) type = "chess960"; else
2500             if(v == VariantLoadable) type = "setup"; else
2501             type = VariantName(v);
2502         }
2503         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2504         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2505             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2506             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2507             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2508             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2509             seekNrList[nrOfSeekAds] = nr;
2510             zList[nrOfSeekAds] = 0;
2511             seekAdList[nrOfSeekAds++] = StrSave(buf);
2512             if(plot) PlotSeekAd(nrOfSeekAds-1);
2513         }
2514 }
2515
2516 void
2517 EraseSeekDot(int i)
2518 {
2519     int x = xList[i], y = yList[i], d=squareSize/4, k;
2520     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2521     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2522     // now replot every dot that overlapped
2523     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2524         int xx = xList[k], yy = yList[k];
2525         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2526             DrawSeekDot(xx, yy, colorList[k]);
2527     }
2528 }
2529
2530 void
2531 RemoveSeekAd(int nr)
2532 {
2533         int i;
2534         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2535             EraseSeekDot(i);
2536             if(seekAdList[i]) free(seekAdList[i]);
2537             seekAdList[i] = seekAdList[--nrOfSeekAds];
2538             seekNrList[i] = seekNrList[nrOfSeekAds];
2539             ratingList[i] = ratingList[nrOfSeekAds];
2540             colorList[i]  = colorList[nrOfSeekAds];
2541             tcList[i] = tcList[nrOfSeekAds];
2542             xList[i]  = xList[nrOfSeekAds];
2543             yList[i]  = yList[nrOfSeekAds];
2544             zList[i]  = zList[nrOfSeekAds];
2545             seekAdList[nrOfSeekAds] = NULL;
2546             break;
2547         }
2548 }
2549
2550 Boolean
2551 MatchSoughtLine(char *line)
2552 {
2553     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2554     int nr, base, inc, u=0; char dummy;
2555
2556     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2557        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2558        (u=1) &&
2559        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2561         // match: compact and save the line
2562         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2563         return TRUE;
2564     }
2565     return FALSE;
2566 }
2567
2568 int
2569 DrawSeekGraph()
2570 {
2571     int i;
2572     if(!seekGraphUp) return FALSE;
2573     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2574     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2575
2576     DrawSeekBackground(0, 0, w, h);
2577     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2578     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2579     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2580         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2581         yy = h-1-yy;
2582         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2583         if(i%500 == 0) {
2584             char buf[MSG_SIZ];
2585             snprintf(buf, MSG_SIZ, "%d", i);
2586             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2587         }
2588     }
2589     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2590     for(i=1; i<100; i+=(i<10?1:5)) {
2591         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2592         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2593         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2594             char buf[MSG_SIZ];
2595             snprintf(buf, MSG_SIZ, "%d", i);
2596             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2597         }
2598     }
2599     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2600     return TRUE;
2601 }
2602
2603 int SeekGraphClick(ClickType click, int x, int y, int moving)
2604 {
2605     static int lastDown = 0, displayed = 0, lastSecond;
2606     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2607         if(click == Release || moving) return FALSE;
2608         nrOfSeekAds = 0;
2609         soughtPending = TRUE;
2610         SendToICS(ics_prefix);
2611         SendToICS("sought\n"); // should this be "sought all"?
2612     } else { // issue challenge based on clicked ad
2613         int dist = 10000; int i, closest = 0, second = 0;
2614         for(i=0; i<nrOfSeekAds; i++) {
2615             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2616             if(d < dist) { dist = d; closest = i; }
2617             second += (d - zList[i] < 120); // count in-range ads
2618             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2619         }
2620         if(dist < 120) {
2621             char buf[MSG_SIZ];
2622             second = (second > 1);
2623             if(displayed != closest || second != lastSecond) {
2624                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2625                 lastSecond = second; displayed = closest;
2626             }
2627             if(click == Press) {
2628                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2629                 lastDown = closest;
2630                 return TRUE;
2631             } // on press 'hit', only show info
2632             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2633             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2634             SendToICS(ics_prefix);
2635             SendToICS(buf);
2636             return TRUE; // let incoming board of started game pop down the graph
2637         } else if(click == Release) { // release 'miss' is ignored
2638             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2639             if(moving == 2) { // right up-click
2640                 nrOfSeekAds = 0; // refresh graph
2641                 soughtPending = TRUE;
2642                 SendToICS(ics_prefix);
2643                 SendToICS("sought\n"); // should this be "sought all"?
2644             }
2645             return TRUE;
2646         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2647         // press miss or release hit 'pop down' seek graph
2648         seekGraphUp = FALSE;
2649         DrawPosition(TRUE, NULL);
2650     }
2651     return TRUE;
2652 }
2653
2654 void
2655 read_from_ics(isr, closure, data, count, error)
2656      InputSourceRef isr;
2657      VOIDSTAR closure;
2658      char *data;
2659      int count;
2660      int error;
2661 {
2662 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2663 #define STARTED_NONE 0
2664 #define STARTED_MOVES 1
2665 #define STARTED_BOARD 2
2666 #define STARTED_OBSERVE 3
2667 #define STARTED_HOLDINGS 4
2668 #define STARTED_CHATTER 5
2669 #define STARTED_COMMENT 6
2670 #define STARTED_MOVES_NOHIDE 7
2671
2672     static int started = STARTED_NONE;
2673     static char parse[20000];
2674     static int parse_pos = 0;
2675     static char buf[BUF_SIZE + 1];
2676     static int firstTime = TRUE, intfSet = FALSE;
2677     static ColorClass prevColor = ColorNormal;
2678     static int savingComment = FALSE;
2679     static int cmatch = 0; // continuation sequence match
2680     char *bp;
2681     char str[MSG_SIZ];
2682     int i, oldi;
2683     int buf_len;
2684     int next_out;
2685     int tkind;
2686     int backup;    /* [DM] For zippy color lines */
2687     char *p;
2688     char talker[MSG_SIZ]; // [HGM] chat
2689     int channel;
2690
2691     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2692
2693     if (appData.debugMode) {
2694       if (!error) {
2695         fprintf(debugFP, "<ICS: ");
2696         show_bytes(debugFP, data, count);
2697         fprintf(debugFP, "\n");
2698       }
2699     }
2700
2701     if (appData.debugMode) { int f = forwardMostMove;
2702         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2703                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2704                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2705     }
2706     if (count > 0) {
2707         /* If last read ended with a partial line that we couldn't parse,
2708            prepend it to the new read and try again. */
2709         if (leftover_len > 0) {
2710             for (i=0; i<leftover_len; i++)
2711               buf[i] = buf[leftover_start + i];
2712         }
2713
2714     /* copy new characters into the buffer */
2715     bp = buf + leftover_len;
2716     buf_len=leftover_len;
2717     for (i=0; i<count; i++)
2718     {
2719         // ignore these
2720         if (data[i] == '\r')
2721             continue;
2722
2723         // join lines split by ICS?
2724         if (!appData.noJoin)
2725         {
2726             /*
2727                 Joining just consists of finding matches against the
2728                 continuation sequence, and discarding that sequence
2729                 if found instead of copying it.  So, until a match
2730                 fails, there's nothing to do since it might be the
2731                 complete sequence, and thus, something we don't want
2732                 copied.
2733             */
2734             if (data[i] == cont_seq[cmatch])
2735             {
2736                 cmatch++;
2737                 if (cmatch == strlen(cont_seq))
2738                 {
2739                     cmatch = 0; // complete match.  just reset the counter
2740
2741                     /*
2742                         it's possible for the ICS to not include the space
2743                         at the end of the last word, making our [correct]
2744                         join operation fuse two separate words.  the server
2745                         does this when the space occurs at the width setting.
2746                     */
2747                     if (!buf_len || buf[buf_len-1] != ' ')
2748                     {
2749                         *bp++ = ' ';
2750                         buf_len++;
2751                     }
2752                 }
2753                 continue;
2754             }
2755             else if (cmatch)
2756             {
2757                 /*
2758                     match failed, so we have to copy what matched before
2759                     falling through and copying this character.  In reality,
2760                     this will only ever be just the newline character, but
2761                     it doesn't hurt to be precise.
2762                 */
2763                 strncpy(bp, cont_seq, cmatch);
2764                 bp += cmatch;
2765                 buf_len += cmatch;
2766                 cmatch = 0;
2767             }
2768         }
2769
2770         // copy this char
2771         *bp++ = data[i];
2772         buf_len++;
2773     }
2774
2775         buf[buf_len] = NULLCHAR;
2776 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2777         next_out = 0;
2778         leftover_start = 0;
2779
2780         i = 0;
2781         while (i < buf_len) {
2782             /* Deal with part of the TELNET option negotiation
2783                protocol.  We refuse to do anything beyond the
2784                defaults, except that we allow the WILL ECHO option,
2785                which ICS uses to turn off password echoing when we are
2786                directly connected to it.  We reject this option
2787                if localLineEditing mode is on (always on in xboard)
2788                and we are talking to port 23, which might be a real
2789                telnet server that will try to keep WILL ECHO on permanently.
2790              */
2791             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2792                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2793                 unsigned char option;
2794                 oldi = i;
2795                 switch ((unsigned char) buf[++i]) {
2796                   case TN_WILL:
2797                     if (appData.debugMode)
2798                       fprintf(debugFP, "\n<WILL ");
2799                     switch (option = (unsigned char) buf[++i]) {
2800                       case TN_ECHO:
2801                         if (appData.debugMode)
2802                           fprintf(debugFP, "ECHO ");
2803                         /* Reply only if this is a change, according
2804                            to the protocol rules. */
2805                         if (remoteEchoOption) break;
2806                         if (appData.localLineEditing &&
2807                             atoi(appData.icsPort) == TN_PORT) {
2808                             TelnetRequest(TN_DONT, TN_ECHO);
2809                         } else {
2810                             EchoOff();
2811                             TelnetRequest(TN_DO, TN_ECHO);
2812                             remoteEchoOption = TRUE;
2813                         }
2814                         break;
2815                       default:
2816                         if (appData.debugMode)
2817                           fprintf(debugFP, "%d ", option);
2818                         /* Whatever this is, we don't want it. */
2819                         TelnetRequest(TN_DONT, option);
2820                         break;
2821                     }
2822                     break;
2823                   case TN_WONT:
2824                     if (appData.debugMode)
2825                       fprintf(debugFP, "\n<WONT ");
2826                     switch (option = (unsigned char) buf[++i]) {
2827                       case TN_ECHO:
2828                         if (appData.debugMode)
2829                           fprintf(debugFP, "ECHO ");
2830                         /* Reply only if this is a change, according
2831                            to the protocol rules. */
2832                         if (!remoteEchoOption) break;
2833                         EchoOn();
2834                         TelnetRequest(TN_DONT, TN_ECHO);
2835                         remoteEchoOption = FALSE;
2836                         break;
2837                       default:
2838                         if (appData.debugMode)
2839                           fprintf(debugFP, "%d ", (unsigned char) option);
2840                         /* Whatever this is, it must already be turned
2841                            off, because we never agree to turn on
2842                            anything non-default, so according to the
2843                            protocol rules, we don't reply. */
2844                         break;
2845                     }
2846                     break;
2847                   case TN_DO:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<DO ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       default:
2852                         /* Whatever this is, we refuse to do it. */
2853                         if (appData.debugMode)
2854                           fprintf(debugFP, "%d ", option);
2855                         TelnetRequest(TN_WONT, option);
2856                         break;
2857                     }
2858                     break;
2859                   case TN_DONT:
2860                     if (appData.debugMode)
2861                       fprintf(debugFP, "\n<DONT ");
2862                     switch (option = (unsigned char) buf[++i]) {
2863                       default:
2864                         if (appData.debugMode)
2865                           fprintf(debugFP, "%d ", option);
2866                         /* Whatever this is, we are already not doing
2867                            it, because we never agree to do anything
2868                            non-default, so according to the protocol
2869                            rules, we don't reply. */
2870                         break;
2871                     }
2872                     break;
2873                   case TN_IAC:
2874                     if (appData.debugMode)
2875                       fprintf(debugFP, "\n<IAC ");
2876                     /* Doubled IAC; pass it through */
2877                     i--;
2878                     break;
2879                   default:
2880                     if (appData.debugMode)
2881                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2882                     /* Drop all other telnet commands on the floor */
2883                     break;
2884                 }
2885                 if (oldi > next_out)
2886                   SendToPlayer(&buf[next_out], oldi - next_out);
2887                 if (++i > next_out)
2888                   next_out = i;
2889                 continue;
2890             }
2891
2892             /* OK, this at least will *usually* work */
2893             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2894                 loggedOn = TRUE;
2895             }
2896
2897             if (loggedOn && !intfSet) {
2898                 if (ics_type == ICS_ICC) {
2899                   snprintf(str, MSG_SIZ,
2900                           "/set-quietly interface %s\n/set-quietly style 12\n",
2901                           programVersion);
2902                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2903                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2904                 } else if (ics_type == ICS_CHESSNET) {
2905                   snprintf(str, MSG_SIZ, "/style 12\n");
2906                 } else {
2907                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2908                   strcat(str, programVersion);
2909                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2910                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2911                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2912 #ifdef WIN32
2913                   strcat(str, "$iset nohighlight 1\n");
2914 #endif
2915                   strcat(str, "$iset lock 1\n$style 12\n");
2916                 }
2917                 SendToICS(str);
2918                 NotifyFrontendLogin();
2919                 intfSet = TRUE;
2920             }
2921
2922             if (started == STARTED_COMMENT) {
2923                 /* Accumulate characters in comment */
2924                 parse[parse_pos++] = buf[i];
2925                 if (buf[i] == '\n') {
2926                     parse[parse_pos] = NULLCHAR;
2927                     if(chattingPartner>=0) {
2928                         char mess[MSG_SIZ];
2929                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2930                         OutputChatMessage(chattingPartner, mess);
2931                         chattingPartner = -1;
2932                         next_out = i+1; // [HGM] suppress printing in ICS window
2933                     } else
2934                     if(!suppressKibitz) // [HGM] kibitz
2935                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2936                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2937                         int nrDigit = 0, nrAlph = 0, j;
2938                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2939                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2940                         parse[parse_pos] = NULLCHAR;
2941                         // try to be smart: if it does not look like search info, it should go to
2942                         // ICS interaction window after all, not to engine-output window.
2943                         for(j=0; j<parse_pos; j++) { // count letters and digits
2944                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2945                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2946                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2947                         }
2948                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2949                             int depth=0; float score;
2950                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2951                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2952                                 pvInfoList[forwardMostMove-1].depth = depth;
2953                                 pvInfoList[forwardMostMove-1].score = 100*score;
2954                             }
2955                             OutputKibitz(suppressKibitz, parse);
2956                         } else {
2957                             char tmp[MSG_SIZ];
2958                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2959                             SendToPlayer(tmp, strlen(tmp));
2960                         }
2961                         next_out = i+1; // [HGM] suppress printing in ICS window
2962                     }
2963                     started = STARTED_NONE;
2964                 } else {
2965                     /* Don't match patterns against characters in comment */
2966                     i++;
2967                     continue;
2968                 }
2969             }
2970             if (started == STARTED_CHATTER) {
2971                 if (buf[i] != '\n') {
2972                     /* Don't match patterns against characters in chatter */
2973                     i++;
2974                     continue;
2975                 }
2976                 started = STARTED_NONE;
2977                 if(suppressKibitz) next_out = i+1;
2978             }
2979
2980             /* Kludge to deal with rcmd protocol */
2981             if (firstTime && looking_at(buf, &i, "\001*")) {
2982                 DisplayFatalError(&buf[1], 0, 1);
2983                 continue;
2984             } else {
2985                 firstTime = FALSE;
2986             }
2987
2988             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2989                 ics_type = ICS_ICC;
2990                 ics_prefix = "/";
2991                 if (appData.debugMode)
2992                   fprintf(debugFP, "ics_type %d\n", ics_type);
2993                 continue;
2994             }
2995             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2996                 ics_type = ICS_FICS;
2997                 ics_prefix = "$";
2998                 if (appData.debugMode)
2999                   fprintf(debugFP, "ics_type %d\n", ics_type);
3000                 continue;
3001             }
3002             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3003                 ics_type = ICS_CHESSNET;
3004                 ics_prefix = "/";
3005                 if (appData.debugMode)
3006                   fprintf(debugFP, "ics_type %d\n", ics_type);
3007                 continue;
3008             }
3009
3010             if (!loggedOn &&
3011                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3012                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3013                  looking_at(buf, &i, "will be \"*\""))) {
3014               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3015               continue;
3016             }
3017
3018             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3019               char buf[MSG_SIZ];
3020               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3021               DisplayIcsInteractionTitle(buf);
3022               have_set_title = TRUE;
3023             }
3024
3025             /* skip finger notes */
3026             if (started == STARTED_NONE &&
3027                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3028                  (buf[i] == '1' && buf[i+1] == '0')) &&
3029                 buf[i+2] == ':' && buf[i+3] == ' ') {
3030               started = STARTED_CHATTER;
3031               i += 3;
3032               continue;
3033             }
3034
3035             oldi = i;
3036             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3037             if(appData.seekGraph) {
3038                 if(soughtPending && MatchSoughtLine(buf+i)) {
3039                     i = strstr(buf+i, "rated") - buf;
3040                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3041                     next_out = leftover_start = i;
3042                     started = STARTED_CHATTER;
3043                     suppressKibitz = TRUE;
3044                     continue;
3045                 }
3046                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3047                         && looking_at(buf, &i, "* ads displayed")) {
3048                     soughtPending = FALSE;
3049                     seekGraphUp = TRUE;
3050                     DrawSeekGraph();
3051                     continue;
3052                 }
3053                 if(appData.autoRefresh) {
3054                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3055                         int s = (ics_type == ICS_ICC); // ICC format differs
3056                         if(seekGraphUp)
3057                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3058                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3059                         looking_at(buf, &i, "*% "); // eat prompt
3060                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3061                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3062                         next_out = i; // suppress
3063                         continue;
3064                     }
3065                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3066                         char *p = star_match[0];
3067                         while(*p) {
3068                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3069                             while(*p && *p++ != ' '); // next
3070                         }
3071                         looking_at(buf, &i, "*% "); // eat prompt
3072                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3073                         next_out = i;
3074                         continue;
3075                     }
3076                 }
3077             }
3078
3079             /* skip formula vars */
3080             if (started == STARTED_NONE &&
3081                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3082               started = STARTED_CHATTER;
3083               i += 3;
3084               continue;
3085             }
3086
3087             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3088             if (appData.autoKibitz && started == STARTED_NONE &&
3089                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3090                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3091                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3092                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3093                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3094                         suppressKibitz = TRUE;
3095                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096                         next_out = i;
3097                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3098                                 && (gameMode == IcsPlayingWhite)) ||
3099                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3100                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3101                             started = STARTED_CHATTER; // own kibitz we simply discard
3102                         else {
3103                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3104                             parse_pos = 0; parse[0] = NULLCHAR;
3105                             savingComment = TRUE;
3106                             suppressKibitz = gameMode != IcsObserving ? 2 :
3107                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3108                         }
3109                         continue;
3110                 } else
3111                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3112                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3113                          && atoi(star_match[0])) {
3114                     // suppress the acknowledgements of our own autoKibitz
3115                     char *p;
3116                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3118                     SendToPlayer(star_match[0], strlen(star_match[0]));
3119                     if(looking_at(buf, &i, "*% ")) // eat prompt
3120                         suppressKibitz = FALSE;
3121                     next_out = i;
3122                     continue;
3123                 }
3124             } // [HGM] kibitz: end of patch
3125
3126             // [HGM] chat: intercept tells by users for which we have an open chat window
3127             channel = -1;
3128             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3129                                            looking_at(buf, &i, "* whispers:") ||
3130                                            looking_at(buf, &i, "* kibitzes:") ||
3131                                            looking_at(buf, &i, "* shouts:") ||
3132                                            looking_at(buf, &i, "* c-shouts:") ||
3133                                            looking_at(buf, &i, "--> * ") ||
3134                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3135                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3136                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3137                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3138                 int p;
3139                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3140                 chattingPartner = -1;
3141
3142                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3143                 for(p=0; p<MAX_CHAT; p++) {
3144                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3145                     talker[0] = '['; strcat(talker, "] ");
3146                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3147                     chattingPartner = p; break;
3148                     }
3149                 } else
3150                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3151                 for(p=0; p<MAX_CHAT; p++) {
3152                     if(!strcmp("kibitzes", chatPartner[p])) {
3153                         talker[0] = '['; strcat(talker, "] ");
3154                         chattingPartner = p; break;
3155                     }
3156                 } else
3157                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3158                 for(p=0; p<MAX_CHAT; p++) {
3159                     if(!strcmp("whispers", chatPartner[p])) {
3160                         talker[0] = '['; strcat(talker, "] ");
3161                         chattingPartner = p; break;
3162                     }
3163                 } else
3164                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3165                   if(buf[i-8] == '-' && buf[i-3] == 't')
3166                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3167                     if(!strcmp("c-shouts", chatPartner[p])) {
3168                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3169                         chattingPartner = p; break;
3170                     }
3171                   }
3172                   if(chattingPartner < 0)
3173                   for(p=0; p<MAX_CHAT; p++) {
3174                     if(!strcmp("shouts", chatPartner[p])) {
3175                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3176                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3177                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3178                         chattingPartner = p; break;
3179                     }
3180                   }
3181                 }
3182                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3183                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3184                     talker[0] = 0; Colorize(ColorTell, FALSE);
3185                     chattingPartner = p; break;
3186                 }
3187                 if(chattingPartner<0) i = oldi; else {
3188                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3189                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3190                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191                     started = STARTED_COMMENT;
3192                     parse_pos = 0; parse[0] = NULLCHAR;
3193                     savingComment = 3 + chattingPartner; // counts as TRUE
3194                     suppressKibitz = TRUE;
3195                     continue;
3196                 }
3197             } // [HGM] chat: end of patch
3198
3199           backup = i;
3200             if (appData.zippyTalk || appData.zippyPlay) {
3201                 /* [DM] Backup address for color zippy lines */
3202 #if ZIPPY
3203                if (loggedOn == TRUE)
3204                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3205                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3206 #endif
3207             } // [DM] 'else { ' deleted
3208                 if (
3209                     /* Regular tells and says */
3210                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3211                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3212                     looking_at(buf, &i, "* says: ") ||
3213                     /* Don't color "message" or "messages" output */
3214                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3215                     looking_at(buf, &i, "*. * at *:*: ") ||
3216                     looking_at(buf, &i, "--* (*:*): ") ||
3217                     /* Message notifications (same color as tells) */
3218                     looking_at(buf, &i, "* has left a message ") ||
3219                     looking_at(buf, &i, "* just sent you a message:\n") ||
3220                     /* Whispers and kibitzes */
3221                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3222                     looking_at(buf, &i, "* kibitzes: ") ||
3223                     /* Channel tells */
3224                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3225
3226                   if (tkind == 1 && strchr(star_match[0], ':')) {
3227                       /* Avoid "tells you:" spoofs in channels */
3228                      tkind = 3;
3229                   }
3230                   if (star_match[0][0] == NULLCHAR ||
3231                       strchr(star_match[0], ' ') ||
3232                       (tkind == 3 && strchr(star_match[1], ' '))) {
3233                     /* Reject bogus matches */
3234                     i = oldi;
3235                   } else {
3236                     if (appData.colorize) {
3237                       if (oldi > next_out) {
3238                         SendToPlayer(&buf[next_out], oldi - next_out);
3239                         next_out = oldi;
3240                       }
3241                       switch (tkind) {
3242                       case 1:
3243                         Colorize(ColorTell, FALSE);
3244                         curColor = ColorTell;
3245                         break;
3246                       case 2:
3247                         Colorize(ColorKibitz, FALSE);
3248                         curColor = ColorKibitz;
3249                         break;
3250                       case 3:
3251                         p = strrchr(star_match[1], '(');
3252                         if (p == NULL) {
3253                           p = star_match[1];
3254                         } else {
3255                           p++;
3256                         }
3257                         if (atoi(p) == 1) {
3258                           Colorize(ColorChannel1, FALSE);
3259                           curColor = ColorChannel1;
3260                         } else {
3261                           Colorize(ColorChannel, FALSE);
3262                           curColor = ColorChannel;
3263                         }
3264                         break;
3265                       case 5:
3266                         curColor = ColorNormal;
3267                         break;
3268                       }
3269                     }
3270                     if (started == STARTED_NONE && appData.autoComment &&
3271                         (gameMode == IcsObserving ||
3272                          gameMode == IcsPlayingWhite ||
3273                          gameMode == IcsPlayingBlack)) {
3274                       parse_pos = i - oldi;
3275                       memcpy(parse, &buf[oldi], parse_pos);
3276                       parse[parse_pos] = NULLCHAR;
3277                       started = STARTED_COMMENT;
3278                       savingComment = TRUE;
3279                     } else {
3280                       started = STARTED_CHATTER;
3281                       savingComment = FALSE;
3282                     }
3283                     loggedOn = TRUE;
3284                     continue;
3285                   }
3286                 }
3287
3288                 if (looking_at(buf, &i, "* s-shouts: ") ||
3289                     looking_at(buf, &i, "* c-shouts: ")) {
3290                     if (appData.colorize) {
3291                         if (oldi > next_out) {
3292                             SendToPlayer(&buf[next_out], oldi - next_out);
3293                             next_out = oldi;
3294                         }
3295                         Colorize(ColorSShout, FALSE);
3296                         curColor = ColorSShout;
3297                     }
3298                     loggedOn = TRUE;
3299                     started = STARTED_CHATTER;
3300                     continue;
3301                 }
3302
3303                 if (looking_at(buf, &i, "--->")) {
3304                     loggedOn = TRUE;
3305                     continue;
3306                 }
3307
3308                 if (looking_at(buf, &i, "* shouts: ") ||
3309                     looking_at(buf, &i, "--> ")) {
3310                     if (appData.colorize) {
3311                         if (oldi > next_out) {
3312                             SendToPlayer(&buf[next_out], oldi - next_out);
3313                             next_out = oldi;
3314                         }
3315                         Colorize(ColorShout, FALSE);
3316                         curColor = ColorShout;
3317                     }
3318                     loggedOn = TRUE;
3319                     started = STARTED_CHATTER;
3320                     continue;
3321                 }
3322
3323                 if (looking_at( buf, &i, "Challenge:")) {
3324                     if (appData.colorize) {
3325                         if (oldi > next_out) {
3326                             SendToPlayer(&buf[next_out], oldi - next_out);
3327                             next_out = oldi;
3328                         }
3329                         Colorize(ColorChallenge, FALSE);
3330                         curColor = ColorChallenge;
3331                     }
3332                     loggedOn = TRUE;
3333                     continue;
3334                 }
3335
3336                 if (looking_at(buf, &i, "* offers you") ||
3337                     looking_at(buf, &i, "* offers to be") ||
3338                     looking_at(buf, &i, "* would like to") ||
3339                     looking_at(buf, &i, "* requests to") ||
3340                     looking_at(buf, &i, "Your opponent offers") ||
3341                     looking_at(buf, &i, "Your opponent requests")) {
3342
3343                     if (appData.colorize) {
3344                         if (oldi > next_out) {
3345                             SendToPlayer(&buf[next_out], oldi - next_out);
3346                             next_out = oldi;
3347                         }
3348                         Colorize(ColorRequest, FALSE);
3349                         curColor = ColorRequest;
3350                     }
3351                     continue;
3352                 }
3353
3354                 if (looking_at(buf, &i, "* (*) seeking")) {
3355                     if (appData.colorize) {
3356                         if (oldi > next_out) {
3357                             SendToPlayer(&buf[next_out], oldi - next_out);
3358                             next_out = oldi;
3359                         }
3360                         Colorize(ColorSeek, FALSE);
3361                         curColor = ColorSeek;
3362                     }
3363                     continue;
3364             }
3365
3366           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3367
3368             if (looking_at(buf, &i, "\\   ")) {
3369                 if (prevColor != ColorNormal) {
3370                     if (oldi > next_out) {
3371                         SendToPlayer(&buf[next_out], oldi - next_out);
3372                         next_out = oldi;
3373                     }
3374                     Colorize(prevColor, TRUE);
3375                     curColor = prevColor;
3376                 }
3377                 if (savingComment) {
3378                     parse_pos = i - oldi;
3379                     memcpy(parse, &buf[oldi], parse_pos);
3380                     parse[parse_pos] = NULLCHAR;
3381                     started = STARTED_COMMENT;
3382                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3383                         chattingPartner = savingComment - 3; // kludge to remember the box
3384                 } else {
3385                     started = STARTED_CHATTER;
3386                 }
3387                 continue;
3388             }
3389
3390             if (looking_at(buf, &i, "Black Strength :") ||
3391                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3392                 looking_at(buf, &i, "<10>") ||
3393                 looking_at(buf, &i, "#@#")) {
3394                 /* Wrong board style */
3395                 loggedOn = TRUE;
3396                 SendToICS(ics_prefix);
3397                 SendToICS("set style 12\n");
3398                 SendToICS(ics_prefix);
3399                 SendToICS("refresh\n");
3400                 continue;
3401             }
3402
3403             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3404                 ICSInitScript();
3405                 have_sent_ICS_logon = 1;
3406                 continue;
3407             }
3408
3409             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3410                 (looking_at(buf, &i, "\n<12> ") ||
3411                  looking_at(buf, &i, "<12> "))) {
3412                 loggedOn = TRUE;
3413                 if (oldi > next_out) {
3414                     SendToPlayer(&buf[next_out], oldi - next_out);
3415                 }
3416                 next_out = i;
3417                 started = STARTED_BOARD;
3418                 parse_pos = 0;
3419                 continue;
3420             }
3421
3422             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3423                 looking_at(buf, &i, "<b1> ")) {
3424                 if (oldi > next_out) {
3425                     SendToPlayer(&buf[next_out], oldi - next_out);
3426                 }
3427                 next_out = i;
3428                 started = STARTED_HOLDINGS;
3429                 parse_pos = 0;
3430                 continue;
3431             }
3432
3433             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3434                 loggedOn = TRUE;
3435                 /* Header for a move list -- first line */
3436
3437                 switch (ics_getting_history) {
3438                   case H_FALSE:
3439                     switch (gameMode) {
3440                       case IcsIdle:
3441                       case BeginningOfGame:
3442                         /* User typed "moves" or "oldmoves" while we
3443                            were idle.  Pretend we asked for these
3444                            moves and soak them up so user can step
3445                            through them and/or save them.
3446                            */
3447                         Reset(FALSE, TRUE);
3448                         gameMode = IcsObserving;
3449                         ModeHighlight();
3450                         ics_gamenum = -1;
3451                         ics_getting_history = H_GOT_UNREQ_HEADER;
3452                         break;
3453                       case EditGame: /*?*/
3454                       case EditPosition: /*?*/
3455                         /* Should above feature work in these modes too? */
3456                         /* For now it doesn't */
3457                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3458                         break;
3459                       default:
3460                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3461                         break;
3462                     }
3463                     break;
3464                   case H_REQUESTED:
3465                     /* Is this the right one? */
3466                     if (gameInfo.white && gameInfo.black &&
3467                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3468                         strcmp(gameInfo.black, star_match[2]) == 0) {
3469                         /* All is well */
3470                         ics_getting_history = H_GOT_REQ_HEADER;
3471                     }
3472                     break;
3473                   case H_GOT_REQ_HEADER:
3474                   case H_GOT_UNREQ_HEADER:
3475                   case H_GOT_UNWANTED_HEADER:
3476                   case H_GETTING_MOVES:
3477                     /* Should not happen */
3478                     DisplayError(_("Error gathering move list: two headers"), 0);
3479                     ics_getting_history = H_FALSE;
3480                     break;
3481                 }
3482
3483                 /* Save player ratings into gameInfo if needed */
3484                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3485                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3486                     (gameInfo.whiteRating == -1 ||
3487                      gameInfo.blackRating == -1)) {
3488
3489                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3490                     gameInfo.blackRating = string_to_rating(star_match[3]);
3491                     if (appData.debugMode)
3492                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3493                               gameInfo.whiteRating, gameInfo.blackRating);
3494                 }
3495                 continue;
3496             }
3497
3498             if (looking_at(buf, &i,
3499               "* * match, initial time: * minute*, increment: * second")) {
3500                 /* Header for a move list -- second line */
3501                 /* Initial board will follow if this is a wild game */
3502                 if (gameInfo.event != NULL) free(gameInfo.event);
3503                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3504                 gameInfo.event = StrSave(str);
3505                 /* [HGM] we switched variant. Translate boards if needed. */
3506                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3507                 continue;
3508             }
3509
3510             if (looking_at(buf, &i, "Move  ")) {
3511                 /* Beginning of a move list */
3512                 switch (ics_getting_history) {
3513                   case H_FALSE:
3514                     /* Normally should not happen */
3515                     /* Maybe user hit reset while we were parsing */
3516                     break;
3517                   case H_REQUESTED:
3518                     /* Happens if we are ignoring a move list that is not
3519                      * the one we just requested.  Common if the user
3520                      * tries to observe two games without turning off
3521                      * getMoveList */
3522                     break;
3523                   case H_GETTING_MOVES:
3524                     /* Should not happen */
3525                     DisplayError(_("Error gathering move list: nested"), 0);
3526                     ics_getting_history = H_FALSE;
3527                     break;
3528                   case H_GOT_REQ_HEADER:
3529                     ics_getting_history = H_GETTING_MOVES;
3530                     started = STARTED_MOVES;
3531                     parse_pos = 0;
3532                     if (oldi > next_out) {
3533                         SendToPlayer(&buf[next_out], oldi - next_out);
3534                     }
3535                     break;
3536                   case H_GOT_UNREQ_HEADER:
3537                     ics_getting_history = H_GETTING_MOVES;
3538                     started = STARTED_MOVES_NOHIDE;
3539                     parse_pos = 0;
3540                     break;
3541                   case H_GOT_UNWANTED_HEADER:
3542                     ics_getting_history = H_FALSE;
3543                     break;
3544                 }
3545                 continue;
3546             }
3547
3548             if (looking_at(buf, &i, "% ") ||
3549                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3550                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3551                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3552                     soughtPending = FALSE;
3553                     seekGraphUp = TRUE;
3554                     DrawSeekGraph();
3555                 }
3556                 if(suppressKibitz) next_out = i;
3557                 savingComment = FALSE;
3558                 suppressKibitz = 0;
3559                 switch (started) {
3560                   case STARTED_MOVES:
3561                   case STARTED_MOVES_NOHIDE:
3562                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3563                     parse[parse_pos + i - oldi] = NULLCHAR;
3564                     ParseGameHistory(parse);
3565 #if ZIPPY
3566                     if (appData.zippyPlay && first.initDone) {
3567                         FeedMovesToProgram(&first, forwardMostMove);
3568                         if (gameMode == IcsPlayingWhite) {
3569                             if (WhiteOnMove(forwardMostMove)) {
3570                                 if (first.sendTime) {
3571                                   if (first.useColors) {
3572                                     SendToProgram("black\n", &first);
3573                                   }
3574                                   SendTimeRemaining(&first, TRUE);
3575                                 }
3576                                 if (first.useColors) {
3577                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3578                                 }
3579                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3580                                 first.maybeThinking = TRUE;
3581                             } else {
3582                                 if (first.usePlayother) {
3583                                   if (first.sendTime) {
3584                                     SendTimeRemaining(&first, TRUE);
3585                                   }
3586                                   SendToProgram("playother\n", &first);
3587                                   firstMove = FALSE;
3588                                 } else {
3589                                   firstMove = TRUE;
3590                                 }
3591                             }
3592                         } else if (gameMode == IcsPlayingBlack) {
3593                             if (!WhiteOnMove(forwardMostMove)) {
3594                                 if (first.sendTime) {
3595                                   if (first.useColors) {
3596                                     SendToProgram("white\n", &first);
3597                                   }
3598                                   SendTimeRemaining(&first, FALSE);
3599                                 }
3600                                 if (first.useColors) {
3601                                   SendToProgram("black\n", &first);
3602                                 }
3603                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3604                                 first.maybeThinking = TRUE;
3605                             } else {
3606                                 if (first.usePlayother) {
3607                                   if (first.sendTime) {
3608                                     SendTimeRemaining(&first, FALSE);
3609                                   }
3610                                   SendToProgram("playother\n", &first);
3611                                   firstMove = FALSE;
3612                                 } else {
3613                                   firstMove = TRUE;
3614                                 }
3615                             }
3616                         }
3617                     }
3618 #endif
3619                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3620                         /* Moves came from oldmoves or moves command
3621                            while we weren't doing anything else.
3622                            */
3623                         currentMove = forwardMostMove;
3624                         ClearHighlights();/*!!could figure this out*/
3625                         flipView = appData.flipView;
3626                         DrawPosition(TRUE, boards[currentMove]);
3627                         DisplayBothClocks();
3628                         snprintf(str, MSG_SIZ, "%s vs. %s",
3629                                 gameInfo.white, gameInfo.black);
3630                         DisplayTitle(str);
3631                         gameMode = IcsIdle;
3632                     } else {
3633                         /* Moves were history of an active game */
3634                         if (gameInfo.resultDetails != NULL) {
3635                             free(gameInfo.resultDetails);
3636                             gameInfo.resultDetails = NULL;
3637                         }
3638                     }
3639                     HistorySet(parseList, backwardMostMove,
3640                                forwardMostMove, currentMove-1);
3641                     DisplayMove(currentMove - 1);
3642                     if (started == STARTED_MOVES) next_out = i;
3643                     started = STARTED_NONE;
3644                     ics_getting_history = H_FALSE;
3645                     break;
3646
3647                   case STARTED_OBSERVE:
3648                     started = STARTED_NONE;
3649                     SendToICS(ics_prefix);
3650                     SendToICS("refresh\n");
3651                     break;
3652
3653                   default:
3654                     break;
3655                 }
3656                 if(bookHit) { // [HGM] book: simulate book reply
3657                     static char bookMove[MSG_SIZ]; // a bit generous?
3658
3659                     programStats.nodes = programStats.depth = programStats.time =
3660                     programStats.score = programStats.got_only_move = 0;
3661                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3662
3663                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3664                     strcat(bookMove, bookHit);
3665                     HandleMachineMove(bookMove, &first);
3666                 }
3667                 continue;
3668             }
3669
3670             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3671                  started == STARTED_HOLDINGS ||
3672                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3673                 /* Accumulate characters in move list or board */
3674                 parse[parse_pos++] = buf[i];
3675             }
3676
3677             /* Start of game messages.  Mostly we detect start of game
3678                when the first board image arrives.  On some versions
3679                of the ICS, though, we need to do a "refresh" after starting
3680                to observe in order to get the current board right away. */
3681             if (looking_at(buf, &i, "Adding game * to observation list")) {
3682                 started = STARTED_OBSERVE;
3683                 continue;
3684             }
3685
3686             /* Handle auto-observe */
3687             if (appData.autoObserve &&
3688                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3689                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3690                 char *player;
3691                 /* Choose the player that was highlighted, if any. */
3692                 if (star_match[0][0] == '\033' ||
3693                     star_match[1][0] != '\033') {
3694                     player = star_match[0];
3695                 } else {
3696                     player = star_match[2];
3697                 }
3698                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3699                         ics_prefix, StripHighlightAndTitle(player));
3700                 SendToICS(str);
3701
3702                 /* Save ratings from notify string */
3703                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3704                 player1Rating = string_to_rating(star_match[1]);
3705                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3706                 player2Rating = string_to_rating(star_match[3]);
3707
3708                 if (appData.debugMode)
3709                   fprintf(debugFP,
3710                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3711                           player1Name, player1Rating,
3712                           player2Name, player2Rating);
3713
3714                 continue;
3715             }
3716
3717             /* Deal with automatic examine mode after a game,
3718                and with IcsObserving -> IcsExamining transition */
3719             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3720                 looking_at(buf, &i, "has made you an examiner of game *")) {
3721
3722                 int gamenum = atoi(star_match[0]);
3723                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3724                     gamenum == ics_gamenum) {
3725                     /* We were already playing or observing this game;
3726                        no need to refetch history */
3727                     gameMode = IcsExamining;
3728                     if (pausing) {
3729                         pauseExamForwardMostMove = forwardMostMove;
3730                     } else if (currentMove < forwardMostMove) {
3731                         ForwardInner(forwardMostMove);
3732                     }
3733                 } else {
3734                     /* I don't think this case really can happen */
3735                     SendToICS(ics_prefix);
3736                     SendToICS("refresh\n");
3737                 }
3738                 continue;
3739             }
3740
3741             /* Error messages */
3742 //          if (ics_user_moved) {
3743             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3744                 if (looking_at(buf, &i, "Illegal move") ||
3745                     looking_at(buf, &i, "Not a legal move") ||
3746                     looking_at(buf, &i, "Your king is in check") ||
3747                     looking_at(buf, &i, "It isn't your turn") ||
3748                     looking_at(buf, &i, "It is not your move")) {
3749                     /* Illegal move */
3750                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3751                         currentMove = forwardMostMove-1;
3752                         DisplayMove(currentMove - 1); /* before DMError */
3753                         DrawPosition(FALSE, boards[currentMove]);
3754                         SwitchClocks(forwardMostMove-1); // [HGM] race
3755                         DisplayBothClocks();
3756                     }
3757                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3758                     ics_user_moved = 0;
3759                     continue;
3760                 }
3761             }
3762
3763             if (looking_at(buf, &i, "still have time") ||
3764                 looking_at(buf, &i, "not out of time") ||
3765                 looking_at(buf, &i, "either player is out of time") ||
3766                 looking_at(buf, &i, "has timeseal; checking")) {
3767                 /* We must have called his flag a little too soon */
3768                 whiteFlag = blackFlag = FALSE;
3769                 continue;
3770             }
3771
3772             if (looking_at(buf, &i, "added * seconds to") ||
3773                 looking_at(buf, &i, "seconds were added to")) {
3774                 /* Update the clocks */
3775                 SendToICS(ics_prefix);
3776                 SendToICS("refresh\n");
3777                 continue;
3778             }
3779
3780             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3781                 ics_clock_paused = TRUE;
3782                 StopClocks();
3783                 continue;
3784             }
3785
3786             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3787                 ics_clock_paused = FALSE;
3788                 StartClocks();
3789                 continue;
3790             }
3791
3792             /* Grab player ratings from the Creating: message.
3793                Note we have to check for the special case when
3794                the ICS inserts things like [white] or [black]. */
3795             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3796                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3797                 /* star_matches:
3798                    0    player 1 name (not necessarily white)
3799                    1    player 1 rating
3800                    2    empty, white, or black (IGNORED)
3801                    3    player 2 name (not necessarily black)
3802                    4    player 2 rating
3803
3804                    The names/ratings are sorted out when the game
3805                    actually starts (below).
3806                 */
3807                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3808                 player1Rating = string_to_rating(star_match[1]);
3809                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3810                 player2Rating = string_to_rating(star_match[4]);
3811
3812                 if (appData.debugMode)
3813                   fprintf(debugFP,
3814                           "Ratings from 'Creating:' %s %d, %s %d\n",
3815                           player1Name, player1Rating,
3816                           player2Name, player2Rating);
3817
3818                 continue;
3819             }
3820
3821             /* Improved generic start/end-of-game messages */
3822             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3823                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3824                 /* If tkind == 0: */
3825                 /* star_match[0] is the game number */
3826                 /*           [1] is the white player's name */
3827                 /*           [2] is the black player's name */
3828                 /* For end-of-game: */
3829                 /*           [3] is the reason for the game end */
3830                 /*           [4] is a PGN end game-token, preceded by " " */
3831                 /* For start-of-game: */
3832                 /*           [3] begins with "Creating" or "Continuing" */
3833                 /*           [4] is " *" or empty (don't care). */
3834                 int gamenum = atoi(star_match[0]);
3835                 char *whitename, *blackname, *why, *endtoken;
3836                 ChessMove endtype = EndOfFile;
3837
3838                 if (tkind == 0) {
3839                   whitename = star_match[1];
3840                   blackname = star_match[2];
3841                   why = star_match[3];
3842                   endtoken = star_match[4];
3843                 } else {
3844                   whitename = star_match[1];
3845                   blackname = star_match[3];
3846                   why = star_match[5];
3847                   endtoken = star_match[6];
3848                 }
3849
3850                 /* Game start messages */
3851                 if (strncmp(why, "Creating ", 9) == 0 ||
3852                     strncmp(why, "Continuing ", 11) == 0) {
3853                     gs_gamenum = gamenum;
3854                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3855                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3856 #if ZIPPY
3857                     if (appData.zippyPlay) {
3858                         ZippyGameStart(whitename, blackname);
3859                     }
3860 #endif /*ZIPPY*/
3861                     partnerBoardValid = FALSE; // [HGM] bughouse
3862                     continue;
3863                 }
3864
3865                 /* Game end messages */
3866                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3867                     ics_gamenum != gamenum) {
3868                     continue;
3869                 }
3870                 while (endtoken[0] == ' ') endtoken++;
3871                 switch (endtoken[0]) {
3872                   case '*':
3873                   default:
3874                     endtype = GameUnfinished;
3875                     break;
3876                   case '0':
3877                     endtype = BlackWins;
3878                     break;
3879                   case '1':
3880                     if (endtoken[1] == '/')
3881                       endtype = GameIsDrawn;
3882                     else
3883                       endtype = WhiteWins;
3884                     break;
3885                 }
3886                 GameEnds(endtype, why, GE_ICS);
3887 #if ZIPPY
3888                 if (appData.zippyPlay && first.initDone) {
3889                     ZippyGameEnd(endtype, why);
3890                     if (first.pr == NULL) {
3891                       /* Start the next process early so that we'll
3892                          be ready for the next challenge */
3893                       StartChessProgram(&first);
3894                     }
3895                     /* Send "new" early, in case this command takes
3896                        a long time to finish, so that we'll be ready
3897                        for the next challenge. */
3898                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3899                     Reset(TRUE, TRUE);
3900                 }
3901 #endif /*ZIPPY*/
3902                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3903                 continue;
3904             }
3905
3906             if (looking_at(buf, &i, "Removing game * from observation") ||
3907                 looking_at(buf, &i, "no longer observing game *") ||
3908                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3909                 if (gameMode == IcsObserving &&
3910                     atoi(star_match[0]) == ics_gamenum)
3911                   {
3912                       /* icsEngineAnalyze */
3913                       if (appData.icsEngineAnalyze) {
3914                             ExitAnalyzeMode();
3915                             ModeHighlight();
3916                       }
3917                       StopClocks();
3918                       gameMode = IcsIdle;
3919                       ics_gamenum = -1;
3920                       ics_user_moved = FALSE;
3921                   }
3922                 continue;
3923             }
3924
3925             if (looking_at(buf, &i, "no longer examining game *")) {
3926                 if (gameMode == IcsExamining &&
3927                     atoi(star_match[0]) == ics_gamenum)
3928                   {
3929                       gameMode = IcsIdle;
3930                       ics_gamenum = -1;
3931                       ics_user_moved = FALSE;
3932                   }
3933                 continue;
3934             }
3935
3936             /* Advance leftover_start past any newlines we find,
3937                so only partial lines can get reparsed */
3938             if (looking_at(buf, &i, "\n")) {
3939                 prevColor = curColor;
3940                 if (curColor != ColorNormal) {
3941                     if (oldi > next_out) {
3942                         SendToPlayer(&buf[next_out], oldi - next_out);
3943                         next_out = oldi;
3944                     }
3945                     Colorize(ColorNormal, FALSE);
3946                     curColor = ColorNormal;
3947                 }
3948                 if (started == STARTED_BOARD) {
3949                     started = STARTED_NONE;
3950                     parse[parse_pos] = NULLCHAR;
3951                     ParseBoard12(parse);
3952                     ics_user_moved = 0;
3953
3954                     /* Send premove here */
3955                     if (appData.premove) {
3956                       char str[MSG_SIZ];
3957                       if (currentMove == 0 &&
3958                           gameMode == IcsPlayingWhite &&
3959                           appData.premoveWhite) {
3960                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3961                         if (appData.debugMode)
3962                           fprintf(debugFP, "Sending premove:\n");
3963                         SendToICS(str);
3964                       } else if (currentMove == 1 &&
3965                                  gameMode == IcsPlayingBlack &&
3966                                  appData.premoveBlack) {
3967                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3968                         if (appData.debugMode)
3969                           fprintf(debugFP, "Sending premove:\n");
3970                         SendToICS(str);
3971                       } else if (gotPremove) {
3972                         gotPremove = 0;
3973                         ClearPremoveHighlights();
3974                         if (appData.debugMode)
3975                           fprintf(debugFP, "Sending premove:\n");
3976                           UserMoveEvent(premoveFromX, premoveFromY,
3977                                         premoveToX, premoveToY,
3978                                         premovePromoChar);
3979                       }
3980                     }
3981
3982                     /* Usually suppress following prompt */
3983                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3984                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3985                         if (looking_at(buf, &i, "*% ")) {
3986                             savingComment = FALSE;
3987                             suppressKibitz = 0;
3988                         }
3989                     }
3990                     next_out = i;
3991                 } else if (started == STARTED_HOLDINGS) {
3992                     int gamenum;
3993                     char new_piece[MSG_SIZ];
3994                     started = STARTED_NONE;
3995                     parse[parse_pos] = NULLCHAR;
3996                     if (appData.debugMode)
3997                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3998                                                         parse, currentMove);
3999                     if (sscanf(parse, " game %d", &gamenum) == 1) {
4000                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4001                         if (gameInfo.variant == VariantNormal) {
4002                           /* [HGM] We seem to switch variant during a game!
4003                            * Presumably no holdings were displayed, so we have
4004                            * to move the position two files to the right to
4005                            * create room for them!
4006                            */
4007                           VariantClass newVariant;
4008                           switch(gameInfo.boardWidth) { // base guess on board width
4009                                 case 9:  newVariant = VariantShogi; break;
4010                                 case 10: newVariant = VariantGreat; break;
4011                                 default: newVariant = VariantCrazyhouse; break;
4012                           }
4013                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4014                           /* Get a move list just to see the header, which
4015                              will tell us whether this is really bug or zh */
4016                           if (ics_getting_history == H_FALSE) {
4017                             ics_getting_history = H_REQUESTED;
4018                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4019                             SendToICS(str);
4020                           }
4021                         }
4022                         new_piece[0] = NULLCHAR;
4023                         sscanf(parse, "game %d white [%s black [%s <- %s",
4024                                &gamenum, white_holding, black_holding,
4025                                new_piece);
4026                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4027                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4028                         /* [HGM] copy holdings to board holdings area */
4029                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4030                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4031                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4032 #if ZIPPY
4033                         if (appData.zippyPlay && first.initDone) {
4034                             ZippyHoldings(white_holding, black_holding,
4035                                           new_piece);
4036                         }
4037 #endif /*ZIPPY*/
4038                         if (tinyLayout || smallLayout) {
4039                             char wh[16], bh[16];
4040                             PackHolding(wh, white_holding);
4041                             PackHolding(bh, black_holding);
4042                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4043                                     gameInfo.white, gameInfo.black);
4044                         } else {
4045                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4046                                     gameInfo.white, white_holding,
4047                                     gameInfo.black, black_holding);
4048                         }
4049                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4050                         DrawPosition(FALSE, boards[currentMove]);
4051                         DisplayTitle(str);
4052                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4053                         sscanf(parse, "game %d white [%s black [%s <- %s",
4054                                &gamenum, white_holding, black_holding,
4055                                new_piece);
4056                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4057                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4058                         /* [HGM] copy holdings to partner-board holdings area */
4059                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4060                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4061                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4062                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4063                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4064                       }
4065                     }
4066                     /* Suppress following prompt */
4067                     if (looking_at(buf, &i, "*% ")) {
4068                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4069                         savingComment = FALSE;
4070                         suppressKibitz = 0;
4071                     }
4072                     next_out = i;
4073                 }
4074                 continue;
4075             }
4076
4077             i++;                /* skip unparsed character and loop back */
4078         }
4079
4080         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4081 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4082 //          SendToPlayer(&buf[next_out], i - next_out);
4083             started != STARTED_HOLDINGS && leftover_start > next_out) {
4084             SendToPlayer(&buf[next_out], leftover_start - next_out);
4085             next_out = i;
4086         }
4087
4088         leftover_len = buf_len - leftover_start;
4089         /* if buffer ends with something we couldn't parse,
4090            reparse it after appending the next read */
4091
4092     } else if (count == 0) {
4093         RemoveInputSource(isr);
4094         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4095     } else {
4096         DisplayFatalError(_("Error reading from ICS"), error, 1);
4097     }
4098 }
4099
4100
4101 /* Board style 12 looks like this:
4102
4103    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
4104
4105  * The "<12> " is stripped before it gets to this routine.  The two
4106  * trailing 0's (flip state and clock ticking) are later addition, and
4107  * some chess servers may not have them, or may have only the first.
4108  * Additional trailing fields may be added in the future.
4109  */
4110
4111 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4112
4113 #define RELATION_OBSERVING_PLAYED    0
4114 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4115 #define RELATION_PLAYING_MYMOVE      1
4116 #define RELATION_PLAYING_NOTMYMOVE  -1
4117 #define RELATION_EXAMINING           2
4118 #define RELATION_ISOLATED_BOARD     -3
4119 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4120
4121 void
4122 ParseBoard12(string)
4123      char *string;
4124 {
4125     GameMode newGameMode;
4126     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4127     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4128     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4129     char to_play, board_chars[200];
4130     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4131     char black[32], white[32];
4132     Board board;
4133     int prevMove = currentMove;
4134     int ticking = 2;
4135     ChessMove moveType;
4136     int fromX, fromY, toX, toY;
4137     char promoChar;
4138     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4139     char *bookHit = NULL; // [HGM] book
4140     Boolean weird = FALSE, reqFlag = FALSE;
4141
4142     fromX = fromY = toX = toY = -1;
4143
4144     newGame = FALSE;
4145
4146     if (appData.debugMode)
4147       fprintf(debugFP, _("Parsing board: %s\n"), string);
4148
4149     move_str[0] = NULLCHAR;
4150     elapsed_time[0] = NULLCHAR;
4151     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4152         int  i = 0, j;
4153         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4154             if(string[i] == ' ') { ranks++; files = 0; }
4155             else files++;
4156             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4157             i++;
4158         }
4159         for(j = 0; j <i; j++) board_chars[j] = string[j];
4160         board_chars[i] = '\0';
4161         string += i + 1;
4162     }
4163     n = sscanf(string, PATTERN, &to_play, &double_push,
4164                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4165                &gamenum, white, black, &relation, &basetime, &increment,
4166                &white_stren, &black_stren, &white_time, &black_time,
4167                &moveNum, str, elapsed_time, move_str, &ics_flip,
4168                &ticking);
4169
4170     if (n < 21) {
4171         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4172         DisplayError(str, 0);
4173         return;
4174     }
4175
4176     /* Convert the move number to internal form */
4177     moveNum = (moveNum - 1) * 2;
4178     if (to_play == 'B') moveNum++;
4179     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4180       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4181                         0, 1);
4182       return;
4183     }
4184
4185     switch (relation) {
4186       case RELATION_OBSERVING_PLAYED:
4187       case RELATION_OBSERVING_STATIC:
4188         if (gamenum == -1) {
4189             /* Old ICC buglet */
4190             relation = RELATION_OBSERVING_STATIC;
4191         }
4192         newGameMode = IcsObserving;
4193         break;
4194       case RELATION_PLAYING_MYMOVE:
4195       case RELATION_PLAYING_NOTMYMOVE:
4196         newGameMode =
4197           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4198             IcsPlayingWhite : IcsPlayingBlack;
4199         break;
4200       case RELATION_EXAMINING:
4201         newGameMode = IcsExamining;
4202         break;
4203       case RELATION_ISOLATED_BOARD:
4204       default:
4205         /* Just display this board.  If user was doing something else,
4206            we will forget about it until the next board comes. */
4207         newGameMode = IcsIdle;
4208         break;
4209       case RELATION_STARTING_POSITION:
4210         newGameMode = gameMode;
4211         break;
4212     }
4213
4214     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4215          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4216       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4217       char *toSqr;
4218       for (k = 0; k < ranks; k++) {
4219         for (j = 0; j < files; j++)
4220           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4221         if(gameInfo.holdingsWidth > 1) {
4222              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4223              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4224         }
4225       }
4226       CopyBoard(partnerBoard, board);
4227       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4228         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4229         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4230       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4231       if(toSqr = strchr(str, '-')) {
4232         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4233         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4234       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4235       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4236       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4237       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4238       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4239       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4240                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4241       DisplayMessage(partnerStatus, "");
4242         partnerBoardValid = TRUE;
4243       return;
4244     }
4245
4246     /* Modify behavior for initial board display on move listing
4247        of wild games.
4248        */
4249     switch (ics_getting_history) {
4250       case H_FALSE:
4251       case H_REQUESTED:
4252         break;
4253       case H_GOT_REQ_HEADER:
4254       case H_GOT_UNREQ_HEADER:
4255         /* This is the initial position of the current game */
4256         gamenum = ics_gamenum;
4257         moveNum = 0;            /* old ICS bug workaround */
4258         if (to_play == 'B') {
4259           startedFromSetupPosition = TRUE;
4260           blackPlaysFirst = TRUE;
4261           moveNum = 1;
4262           if (forwardMostMove == 0) forwardMostMove = 1;
4263           if (backwardMostMove == 0) backwardMostMove = 1;
4264           if (currentMove == 0) currentMove = 1;
4265         }
4266         newGameMode = gameMode;
4267         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4268         break;
4269       case H_GOT_UNWANTED_HEADER:
4270         /* This is an initial board that we don't want */
4271         return;
4272       case H_GETTING_MOVES:
4273         /* Should not happen */
4274         DisplayError(_("Error gathering move list: extra board"), 0);
4275         ics_getting_history = H_FALSE;
4276         return;
4277     }
4278
4279    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4280                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4281      /* [HGM] We seem to have switched variant unexpectedly
4282       * Try to guess new variant from board size
4283       */
4284           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4285           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4286           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4287           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4288           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4289           if(!weird) newVariant = VariantNormal;
4290           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4291           /* Get a move list just to see the header, which
4292              will tell us whether this is really bug or zh */
4293           if (ics_getting_history == H_FALSE) {
4294             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4295             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4296             SendToICS(str);
4297           }
4298     }
4299
4300     /* Take action if this is the first board of a new game, or of a
4301        different game than is currently being displayed.  */
4302     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4303         relation == RELATION_ISOLATED_BOARD) {
4304
4305         /* Forget the old game and get the history (if any) of the new one */
4306         if (gameMode != BeginningOfGame) {
4307           Reset(TRUE, TRUE);
4308         }
4309         newGame = TRUE;
4310         if (appData.autoRaiseBoard) BoardToTop();
4311         prevMove = -3;
4312         if (gamenum == -1) {
4313             newGameMode = IcsIdle;
4314         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4315                    appData.getMoveList && !reqFlag) {
4316             /* Need to get game history */
4317             ics_getting_history = H_REQUESTED;
4318             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4319             SendToICS(str);
4320         }
4321
4322         /* Initially flip the board to have black on the bottom if playing
4323            black or if the ICS flip flag is set, but let the user change
4324            it with the Flip View button. */
4325         flipView = appData.autoFlipView ?
4326           (newGameMode == IcsPlayingBlack) || ics_flip :
4327           appData.flipView;
4328
4329         /* Done with values from previous mode; copy in new ones */
4330         gameMode = newGameMode;
4331         ModeHighlight();
4332         ics_gamenum = gamenum;
4333         if (gamenum == gs_gamenum) {
4334             int klen = strlen(gs_kind);
4335             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4336             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4337             gameInfo.event = StrSave(str);
4338         } else {
4339             gameInfo.event = StrSave("ICS game");
4340         }
4341         gameInfo.site = StrSave(appData.icsHost);
4342         gameInfo.date = PGNDate();
4343         gameInfo.round = StrSave("-");
4344         gameInfo.white = StrSave(white);
4345         gameInfo.black = StrSave(black);
4346         timeControl = basetime * 60 * 1000;
4347         timeControl_2 = 0;
4348         timeIncrement = increment * 1000;
4349         movesPerSession = 0;
4350         gameInfo.timeControl = TimeControlTagValue();
4351         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4352   if (appData.debugMode) {
4353     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4354     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4355     setbuf(debugFP, NULL);
4356   }
4357
4358         gameInfo.outOfBook = NULL;
4359
4360         /* Do we have the ratings? */
4361         if (strcmp(player1Name, white) == 0 &&
4362             strcmp(player2Name, black) == 0) {
4363             if (appData.debugMode)
4364               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4365                       player1Rating, player2Rating);
4366             gameInfo.whiteRating = player1Rating;
4367             gameInfo.blackRating = player2Rating;
4368         } else if (strcmp(player2Name, white) == 0 &&
4369                    strcmp(player1Name, black) == 0) {
4370             if (appData.debugMode)
4371               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4372                       player2Rating, player1Rating);
4373             gameInfo.whiteRating = player2Rating;
4374             gameInfo.blackRating = player1Rating;
4375         }
4376         player1Name[0] = player2Name[0] = NULLCHAR;
4377
4378         /* Silence shouts if requested */
4379         if (appData.quietPlay &&
4380             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4381             SendToICS(ics_prefix);
4382             SendToICS("set shout 0\n");
4383         }
4384     }
4385
4386     /* Deal with midgame name changes */
4387     if (!newGame) {
4388         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4389             if (gameInfo.white) free(gameInfo.white);
4390             gameInfo.white = StrSave(white);
4391         }
4392         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4393             if (gameInfo.black) free(gameInfo.black);
4394             gameInfo.black = StrSave(black);
4395         }
4396     }
4397
4398     /* Throw away game result if anything actually changes in examine mode */
4399     if (gameMode == IcsExamining && !newGame) {
4400         gameInfo.result = GameUnfinished;
4401         if (gameInfo.resultDetails != NULL) {
4402             free(gameInfo.resultDetails);
4403             gameInfo.resultDetails = NULL;
4404         }
4405     }
4406
4407     /* In pausing && IcsExamining mode, we ignore boards coming
4408        in if they are in a different variation than we are. */
4409     if (pauseExamInvalid) return;
4410     if (pausing && gameMode == IcsExamining) {
4411         if (moveNum <= pauseExamForwardMostMove) {
4412             pauseExamInvalid = TRUE;
4413             forwardMostMove = pauseExamForwardMostMove;
4414             return;
4415         }
4416     }
4417
4418   if (appData.debugMode) {
4419     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4420   }
4421     /* Parse the board */
4422     for (k = 0; k < ranks; k++) {
4423       for (j = 0; j < files; j++)
4424         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4425       if(gameInfo.holdingsWidth > 1) {
4426            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4427            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4428       }
4429     }
4430     CopyBoard(boards[moveNum], board);
4431     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4432     if (moveNum == 0) {
4433         startedFromSetupPosition =
4434           !CompareBoards(board, initialPosition);
4435         if(startedFromSetupPosition)
4436             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4437     }
4438
4439     /* [HGM] Set castling rights. Take the outermost Rooks,
4440        to make it also work for FRC opening positions. Note that board12
4441        is really defective for later FRC positions, as it has no way to
4442        indicate which Rook can castle if they are on the same side of King.
4443        For the initial position we grant rights to the outermost Rooks,
4444        and remember thos rights, and we then copy them on positions
4445        later in an FRC game. This means WB might not recognize castlings with
4446        Rooks that have moved back to their original position as illegal,
4447        but in ICS mode that is not its job anyway.
4448     */
4449     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4450     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4451
4452         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4453             if(board[0][i] == WhiteRook) j = i;
4454         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4455         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4456             if(board[0][i] == WhiteRook) j = i;
4457         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4458         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4459             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4460         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4461         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4462             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4463         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4464
4465         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4466         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4467             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4468         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4469             if(board[BOARD_HEIGHT-1][k] == bKing)
4470                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4471         if(gameInfo.variant == VariantTwoKings) {
4472             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4473             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4474             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4475         }
4476     } else { int r;
4477         r = boards[moveNum][CASTLING][0] = initialRights[0];
4478         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4479         r = boards[moveNum][CASTLING][1] = initialRights[1];
4480         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4481         r = boards[moveNum][CASTLING][3] = initialRights[3];
4482         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4483         r = boards[moveNum][CASTLING][4] = initialRights[4];
4484         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4485         /* wildcastle kludge: always assume King has rights */
4486         r = boards[moveNum][CASTLING][2] = initialRights[2];
4487         r = boards[moveNum][CASTLING][5] = initialRights[5];
4488     }
4489     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4490     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4491
4492
4493     if (ics_getting_history == H_GOT_REQ_HEADER ||
4494         ics_getting_history == H_GOT_UNREQ_HEADER) {
4495         /* This was an initial position from a move list, not
4496            the current position */
4497         return;
4498     }
4499
4500     /* Update currentMove and known move number limits */
4501     newMove = newGame || moveNum > forwardMostMove;
4502
4503     if (newGame) {
4504         forwardMostMove = backwardMostMove = currentMove = moveNum;
4505         if (gameMode == IcsExamining && moveNum == 0) {
4506           /* Workaround for ICS limitation: we are not told the wild
4507              type when starting to examine a game.  But if we ask for
4508              the move list, the move list header will tell us */
4509             ics_getting_history = H_REQUESTED;
4510             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4511             SendToICS(str);
4512         }
4513     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4514                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4515 #if ZIPPY
4516         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4517         /* [HGM] applied this also to an engine that is silently watching        */
4518         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4519             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4520             gameInfo.variant == currentlyInitializedVariant) {
4521           takeback = forwardMostMove - moveNum;
4522           for (i = 0; i < takeback; i++) {
4523             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4524             SendToProgram("undo\n", &first);
4525           }
4526         }
4527 #endif
4528
4529         forwardMostMove = moveNum;
4530         if (!pausing || currentMove > forwardMostMove)
4531           currentMove = forwardMostMove;
4532     } else {
4533         /* New part of history that is not contiguous with old part */
4534         if (pausing && gameMode == IcsExamining) {
4535             pauseExamInvalid = TRUE;
4536             forwardMostMove = pauseExamForwardMostMove;
4537             return;
4538         }
4539         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4540 #if ZIPPY
4541             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4542                 // [HGM] when we will receive the move list we now request, it will be
4543                 // fed to the engine from the first move on. So if the engine is not
4544                 // in the initial position now, bring it there.
4545                 InitChessProgram(&first, 0);
4546             }
4547 #endif
4548             ics_getting_history = H_REQUESTED;
4549             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4550             SendToICS(str);
4551         }
4552         forwardMostMove = backwardMostMove = currentMove = moveNum;
4553     }
4554
4555     /* Update the clocks */
4556     if (strchr(elapsed_time, '.')) {
4557       /* Time is in ms */
4558       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4559       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4560     } else {
4561       /* Time is in seconds */
4562       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4563       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4564     }
4565
4566
4567 #if ZIPPY
4568     if (appData.zippyPlay && newGame &&
4569         gameMode != IcsObserving && gameMode != IcsIdle &&
4570         gameMode != IcsExamining)
4571       ZippyFirstBoard(moveNum, basetime, increment);
4572 #endif
4573
4574     /* Put the move on the move list, first converting
4575        to canonical algebraic form. */
4576     if (moveNum > 0) {
4577   if (appData.debugMode) {
4578     if (appData.debugMode) { int f = forwardMostMove;
4579         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4580                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4581                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4582     }
4583     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4584     fprintf(debugFP, "moveNum = %d\n", moveNum);
4585     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4586     setbuf(debugFP, NULL);
4587   }
4588         if (moveNum <= backwardMostMove) {
4589             /* We don't know what the board looked like before
4590                this move.  Punt. */
4591           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4592             strcat(parseList[moveNum - 1], " ");
4593             strcat(parseList[moveNum - 1], elapsed_time);
4594             moveList[moveNum - 1][0] = NULLCHAR;
4595         } else if (strcmp(move_str, "none") == 0) {
4596             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4597             /* Again, we don't know what the board looked like;
4598                this is really the start of the game. */
4599             parseList[moveNum - 1][0] = NULLCHAR;
4600             moveList[moveNum - 1][0] = NULLCHAR;
4601             backwardMostMove = moveNum;
4602             startedFromSetupPosition = TRUE;
4603             fromX = fromY = toX = toY = -1;
4604         } else {
4605           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4606           //                 So we parse the long-algebraic move string in stead of the SAN move
4607           int valid; char buf[MSG_SIZ], *prom;
4608
4609           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4610                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4611           // str looks something like "Q/a1-a2"; kill the slash
4612           if(str[1] == '/')
4613             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4614           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4615           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4616                 strcat(buf, prom); // long move lacks promo specification!
4617           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4618                 if(appData.debugMode)
4619                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4620                 safeStrCpy(move_str, buf, MSG_SIZ);
4621           }
4622           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4623                                 &fromX, &fromY, &toX, &toY, &promoChar)
4624                || ParseOneMove(buf, moveNum - 1, &moveType,
4625                                 &fromX, &fromY, &toX, &toY, &promoChar);
4626           // end of long SAN patch
4627           if (valid) {
4628             (void) CoordsToAlgebraic(boards[moveNum - 1],
4629                                      PosFlags(moveNum - 1),
4630                                      fromY, fromX, toY, toX, promoChar,
4631                                      parseList[moveNum-1]);
4632             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4633               case MT_NONE:
4634               case MT_STALEMATE:
4635               default:
4636                 break;
4637               case MT_CHECK:
4638                 if(gameInfo.variant != VariantShogi)
4639                     strcat(parseList[moveNum - 1], "+");
4640                 break;
4641               case MT_CHECKMATE:
4642               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4643                 strcat(parseList[moveNum - 1], "#");
4644                 break;
4645             }
4646             strcat(parseList[moveNum - 1], " ");
4647             strcat(parseList[moveNum - 1], elapsed_time);
4648             /* currentMoveString is set as a side-effect of ParseOneMove */
4649             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4650             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4651             strcat(moveList[moveNum - 1], "\n");
4652
4653             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4654                                  && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4655               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4656                 ChessSquare old, new = boards[moveNum][k][j];
4657                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4658                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4659                   if(old == new) continue;
4660                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4661                   else if(new == WhiteWazir || new == BlackWazir) {
4662                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4663                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4664                       else boards[moveNum][k][j] = old; // preserve type of Gold
4665                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4666                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4667               }
4668           } else {
4669             /* Move from ICS was illegal!?  Punt. */
4670             if (appData.debugMode) {
4671               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4672               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4673             }
4674             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4675             strcat(parseList[moveNum - 1], " ");
4676             strcat(parseList[moveNum - 1], elapsed_time);
4677             moveList[moveNum - 1][0] = NULLCHAR;
4678             fromX = fromY = toX = toY = -1;
4679           }
4680         }
4681   if (appData.debugMode) {
4682     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4683     setbuf(debugFP, NULL);
4684   }
4685
4686 #if ZIPPY
4687         /* Send move to chess program (BEFORE animating it). */
4688         if (appData.zippyPlay && !newGame && newMove &&
4689            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4690
4691             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4692                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4693                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4694                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4695                             move_str);
4696                     DisplayError(str, 0);
4697                 } else {
4698                     if (first.sendTime) {
4699                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4700                     }
4701                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4702                     if (firstMove && !bookHit) {
4703                         firstMove = FALSE;
4704                         if (first.useColors) {
4705                           SendToProgram(gameMode == IcsPlayingWhite ?
4706                                         "white\ngo\n" :
4707                                         "black\ngo\n", &first);
4708                         } else {
4709                           SendToProgram("go\n", &first);
4710                         }
4711                         first.maybeThinking = TRUE;
4712                     }
4713                 }
4714             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4715               if (moveList[moveNum - 1][0] == NULLCHAR) {
4716                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4717                 DisplayError(str, 0);
4718               } else {
4719                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4720                 SendMoveToProgram(moveNum - 1, &first);
4721               }
4722             }
4723         }
4724 #endif
4725     }
4726
4727     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4728         /* If move comes from a remote source, animate it.  If it
4729            isn't remote, it will have already been animated. */
4730         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4731             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4732         }
4733         if (!pausing && appData.highlightLastMove) {
4734             SetHighlights(fromX, fromY, toX, toY);
4735         }
4736     }
4737
4738     /* Start the clocks */
4739     whiteFlag = blackFlag = FALSE;
4740     appData.clockMode = !(basetime == 0 && increment == 0);
4741     if (ticking == 0) {
4742       ics_clock_paused = TRUE;
4743       StopClocks();
4744     } else if (ticking == 1) {
4745       ics_clock_paused = FALSE;
4746     }
4747     if (gameMode == IcsIdle ||
4748         relation == RELATION_OBSERVING_STATIC ||
4749         relation == RELATION_EXAMINING ||
4750         ics_clock_paused)
4751       DisplayBothClocks();
4752     else
4753       StartClocks();
4754
4755     /* Display opponents and material strengths */
4756     if (gameInfo.variant != VariantBughouse &&
4757         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4758         if (tinyLayout || smallLayout) {
4759             if(gameInfo.variant == VariantNormal)
4760               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4761                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4762                     basetime, increment);
4763             else
4764               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4765                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4766                     basetime, increment, (int) gameInfo.variant);
4767         } else {
4768             if(gameInfo.variant == VariantNormal)
4769               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4770                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4771                     basetime, increment);
4772             else
4773               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4774                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4775                     basetime, increment, VariantName(gameInfo.variant));
4776         }
4777         DisplayTitle(str);
4778   if (appData.debugMode) {
4779     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4780   }
4781     }
4782
4783
4784     /* Display the board */
4785     if (!pausing && !appData.noGUI) {
4786
4787       if (appData.premove)
4788           if (!gotPremove ||
4789              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4790              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4791               ClearPremoveHighlights();
4792
4793       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4794         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4795       DrawPosition(j, boards[currentMove]);
4796
4797       DisplayMove(moveNum - 1);
4798       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4799             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4800               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4801         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4802       }
4803     }
4804
4805     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4806 #if ZIPPY
4807     if(bookHit) { // [HGM] book: simulate book reply
4808         static char bookMove[MSG_SIZ]; // a bit generous?
4809
4810         programStats.nodes = programStats.depth = programStats.time =
4811         programStats.score = programStats.got_only_move = 0;
4812         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4813
4814         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4815         strcat(bookMove, bookHit);
4816         HandleMachineMove(bookMove, &first);
4817     }
4818 #endif
4819 }
4820
4821 void
4822 GetMoveListEvent()
4823 {
4824     char buf[MSG_SIZ];
4825     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4826         ics_getting_history = H_REQUESTED;
4827         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4828         SendToICS(buf);
4829     }
4830 }
4831
4832 void
4833 AnalysisPeriodicEvent(force)
4834      int force;
4835 {
4836     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4837          && !force) || !appData.periodicUpdates)
4838       return;
4839
4840     /* Send . command to Crafty to collect stats */
4841     SendToProgram(".\n", &first);
4842
4843     /* Don't send another until we get a response (this makes
4844        us stop sending to old Crafty's which don't understand
4845        the "." command (sending illegal cmds resets node count & time,
4846        which looks bad)) */
4847     programStats.ok_to_send = 0;
4848 }
4849
4850 void ics_update_width(new_width)
4851         int new_width;
4852 {
4853         ics_printf("set width %d\n", new_width);
4854 }
4855
4856 void
4857 SendMoveToProgram(moveNum, cps)
4858      int moveNum;
4859      ChessProgramState *cps;
4860 {
4861     char buf[MSG_SIZ];
4862
4863     if (cps->useUsermove) {
4864       SendToProgram("usermove ", cps);
4865     }
4866     if (cps->useSAN) {
4867       char *space;
4868       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4869         int len = space - parseList[moveNum];
4870         memcpy(buf, parseList[moveNum], len);
4871         buf[len++] = '\n';
4872         buf[len] = NULLCHAR;
4873       } else {
4874         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4875       }
4876       SendToProgram(buf, cps);
4877     } else {
4878       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4879         AlphaRank(moveList[moveNum], 4);
4880         SendToProgram(moveList[moveNum], cps);
4881         AlphaRank(moveList[moveNum], 4); // and back
4882       } else
4883       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4884        * the engine. It would be nice to have a better way to identify castle
4885        * moves here. */
4886       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4887                                                                          && cps->useOOCastle) {
4888         int fromX = moveList[moveNum][0] - AAA;
4889         int fromY = moveList[moveNum][1] - ONE;
4890         int toX = moveList[moveNum][2] - AAA;
4891         int toY = moveList[moveNum][3] - ONE;
4892         if((boards[moveNum][fromY][fromX] == WhiteKing
4893             && boards[moveNum][toY][toX] == WhiteRook)
4894            || (boards[moveNum][fromY][fromX] == BlackKing
4895                && boards[moveNum][toY][toX] == BlackRook)) {
4896           if(toX > fromX) SendToProgram("O-O\n", cps);
4897           else SendToProgram("O-O-O\n", cps);
4898         }
4899         else SendToProgram(moveList[moveNum], cps);
4900       } else
4901       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4902         if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4903           snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4904                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4905         } else
4906           snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4907                                                moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4908         SendToProgram(buf, cps);
4909       }
4910       else SendToProgram(moveList[moveNum], cps);
4911       /* End of additions by Tord */
4912     }
4913
4914     /* [HGM] setting up the opening has brought engine in force mode! */
4915     /*       Send 'go' if we are in a mode where machine should play. */
4916     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4917         (gameMode == TwoMachinesPlay   ||
4918 #if ZIPPY
4919          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4920 #endif
4921          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4922         SendToProgram("go\n", cps);
4923   if (appData.debugMode) {
4924     fprintf(debugFP, "(extra)\n");
4925   }
4926     }
4927     setboardSpoiledMachineBlack = 0;
4928 }
4929
4930 void
4931 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4932      ChessMove moveType;
4933      int fromX, fromY, toX, toY;
4934      char promoChar;
4935 {
4936     char user_move[MSG_SIZ];
4937
4938     switch (moveType) {
4939       default:
4940         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4941                 (int)moveType, fromX, fromY, toX, toY);
4942         DisplayError(user_move + strlen("say "), 0);
4943         break;
4944       case WhiteKingSideCastle:
4945       case BlackKingSideCastle:
4946       case WhiteQueenSideCastleWild:
4947       case BlackQueenSideCastleWild:
4948       /* PUSH Fabien */
4949       case WhiteHSideCastleFR:
4950       case BlackHSideCastleFR:
4951       /* POP Fabien */
4952         snprintf(user_move, MSG_SIZ, "o-o\n");
4953         break;
4954       case WhiteQueenSideCastle:
4955       case BlackQueenSideCastle:
4956       case WhiteKingSideCastleWild:
4957       case BlackKingSideCastleWild:
4958       /* PUSH Fabien */
4959       case WhiteASideCastleFR:
4960       case BlackASideCastleFR:
4961       /* POP Fabien */
4962         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4963         break;
4964       case WhiteNonPromotion:
4965       case BlackNonPromotion:
4966         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4967         break;
4968       case WhitePromotion:
4969       case BlackPromotion:
4970         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4971           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4972                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4973                 PieceToChar(WhiteFerz));
4974         else if(gameInfo.variant == VariantGreat)
4975           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4976                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4977                 PieceToChar(WhiteMan));
4978         else
4979           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4980                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4981                 promoChar);
4982         break;
4983       case WhiteDrop:
4984       case BlackDrop:
4985       drop:
4986         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4987                  ToUpper(PieceToChar((ChessSquare) fromX)),
4988                  AAA + toX, ONE + toY);
4989         break;
4990       case IllegalMove:  /* could be a variant we don't quite understand */
4991         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4992       case NormalMove:
4993       case WhiteCapturesEnPassant:
4994       case BlackCapturesEnPassant:
4995         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4996                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4997         break;
4998     }
4999     SendToICS(user_move);
5000     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5001         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5002 }
5003
5004 void
5005 UploadGameEvent()
5006 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5007     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5008     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5009     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5010         DisplayError("You cannot do this while you are playing or observing", 0);
5011         return;
5012     }
5013     if(gameMode != IcsExamining) { // is this ever not the case?
5014         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5015
5016         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5017           snprintf(command,MSG_SIZ, "match %s", ics_handle);
5018         } else { // on FICS we must first go to general examine mode
5019           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5020         }
5021         if(gameInfo.variant != VariantNormal) {
5022             // try figure out wild number, as xboard names are not always valid on ICS
5023             for(i=1; i<=36; i++) {
5024               snprintf(buf, MSG_SIZ, "wild/%d", i);
5025                 if(StringToVariant(buf) == gameInfo.variant) break;
5026             }
5027             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5028             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5029             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5030         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5031         SendToICS(ics_prefix);
5032         SendToICS(buf);
5033         if(startedFromSetupPosition || backwardMostMove != 0) {
5034           fen = PositionToFEN(backwardMostMove, NULL);
5035           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5036             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5037             SendToICS(buf);
5038           } else { // FICS: everything has to set by separate bsetup commands
5039             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5040             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5041             SendToICS(buf);
5042             if(!WhiteOnMove(backwardMostMove)) {
5043                 SendToICS("bsetup tomove black\n");
5044             }
5045             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5046             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5047             SendToICS(buf);
5048             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5049             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5050             SendToICS(buf);
5051             i = boards[backwardMostMove][EP_STATUS];
5052             if(i >= 0) { // set e.p.
5053               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5054                 SendToICS(buf);
5055             }
5056             bsetup++;
5057           }
5058         }
5059       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5060             SendToICS("bsetup done\n"); // switch to normal examining.
5061     }
5062     for(i = backwardMostMove; i<last; i++) {
5063         char buf[20];
5064         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5065         SendToICS(buf);
5066     }
5067     SendToICS(ics_prefix);
5068     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5069 }
5070
5071 void
5072 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5073      int rf, ff, rt, ft;
5074      char promoChar;
5075      char move[7];
5076 {
5077     if (rf == DROP_RANK) {
5078       sprintf(move, "%c@%c%c\n",
5079                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5080     } else {
5081         if (promoChar == 'x' || promoChar == NULLCHAR) {
5082           sprintf(move, "%c%c%c%c\n",
5083                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5084         } else {
5085             sprintf(move, "%c%c%c%c%c\n",
5086                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5087         }
5088     }
5089 }
5090
5091 void
5092 ProcessICSInitScript(f)
5093      FILE *f;
5094 {
5095     char buf[MSG_SIZ];
5096
5097     while (fgets(buf, MSG_SIZ, f)) {
5098         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5099     }
5100
5101     fclose(f);
5102 }
5103
5104
5105 static int lastX, lastY, selectFlag, dragging;
5106
5107 void
5108 Sweep(int step)
5109 {
5110     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5111     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5112     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5113     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5114     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5115     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5116     do {
5117         promoSweep -= step;
5118         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5119         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5120         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5121         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5122         if(!step) step = 1;
5123     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5124             appData.testLegality && (promoSweep == king ||
5125             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5126     ChangeDragPiece(promoSweep);
5127 }
5128
5129 int PromoScroll(int x, int y)
5130 {
5131   int step = 0;
5132
5133   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5134   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5135   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5136   if(!step) return FALSE;
5137   lastX = x; lastY = y;
5138   if((promoSweep < BlackPawn) == flipView) step = -step;
5139   if(step > 0) selectFlag = 1;
5140   if(!selectFlag) Sweep(step);
5141   return FALSE;
5142 }
5143
5144 void
5145 NextPiece(int step)
5146 {
5147     ChessSquare piece = boards[currentMove][toY][toX];
5148     do {
5149         pieceSweep -= step;
5150         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5151         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5152         if(!step) step = -1;
5153     } while(PieceToChar(pieceSweep) == '.');
5154     boards[currentMove][toY][toX] = pieceSweep;
5155     DrawPosition(FALSE, boards[currentMove]);
5156     boards[currentMove][toY][toX] = piece;
5157 }
5158 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5159 void
5160 AlphaRank(char *move, int n)
5161 {
5162 //    char *p = move, c; int x, y;
5163
5164     if (appData.debugMode) {
5165         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5166     }
5167
5168     if(move[1]=='*' &&
5169        move[2]>='0' && move[2]<='9' &&
5170        move[3]>='a' && move[3]<='x'    ) {
5171         move[1] = '@';
5172         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5173         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5174     } else
5175     if(move[0]>='0' && move[0]<='9' &&
5176        move[1]>='a' && move[1]<='x' &&
5177        move[2]>='0' && move[2]<='9' &&
5178        move[3]>='a' && move[3]<='x'    ) {
5179         /* input move, Shogi -> normal */
5180         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5181         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5182         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5183         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5184     } else
5185     if(move[1]=='@' &&
5186        move[3]>='0' && move[3]<='9' &&
5187        move[2]>='a' && move[2]<='x'    ) {
5188         move[1] = '*';
5189         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5190         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5191     } else
5192     if(
5193        move[0]>='a' && move[0]<='x' &&
5194        move[3]>='0' && move[3]<='9' &&
5195        move[2]>='a' && move[2]<='x'    ) {
5196          /* output move, normal -> Shogi */
5197         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5198         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5199         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5200         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5201         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5202     }
5203     if (appData.debugMode) {
5204         fprintf(debugFP, "   out = '%s'\n", move);
5205     }
5206 }
5207
5208 char yy_textstr[8000];
5209
5210 /* Parser for moves from gnuchess, ICS, or user typein box */
5211 Boolean
5212 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5213      char *move;
5214      int moveNum;
5215      ChessMove *moveType;
5216      int *fromX, *fromY, *toX, *toY;
5217      char *promoChar;
5218 {
5219     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5220
5221     switch (*moveType) {
5222       case WhitePromotion:
5223       case BlackPromotion:
5224       case WhiteNonPromotion:
5225       case BlackNonPromotion:
5226       case NormalMove:
5227       case WhiteCapturesEnPassant:
5228       case BlackCapturesEnPassant:
5229       case WhiteKingSideCastle:
5230       case WhiteQueenSideCastle:
5231       case BlackKingSideCastle:
5232       case BlackQueenSideCastle:
5233       case WhiteKingSideCastleWild:
5234       case WhiteQueenSideCastleWild:
5235       case BlackKingSideCastleWild:
5236       case BlackQueenSideCastleWild:
5237       /* Code added by Tord: */
5238       case WhiteHSideCastleFR:
5239       case WhiteASideCastleFR:
5240       case BlackHSideCastleFR:
5241       case BlackASideCastleFR:
5242       /* End of code added by Tord */
5243       case IllegalMove:         /* bug or odd chess variant */
5244         *fromX = currentMoveString[0] - AAA;
5245         *fromY = currentMoveString[1] - ONE;
5246         *toX = currentMoveString[2] - AAA;
5247         *toY = currentMoveString[3] - ONE;
5248         *promoChar = currentMoveString[4];
5249         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5250             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5251     if (appData.debugMode) {
5252         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5253     }
5254             *fromX = *fromY = *toX = *toY = 0;
5255             return FALSE;
5256         }
5257         if (appData.testLegality) {
5258           return (*moveType != IllegalMove);
5259         } else {
5260           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5261                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5262         }
5263
5264       case WhiteDrop:
5265       case BlackDrop:
5266         *fromX = *moveType == WhiteDrop ?
5267           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5268           (int) CharToPiece(ToLower(currentMoveString[0]));
5269         *fromY = DROP_RANK;
5270         *toX = currentMoveString[2] - AAA;
5271         *toY = currentMoveString[3] - ONE;
5272         *promoChar = NULLCHAR;
5273         return TRUE;
5274
5275       case AmbiguousMove:
5276       case ImpossibleMove:
5277       case EndOfFile:
5278       case ElapsedTime:
5279       case Comment:
5280       case PGNTag:
5281       case NAG:
5282       case WhiteWins:
5283       case BlackWins:
5284       case GameIsDrawn:
5285       default:
5286     if (appData.debugMode) {
5287         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5288     }
5289         /* bug? */
5290         *fromX = *fromY = *toX = *toY = 0;
5291         *promoChar = NULLCHAR;
5292         return FALSE;
5293     }
5294 }
5295
5296 Boolean pushed = FALSE;
5297 char *lastParseAttempt;
5298
5299 void
5300 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5301 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5302   int fromX, fromY, toX, toY; char promoChar;
5303   ChessMove moveType;
5304   Boolean valid;
5305   int nr = 0;
5306
5307   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5308     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5309     pushed = TRUE;
5310   }
5311   endPV = forwardMostMove;
5312   do {
5313     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5314     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5315     lastParseAttempt = pv;
5316     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5317 if(appData.debugMode){
5318 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5319 }
5320     if(!valid && nr == 0 &&
5321        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5322         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5323         // Hande case where played move is different from leading PV move
5324         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5325         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5326         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5327         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5328           endPV += 2; // if position different, keep this
5329           moveList[endPV-1][0] = fromX + AAA;
5330           moveList[endPV-1][1] = fromY + ONE;
5331           moveList[endPV-1][2] = toX + AAA;
5332           moveList[endPV-1][3] = toY + ONE;
5333           parseList[endPV-1][0] = NULLCHAR;
5334           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5335         }
5336       }
5337     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5338     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5339     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5340     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5341         valid++; // allow comments in PV
5342         continue;
5343     }
5344     nr++;
5345     if(endPV+1 > framePtr) break; // no space, truncate
5346     if(!valid) break;
5347     endPV++;
5348     CopyBoard(boards[endPV], boards[endPV-1]);
5349     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5350     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5351     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5352     CoordsToAlgebraic(boards[endPV - 1],
5353                              PosFlags(endPV - 1),
5354                              fromY, fromX, toY, toX, promoChar,
5355                              parseList[endPV - 1]);
5356   } while(valid);
5357   if(atEnd == 2) return; // used hidden, for PV conversion
5358   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5359   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5360   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5361                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5362   DrawPosition(TRUE, boards[currentMove]);
5363 }
5364
5365 int
5366 MultiPV(ChessProgramState *cps)
5367 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5368         int i;
5369         for(i=0; i<cps->nrOptions; i++)
5370             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5371                 return i;
5372         return -1;
5373 }
5374
5375 Boolean
5376 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5377 {
5378         int startPV, multi, lineStart, origIndex = index;
5379         char *p, buf2[MSG_SIZ];
5380
5381         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5382         lastX = x; lastY = y;
5383         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5384         lineStart = startPV = index;
5385         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5386         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5387         index = startPV;
5388         do{ while(buf[index] && buf[index] != '\n') index++;
5389         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5390         buf[index] = 0;
5391         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5392                 int n = first.option[multi].value;
5393                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5394                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5395                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5396                 first.option[multi].value = n;
5397                 *start = *end = 0;
5398                 return FALSE;
5399         }
5400         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5401         *start = startPV; *end = index-1;
5402         return TRUE;
5403 }
5404
5405 char *
5406 PvToSAN(char *pv)
5407 {
5408         static char buf[10*MSG_SIZ];
5409         int i, k=0, savedEnd=endPV;
5410         *buf = NULLCHAR;
5411         if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5412         ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5413         for(i = forwardMostMove; i<endPV; i++){
5414             if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5415             else    snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5416             k += strlen(buf+k);
5417         }
5418         snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5419         if(forwardMostMove < savedEnd) PopInner(0);
5420         endPV = savedEnd;
5421         return buf;
5422 }
5423
5424 Boolean
5425 LoadPV(int x, int y)
5426 { // called on right mouse click to load PV
5427   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5428   lastX = x; lastY = y;
5429   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5430   return TRUE;
5431 }
5432
5433 void
5434 UnLoadPV()
5435 {
5436   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5437   if(endPV < 0) return;
5438   endPV = -1;
5439   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5440         Boolean saveAnimate = appData.animate;
5441         if(pushed) {
5442             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5443                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5444             } else storedGames--; // abandon shelved tail of original game
5445         }
5446         pushed = FALSE;
5447         forwardMostMove = currentMove;
5448         currentMove = oldFMM;
5449         appData.animate = FALSE;
5450         ToNrEvent(forwardMostMove);
5451         appData.animate = saveAnimate;
5452   }
5453   currentMove = forwardMostMove;
5454   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5455   ClearPremoveHighlights();
5456   DrawPosition(TRUE, boards[currentMove]);
5457 }
5458
5459 void
5460 MovePV(int x, int y, int h)
5461 { // step through PV based on mouse coordinates (called on mouse move)
5462   int margin = h>>3, step = 0;
5463
5464   // we must somehow check if right button is still down (might be released off board!)
5465   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5466   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5467   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5468   if(!step) return;
5469   lastX = x; lastY = y;
5470
5471   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5472   if(endPV < 0) return;
5473   if(y < margin) step = 1; else
5474   if(y > h - margin) step = -1;
5475   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5476   currentMove += step;
5477   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5478   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5479                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5480   DrawPosition(FALSE, boards[currentMove]);
5481 }
5482
5483
5484 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5485 // All positions will have equal probability, but the current method will not provide a unique
5486 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5487 #define DARK 1
5488 #define LITE 2
5489 #define ANY 3
5490
5491 int squaresLeft[4];
5492 int piecesLeft[(int)BlackPawn];
5493 int seed, nrOfShuffles;
5494
5495 void GetPositionNumber()
5496 {       // sets global variable seed
5497         int i;
5498
5499         seed = appData.defaultFrcPosition;
5500         if(seed < 0) { // randomize based on time for negative FRC position numbers
5501                 for(i=0; i<50; i++) seed += random();
5502                 seed = random() ^ random() >> 8 ^ random() << 8;
5503                 if(seed<0) seed = -seed;
5504         }
5505 }
5506
5507 int put(Board board, int pieceType, int rank, int n, int shade)
5508 // put the piece on the (n-1)-th empty squares of the given shade
5509 {
5510         int i;
5511
5512         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5513                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5514                         board[rank][i] = (ChessSquare) pieceType;
5515                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5516                         squaresLeft[ANY]--;
5517                         piecesLeft[pieceType]--;
5518                         return i;
5519                 }
5520         }
5521         return -1;
5522 }
5523
5524
5525 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5526 // calculate where the next piece goes, (any empty square), and put it there
5527 {
5528         int i;
5529
5530         i = seed % squaresLeft[shade];
5531         nrOfShuffles *= squaresLeft[shade];
5532         seed /= squaresLeft[shade];
5533         put(board, pieceType, rank, i, shade);
5534 }
5535
5536 void AddTwoPieces(Board board, int pieceType, int rank)
5537 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5538 {
5539         int i, n=squaresLeft[ANY], j=n-1, k;
5540
5541         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5542         i = seed % k;  // pick one
5543         nrOfShuffles *= k;
5544         seed /= k;
5545         while(i >= j) i -= j--;
5546         j = n - 1 - j; i += j;
5547         put(board, pieceType, rank, j, ANY);
5548         put(board, pieceType, rank, i, ANY);
5549 }
5550
5551 void SetUpShuffle(Board board, int number)
5552 {
5553         int i, p, first=1;
5554
5555         GetPositionNumber(); nrOfShuffles = 1;
5556
5557         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5558         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5559         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5560
5561         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5562
5563         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5564             p = (int) board[0][i];
5565             if(p < (int) BlackPawn) piecesLeft[p] ++;
5566             board[0][i] = EmptySquare;
5567         }
5568
5569         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5570             // shuffles restricted to allow normal castling put KRR first
5571             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5572                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5573             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5574                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5575             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5576                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5577             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5578                 put(board, WhiteRook, 0, 0, ANY);
5579             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5580         }
5581
5582         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5583             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5584             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5585                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5586                 while(piecesLeft[p] >= 2) {
5587                     AddOnePiece(board, p, 0, LITE);
5588                     AddOnePiece(board, p, 0, DARK);
5589                 }
5590                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5591             }
5592
5593         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5594             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5595             // but we leave King and Rooks for last, to possibly obey FRC restriction
5596             if(p == (int)WhiteRook) continue;
5597             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5598             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5599         }
5600
5601         // now everything is placed, except perhaps King (Unicorn) and Rooks
5602
5603         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5604             // Last King gets castling rights
5605             while(piecesLeft[(int)WhiteUnicorn]) {
5606                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5607                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5608             }
5609
5610             while(piecesLeft[(int)WhiteKing]) {
5611                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5612                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5613             }
5614
5615
5616         } else {
5617             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5618             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5619         }
5620
5621         // Only Rooks can be left; simply place them all
5622         while(piecesLeft[(int)WhiteRook]) {
5623                 i = put(board, WhiteRook, 0, 0, ANY);
5624                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5625                         if(first) {
5626                                 first=0;
5627                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5628                         }
5629                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5630                 }
5631         }
5632         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5633             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5634         }
5635
5636         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5637 }
5638
5639 int SetCharTable( char *table, const char * map )
5640 /* [HGM] moved here from winboard.c because of its general usefulness */
5641 /*       Basically a safe strcpy that uses the last character as King */
5642 {
5643     int result = FALSE; int NrPieces;
5644
5645     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5646                     && NrPieces >= 12 && !(NrPieces&1)) {
5647         int i; /* [HGM] Accept even length from 12 to 34 */
5648
5649         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5650         for( i=0; i<NrPieces/2-1; i++ ) {
5651             table[i] = map[i];
5652             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5653         }
5654         table[(int) WhiteKing]  = map[NrPieces/2-1];
5655         table[(int) BlackKing]  = map[NrPieces-1];
5656
5657         result = TRUE;
5658     }
5659
5660     return result;
5661 }
5662
5663 void Prelude(Board board)
5664 {       // [HGM] superchess: random selection of exo-pieces
5665         int i, j, k; ChessSquare p;
5666         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5667
5668         GetPositionNumber(); // use FRC position number
5669
5670         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5671             SetCharTable(pieceToChar, appData.pieceToCharTable);
5672             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5673                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5674         }
5675
5676         j = seed%4;                 seed /= 4;
5677         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5678         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5679         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5680         j = seed%3 + (seed%3 >= j); seed /= 3;
5681         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5682         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5683         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5684         j = seed%3;                 seed /= 3;
5685         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5686         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5687         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5688         j = seed%2 + (seed%2 >= j); seed /= 2;
5689         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5690         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5691         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5692         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5693         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5694         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5695         put(board, exoPieces[0],    0, 0, ANY);
5696         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5697 }
5698
5699 void
5700 InitPosition(redraw)
5701      int redraw;
5702 {
5703     ChessSquare (* pieces)[BOARD_FILES];
5704     int i, j, pawnRow, overrule,
5705     oldx = gameInfo.boardWidth,
5706     oldy = gameInfo.boardHeight,
5707     oldh = gameInfo.holdingsWidth;
5708     static int oldv;
5709
5710     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5711
5712     /* [AS] Initialize pv info list [HGM] and game status */
5713     {
5714         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5715             pvInfoList[i].depth = 0;
5716             boards[i][EP_STATUS] = EP_NONE;
5717             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5718         }
5719
5720         initialRulePlies = 0; /* 50-move counter start */
5721
5722         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5723         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5724     }
5725
5726
5727     /* [HGM] logic here is completely changed. In stead of full positions */
5728     /* the initialized data only consist of the two backranks. The switch */
5729     /* selects which one we will use, which is than copied to the Board   */
5730     /* initialPosition, which for the rest is initialized by Pawns and    */
5731     /* empty squares. This initial position is then copied to boards[0],  */
5732     /* possibly after shuffling, so that it remains available.            */
5733
5734     gameInfo.holdingsWidth = 0; /* default board sizes */
5735     gameInfo.boardWidth    = 8;
5736     gameInfo.boardHeight   = 8;
5737     gameInfo.holdingsSize  = 0;
5738     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5739     for(i=0; i<BOARD_FILES-2; i++)
5740       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5741     initialPosition[EP_STATUS] = EP_NONE;
5742     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5743     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5744          SetCharTable(pieceNickName, appData.pieceNickNames);
5745     else SetCharTable(pieceNickName, "............");
5746     pieces = FIDEArray;
5747
5748     switch (gameInfo.variant) {
5749     case VariantFischeRandom:
5750       shuffleOpenings = TRUE;
5751     default:
5752       break;
5753     case VariantShatranj:
5754       pieces = ShatranjArray;
5755       nrCastlingRights = 0;
5756       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5757       break;
5758     case VariantMakruk:
5759       pieces = makrukArray;
5760       nrCastlingRights = 0;
5761       startedFromSetupPosition = TRUE;
5762       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5763       break;
5764     case VariantTwoKings:
5765       pieces = twoKingsArray;
5766       break;
5767     case VariantGrand:
5768       pieces = GrandArray;
5769       nrCastlingRights = 0;
5770       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5771       gameInfo.boardWidth = 10;
5772       gameInfo.boardHeight = 10;
5773       gameInfo.holdingsSize = 7;
5774       break;
5775     case VariantCapaRandom:
5776       shuffleOpenings = TRUE;
5777     case VariantCapablanca:
5778       pieces = CapablancaArray;
5779       gameInfo.boardWidth = 10;
5780       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5781       break;
5782     case VariantGothic:
5783       pieces = GothicArray;
5784       gameInfo.boardWidth = 10;
5785       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5786       break;
5787     case VariantSChess:
5788       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5789       gameInfo.holdingsSize = 7;
5790       break;
5791     case VariantJanus:
5792       pieces = JanusArray;
5793       gameInfo.boardWidth = 10;
5794       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5795       nrCastlingRights = 6;
5796         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5797         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5798         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5799         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5800         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5801         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5802       break;
5803     case VariantFalcon:
5804       pieces = FalconArray;
5805       gameInfo.boardWidth = 10;
5806       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5807       break;
5808     case VariantXiangqi:
5809       pieces = XiangqiArray;
5810       gameInfo.boardWidth  = 9;
5811       gameInfo.boardHeight = 10;
5812       nrCastlingRights = 0;
5813       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5814       break;
5815     case VariantShogi:
5816       pieces = ShogiArray;
5817       gameInfo.boardWidth  = 9;
5818       gameInfo.boardHeight = 9;
5819       gameInfo.holdingsSize = 7;
5820       nrCastlingRights = 0;
5821       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5822       break;
5823     case VariantCourier:
5824       pieces = CourierArray;
5825       gameInfo.boardWidth  = 12;
5826       nrCastlingRights = 0;
5827       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5828       break;
5829     case VariantKnightmate:
5830       pieces = KnightmateArray;
5831       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5832       break;
5833     case VariantSpartan:
5834       pieces = SpartanArray;
5835       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5836       break;
5837     case VariantFairy:
5838       pieces = fairyArray;
5839       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5840       break;
5841     case VariantGreat:
5842       pieces = GreatArray;
5843       gameInfo.boardWidth = 10;
5844       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5845       gameInfo.holdingsSize = 8;
5846       break;
5847     case VariantSuper:
5848       pieces = FIDEArray;
5849       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5850       gameInfo.holdingsSize = 8;
5851       startedFromSetupPosition = TRUE;
5852       break;
5853     case VariantCrazyhouse:
5854     case VariantBughouse:
5855       pieces = FIDEArray;
5856       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5857       gameInfo.holdingsSize = 5;
5858       break;
5859     case VariantWildCastle:
5860       pieces = FIDEArray;
5861       /* !!?shuffle with kings guaranteed to be on d or e file */
5862       shuffleOpenings = 1;
5863       break;
5864     case VariantNoCastle:
5865       pieces = FIDEArray;
5866       nrCastlingRights = 0;
5867       /* !!?unconstrained back-rank shuffle */
5868       shuffleOpenings = 1;
5869       break;
5870     }
5871
5872     overrule = 0;
5873     if(appData.NrFiles >= 0) {
5874         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5875         gameInfo.boardWidth = appData.NrFiles;
5876     }
5877     if(appData.NrRanks >= 0) {
5878         gameInfo.boardHeight = appData.NrRanks;
5879     }
5880     if(appData.holdingsSize >= 0) {
5881         i = appData.holdingsSize;
5882         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5883         gameInfo.holdingsSize = i;
5884     }
5885     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5886     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5887         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5888
5889     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5890     if(pawnRow < 1) pawnRow = 1;
5891     if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5892
5893     /* User pieceToChar list overrules defaults */
5894     if(appData.pieceToCharTable != NULL)
5895         SetCharTable(pieceToChar, appData.pieceToCharTable);
5896
5897     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5898
5899         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5900             s = (ChessSquare) 0; /* account holding counts in guard band */
5901         for( i=0; i<BOARD_HEIGHT; i++ )
5902             initialPosition[i][j] = s;
5903
5904         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5905         initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5906         initialPosition[pawnRow][j] = WhitePawn;
5907         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5908         if(gameInfo.variant == VariantXiangqi) {
5909             if(j&1) {
5910                 initialPosition[pawnRow][j] =
5911                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5912                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5913                    initialPosition[2][j] = WhiteCannon;
5914                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5915                 }
5916             }
5917         }
5918         if(gameInfo.variant == VariantGrand) {
5919             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5920                initialPosition[0][j] = WhiteRook;
5921                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5922             }
5923         }
5924         initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
5925     }
5926     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5927
5928             j=BOARD_LEFT+1;
5929             initialPosition[1][j] = WhiteBishop;
5930             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5931             j=BOARD_RGHT-2;
5932             initialPosition[1][j] = WhiteRook;
5933             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5934     }
5935
5936     if( nrCastlingRights == -1) {
5937         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5938         /*       This sets default castling rights from none to normal corners   */
5939         /* Variants with other castling rights must set them themselves above    */
5940         nrCastlingRights = 6;
5941
5942         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5943         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5944         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5945         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5946         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5947         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5948      }
5949
5950      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5951      if(gameInfo.variant == VariantGreat) { // promotion commoners
5952         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5953         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5954         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5955         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5956      }
5957      if( gameInfo.variant == VariantSChess ) {
5958       initialPosition[1][0] = BlackMarshall;
5959       initialPosition[2][0] = BlackAngel;
5960       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5961       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5962       initialPosition[1][1] = initialPosition[2][1] = 
5963       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5964      }
5965   if (appData.debugMode) {
5966     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5967   }
5968     if(shuffleOpenings) {
5969         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5970         startedFromSetupPosition = TRUE;
5971     }
5972     if(startedFromPositionFile) {
5973       /* [HGM] loadPos: use PositionFile for every new game */
5974       CopyBoard(initialPosition, filePosition);
5975       for(i=0; i<nrCastlingRights; i++)
5976           initialRights[i] = filePosition[CASTLING][i];
5977       startedFromSetupPosition = TRUE;
5978     }
5979
5980     CopyBoard(boards[0], initialPosition);
5981
5982     if(oldx != gameInfo.boardWidth ||
5983        oldy != gameInfo.boardHeight ||
5984        oldv != gameInfo.variant ||
5985        oldh != gameInfo.holdingsWidth
5986                                          )
5987             InitDrawingSizes(-2 ,0);
5988
5989     oldv = gameInfo.variant;
5990     if (redraw)
5991       DrawPosition(TRUE, boards[currentMove]);
5992 }
5993
5994 void
5995 SendBoard(cps, moveNum)
5996      ChessProgramState *cps;
5997      int moveNum;
5998 {
5999     char message[MSG_SIZ];
6000
6001     if (cps->useSetboard) {
6002       char* fen = PositionToFEN(moveNum, cps->fenOverride);
6003       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6004       SendToProgram(message, cps);
6005       free(fen);
6006
6007     } else {
6008       ChessSquare *bp;
6009       int i, j;
6010       /* Kludge to set black to move, avoiding the troublesome and now
6011        * deprecated "black" command.
6012        */
6013       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6014         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6015
6016       SendToProgram("edit\n", cps);
6017       SendToProgram("#\n", cps);
6018       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6019         bp = &boards[moveNum][i][BOARD_LEFT];
6020         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6021           if ((int) *bp < (int) BlackPawn) {
6022             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6023                     AAA + j, ONE + i);
6024             if(message[0] == '+' || message[0] == '~') {
6025               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6026                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6027                         AAA + j, ONE + i);
6028             }
6029             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6030                 message[1] = BOARD_RGHT   - 1 - j + '1';
6031                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6032             }
6033             SendToProgram(message, cps);
6034           }
6035         }
6036       }
6037
6038       SendToProgram("c\n", cps);
6039       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6040         bp = &boards[moveNum][i][BOARD_LEFT];
6041         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6042           if (((int) *bp != (int) EmptySquare)
6043               && ((int) *bp >= (int) BlackPawn)) {
6044             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6045                     AAA + j, ONE + i);
6046             if(message[0] == '+' || message[0] == '~') {
6047               snprintf(message, MSG_SIZ,"%c%c%c+\n",
6048                         PieceToChar((ChessSquare)(DEMOTED *bp)),
6049                         AAA + j, ONE + i);
6050             }
6051             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6052                 message[1] = BOARD_RGHT   - 1 - j + '1';
6053                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6054             }
6055             SendToProgram(message, cps);
6056           }
6057         }
6058       }
6059
6060       SendToProgram(".\n", cps);
6061     }
6062     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6063 }
6064
6065 ChessSquare
6066 DefaultPromoChoice(int white)
6067 {
6068     ChessSquare result;
6069     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6070         result = WhiteFerz; // no choice
6071     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6072         result= WhiteKing; // in Suicide Q is the last thing we want
6073     else if(gameInfo.variant == VariantSpartan)
6074         result = white ? WhiteQueen : WhiteAngel;
6075     else result = WhiteQueen;
6076     if(!white) result = WHITE_TO_BLACK result;
6077     return result;
6078 }
6079
6080 static int autoQueen; // [HGM] oneclick
6081
6082 int
6083 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6084 {
6085     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6086     /* [HGM] add Shogi promotions */
6087     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6088     ChessSquare piece;
6089     ChessMove moveType;
6090     Boolean premove;
6091
6092     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6093     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6094
6095     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6096       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6097         return FALSE;
6098
6099     piece = boards[currentMove][fromY][fromX];
6100     if(gameInfo.variant == VariantShogi) {
6101         promotionZoneSize = BOARD_HEIGHT/3;
6102         highestPromotingPiece = (int)WhiteFerz;
6103     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6104         promotionZoneSize = 3;
6105     }
6106
6107     // Treat Lance as Pawn when it is not representing Amazon
6108     if(gameInfo.variant != VariantSuper) {
6109         if(piece == WhiteLance) piece = WhitePawn; else
6110         if(piece == BlackLance) piece = BlackPawn;
6111     }
6112
6113     // next weed out all moves that do not touch the promotion zone at all
6114     if((int)piece >= BlackPawn) {
6115         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6116              return FALSE;
6117         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6118     } else {
6119         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6120            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6121     }
6122
6123     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6124
6125     // weed out mandatory Shogi promotions
6126     if(gameInfo.variant == VariantShogi) {
6127         if(piece >= BlackPawn) {
6128             if(toY == 0 && piece == BlackPawn ||
6129                toY == 0 && piece == BlackQueen ||
6130                toY <= 1 && piece == BlackKnight) {
6131                 *promoChoice = '+';
6132                 return FALSE;
6133             }
6134         } else {
6135             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6136                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6137                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6138                 *promoChoice = '+';
6139                 return FALSE;
6140             }
6141         }
6142     }
6143
6144     // weed out obviously illegal Pawn moves
6145     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6146         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6147         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6148         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6149         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6150         // note we are not allowed to test for valid (non-)capture, due to premove
6151     }
6152
6153     // we either have a choice what to promote to, or (in Shogi) whether to promote
6154     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6155         *promoChoice = PieceToChar(BlackFerz);  // no choice
6156         return FALSE;
6157     }
6158     // no sense asking what we must promote to if it is going to explode...
6159     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6160         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6161         return FALSE;
6162     }
6163     // give caller the default choice even if we will not make it
6164     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6165     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6166     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6167                            && gameInfo.variant != VariantShogi
6168                            && gameInfo.variant != VariantSuper) return FALSE;
6169     if(autoQueen) return FALSE; // predetermined
6170
6171     // suppress promotion popup on illegal moves that are not premoves
6172     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6173               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6174     if(appData.testLegality && !premove) {
6175         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6176                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6177         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6178             return FALSE;
6179     }
6180
6181     return TRUE;
6182 }
6183
6184 int
6185 InPalace(row, column)
6186      int row, column;
6187 {   /* [HGM] for Xiangqi */
6188     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6189          column < (BOARD_WIDTH + 4)/2 &&
6190          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6191     return FALSE;
6192 }
6193
6194 int
6195 PieceForSquare (x, y)
6196      int x;
6197      int y;
6198 {
6199   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6200      return -1;
6201   else
6202      return boards[currentMove][y][x];
6203 }
6204
6205 int
6206 OKToStartUserMove(x, y)
6207      int x, y;
6208 {
6209     ChessSquare from_piece;
6210     int white_piece;
6211
6212     if (matchMode) return FALSE;
6213     if (gameMode == EditPosition) return TRUE;
6214
6215     if (x >= 0 && y >= 0)
6216       from_piece = boards[currentMove][y][x];
6217     else
6218       from_piece = EmptySquare;
6219
6220     if (from_piece == EmptySquare) return FALSE;
6221
6222     white_piece = (int)from_piece >= (int)WhitePawn &&
6223       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6224
6225     switch (gameMode) {
6226       case PlayFromGameFile:
6227       case AnalyzeFile:
6228       case TwoMachinesPlay:
6229       case EndOfGame:
6230         return FALSE;
6231
6232       case IcsObserving:
6233       case IcsIdle:
6234         return FALSE;
6235
6236       case MachinePlaysWhite:
6237       case IcsPlayingBlack:
6238         if (appData.zippyPlay) return FALSE;
6239         if (white_piece) {
6240             DisplayMoveError(_("You are playing Black"));
6241             return FALSE;
6242         }
6243         break;
6244
6245       case MachinePlaysBlack:
6246       case IcsPlayingWhite:
6247         if (appData.zippyPlay) return FALSE;
6248         if (!white_piece) {
6249             DisplayMoveError(_("You are playing White"));
6250             return FALSE;
6251         }
6252         break;
6253
6254       case EditGame:
6255         if (!white_piece && WhiteOnMove(currentMove)) {
6256             DisplayMoveError(_("It is White's turn"));
6257             return FALSE;
6258         }
6259         if (white_piece && !WhiteOnMove(currentMove)) {
6260             DisplayMoveError(_("It is Black's turn"));
6261             return FALSE;
6262         }
6263         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6264             /* Editing correspondence game history */
6265             /* Could disallow this or prompt for confirmation */
6266             cmailOldMove = -1;
6267         }
6268         break;
6269
6270       case BeginningOfGame:
6271         if (appData.icsActive) return FALSE;
6272         if (!appData.noChessProgram) {
6273             if (!white_piece) {
6274                 DisplayMoveError(_("You are playing White"));
6275                 return FALSE;
6276             }
6277         }
6278         break;
6279
6280       case Training:
6281         if (!white_piece && WhiteOnMove(currentMove)) {
6282             DisplayMoveError(_("It is White's turn"));
6283             return FALSE;
6284         }
6285         if (white_piece && !WhiteOnMove(currentMove)) {
6286             DisplayMoveError(_("It is Black's turn"));
6287             return FALSE;
6288         }
6289         break;
6290
6291       default:
6292       case IcsExamining:
6293         break;
6294     }
6295     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6296         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6297         && gameMode != AnalyzeFile && gameMode != Training) {
6298         DisplayMoveError(_("Displayed position is not current"));
6299         return FALSE;
6300     }
6301     return TRUE;
6302 }
6303
6304 Boolean
6305 OnlyMove(int *x, int *y, Boolean captures) {
6306     DisambiguateClosure cl;
6307     if (appData.zippyPlay || !appData.testLegality) return FALSE;
6308     switch(gameMode) {
6309       case MachinePlaysBlack:
6310       case IcsPlayingWhite:
6311       case BeginningOfGame:
6312         if(!WhiteOnMove(currentMove)) return FALSE;
6313         break;
6314       case MachinePlaysWhite:
6315       case IcsPlayingBlack:
6316         if(WhiteOnMove(currentMove)) return FALSE;
6317         break;
6318       case EditGame:
6319         break;
6320       default:
6321         return FALSE;
6322     }
6323     cl.pieceIn = EmptySquare;
6324     cl.rfIn = *y;
6325     cl.ffIn = *x;
6326     cl.rtIn = -1;
6327     cl.ftIn = -1;
6328     cl.promoCharIn = NULLCHAR;
6329     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6330     if( cl.kind == NormalMove ||
6331         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6332         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6333         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6334       fromX = cl.ff;
6335       fromY = cl.rf;
6336       *x = cl.ft;
6337       *y = cl.rt;
6338       return TRUE;
6339     }
6340     if(cl.kind != ImpossibleMove) return FALSE;
6341     cl.pieceIn = EmptySquare;
6342     cl.rfIn = -1;
6343     cl.ffIn = -1;
6344     cl.rtIn = *y;
6345     cl.ftIn = *x;
6346     cl.promoCharIn = NULLCHAR;
6347     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6348     if( cl.kind == NormalMove ||
6349         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6350         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6351         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6352       fromX = cl.ff;
6353       fromY = cl.rf;
6354       *x = cl.ft;
6355       *y = cl.rt;
6356       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6357       return TRUE;
6358     }
6359     return FALSE;
6360 }
6361
6362 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6363 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6364 int lastLoadGameUseList = FALSE;
6365 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6366 ChessMove lastLoadGameStart = EndOfFile;
6367
6368 void
6369 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6370      int fromX, fromY, toX, toY;
6371      int promoChar;
6372 {
6373     ChessMove moveType;
6374     ChessSquare pdown, pup;
6375
6376     /* Check if the user is playing in turn.  This is complicated because we
6377        let the user "pick up" a piece before it is his turn.  So the piece he
6378        tried to pick up may have been captured by the time he puts it down!
6379        Therefore we use the color the user is supposed to be playing in this
6380        test, not the color of the piece that is currently on the starting
6381        square---except in EditGame mode, where the user is playing both
6382        sides; fortunately there the capture race can't happen.  (It can
6383        now happen in IcsExamining mode, but that's just too bad.  The user
6384        will get a somewhat confusing message in that case.)
6385        */
6386
6387     switch (gameMode) {
6388       case PlayFromGameFile:
6389       case AnalyzeFile:
6390       case TwoMachinesPlay:
6391       case EndOfGame:
6392       case IcsObserving:
6393       case IcsIdle:
6394         /* We switched into a game mode where moves are not accepted,
6395            perhaps while the mouse button was down. */
6396         return;
6397
6398       case MachinePlaysWhite:
6399         /* User is moving for Black */
6400         if (WhiteOnMove(currentMove)) {
6401             DisplayMoveError(_("It is White's turn"));
6402             return;
6403         }
6404         break;
6405
6406       case MachinePlaysBlack:
6407         /* User is moving for White */
6408         if (!WhiteOnMove(currentMove)) {
6409             DisplayMoveError(_("It is Black's turn"));
6410             return;
6411         }
6412         break;
6413
6414       case EditGame:
6415       case IcsExamining:
6416       case BeginningOfGame:
6417       case AnalyzeMode:
6418       case Training:
6419         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6420         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6421             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6422             /* User is moving for Black */
6423             if (WhiteOnMove(currentMove)) {
6424                 DisplayMoveError(_("It is White's turn"));
6425                 return;
6426             }
6427         } else {
6428             /* User is moving for White */
6429             if (!WhiteOnMove(currentMove)) {
6430                 DisplayMoveError(_("It is Black's turn"));
6431                 return;
6432             }
6433         }
6434         break;
6435
6436       case IcsPlayingBlack:
6437         /* User is moving for Black */
6438         if (WhiteOnMove(currentMove)) {
6439             if (!appData.premove) {
6440                 DisplayMoveError(_("It is White's turn"));
6441             } else if (toX >= 0 && toY >= 0) {
6442                 premoveToX = toX;
6443                 premoveToY = toY;
6444                 premoveFromX = fromX;
6445                 premoveFromY = fromY;
6446                 premovePromoChar = promoChar;
6447                 gotPremove = 1;
6448                 if (appData.debugMode)
6449                     fprintf(debugFP, "Got premove: fromX %d,"
6450                             "fromY %d, toX %d, toY %d\n",
6451                             fromX, fromY, toX, toY);
6452             }
6453             return;
6454         }
6455         break;
6456
6457       case IcsPlayingWhite:
6458         /* User is moving for White */
6459         if (!WhiteOnMove(currentMove)) {
6460             if (!appData.premove) {
6461                 DisplayMoveError(_("It is Black's turn"));
6462             } else if (toX >= 0 && toY >= 0) {
6463                 premoveToX = toX;
6464                 premoveToY = toY;
6465                 premoveFromX = fromX;
6466                 premoveFromY = fromY;
6467                 premovePromoChar = promoChar;
6468                 gotPremove = 1;
6469                 if (appData.debugMode)
6470                     fprintf(debugFP, "Got premove: fromX %d,"
6471                             "fromY %d, toX %d, toY %d\n",
6472                             fromX, fromY, toX, toY);
6473             }
6474             return;
6475         }
6476         break;
6477
6478       default:
6479         break;
6480
6481       case EditPosition:
6482         /* EditPosition, empty square, or different color piece;
6483            click-click move is possible */
6484         if (toX == -2 || toY == -2) {
6485             boards[0][fromY][fromX] = EmptySquare;
6486             DrawPosition(FALSE, boards[currentMove]);
6487             return;
6488         } else if (toX >= 0 && toY >= 0) {
6489             boards[0][toY][toX] = boards[0][fromY][fromX];
6490             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6491                 if(boards[0][fromY][0] != EmptySquare) {
6492                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6493                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6494                 }
6495             } else
6496             if(fromX == BOARD_RGHT+1) {
6497                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6498                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6499                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6500                 }
6501             } else
6502             boards[0][fromY][fromX] = EmptySquare;
6503             DrawPosition(FALSE, boards[currentMove]);
6504             return;
6505         }
6506         return;
6507     }
6508
6509     if(toX < 0 || toY < 0) return;
6510     pdown = boards[currentMove][fromY][fromX];
6511     pup = boards[currentMove][toY][toX];
6512
6513     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6514     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6515          if( pup != EmptySquare ) return;
6516          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6517            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6518                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6519            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6520            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6521            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6522            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6523          fromY = DROP_RANK;
6524     }
6525
6526     /* [HGM] always test for legality, to get promotion info */
6527     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6528                                          fromY, fromX, toY, toX, promoChar);
6529     /* [HGM] but possibly ignore an IllegalMove result */
6530     if (appData.testLegality) {
6531         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6532             DisplayMoveError(_("Illegal move"));
6533             return;
6534         }
6535     }
6536
6537     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6538 }
6539
6540 /* Common tail of UserMoveEvent and DropMenuEvent */
6541 int
6542 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6543      ChessMove moveType;
6544      int fromX, fromY, toX, toY;
6545      /*char*/int promoChar;
6546 {
6547     char *bookHit = 0;
6548
6549     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6550         // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6551         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6552         if(WhiteOnMove(currentMove)) {
6553             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6554         } else {
6555             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6556         }
6557     }
6558
6559     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6560        move type in caller when we know the move is a legal promotion */
6561     if(moveType == NormalMove && promoChar)
6562         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6563
6564     /* [HGM] <popupFix> The following if has been moved here from
6565        UserMoveEvent(). Because it seemed to belong here (why not allow
6566        piece drops in training games?), and because it can only be
6567        performed after it is known to what we promote. */
6568     if (gameMode == Training) {
6569       /* compare the move played on the board to the next move in the
6570        * game. If they match, display the move and the opponent's response.
6571        * If they don't match, display an error message.
6572        */
6573       int saveAnimate;
6574       Board testBoard;
6575       CopyBoard(testBoard, boards[currentMove]);
6576       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6577
6578       if (CompareBoards(testBoard, boards[currentMove+1])) {
6579         ForwardInner(currentMove+1);
6580
6581         /* Autoplay the opponent's response.
6582          * if appData.animate was TRUE when Training mode was entered,
6583          * the response will be animated.
6584          */
6585         saveAnimate = appData.animate;
6586         appData.animate = animateTraining;
6587         ForwardInner(currentMove+1);
6588         appData.animate = saveAnimate;
6589
6590         /* check for the end of the game */
6591         if (currentMove >= forwardMostMove) {
6592           gameMode = PlayFromGameFile;
6593           ModeHighlight();
6594           SetTrainingModeOff();
6595           DisplayInformation(_("End of game"));
6596         }
6597       } else {
6598         DisplayError(_("Incorrect move"), 0);
6599       }
6600       return 1;
6601     }
6602
6603   /* Ok, now we know that the move is good, so we can kill
6604      the previous line in Analysis Mode */
6605   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6606                                 && currentMove < forwardMostMove) {
6607     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6608     else forwardMostMove = currentMove;
6609   }
6610
6611   /* If we need the chess program but it's dead, restart it */
6612   ResurrectChessProgram();
6613
6614   /* A user move restarts a paused game*/
6615   if (pausing)
6616     PauseEvent();
6617
6618   thinkOutput[0] = NULLCHAR;
6619
6620   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6621
6622   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6623     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6624     return 1;
6625   }
6626
6627   if (gameMode == BeginningOfGame) {
6628     if (appData.noChessProgram) {
6629       gameMode = EditGame;
6630       SetGameInfo();
6631     } else {
6632       char buf[MSG_SIZ];
6633       gameMode = MachinePlaysBlack;
6634       StartClocks();
6635       SetGameInfo();
6636       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6637       DisplayTitle(buf);
6638       if (first.sendName) {
6639         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6640         SendToProgram(buf, &first);
6641       }
6642       StartClocks();
6643     }
6644     ModeHighlight();
6645   }
6646
6647   /* Relay move to ICS or chess engine */
6648   if (appData.icsActive) {
6649     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6650         gameMode == IcsExamining) {
6651       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6652         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6653         SendToICS("draw ");
6654         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6655       }
6656       // also send plain move, in case ICS does not understand atomic claims
6657       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6658       ics_user_moved = 1;
6659     }
6660   } else {
6661     if (first.sendTime && (gameMode == BeginningOfGame ||
6662                            gameMode == MachinePlaysWhite ||
6663                            gameMode == MachinePlaysBlack)) {
6664       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6665     }
6666     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6667          // [HGM] book: if program might be playing, let it use book
6668         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6669         first.maybeThinking = TRUE;
6670     } else SendMoveToProgram(forwardMostMove-1, &first);
6671     if (currentMove == cmailOldMove + 1) {
6672       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6673     }
6674   }
6675
6676   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6677
6678   switch (gameMode) {
6679   case EditGame:
6680     if(appData.testLegality)
6681     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6682     case MT_NONE:
6683     case MT_CHECK:
6684       break;
6685     case MT_CHECKMATE:
6686     case MT_STAINMATE:
6687       if (WhiteOnMove(currentMove)) {
6688         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6689       } else {
6690         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6691       }
6692       break;
6693     case MT_STALEMATE:
6694       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6695       break;
6696     }
6697     break;
6698
6699   case MachinePlaysBlack:
6700   case MachinePlaysWhite:
6701     /* disable certain menu options while machine is thinking */
6702     SetMachineThinkingEnables();
6703     break;
6704
6705   default:
6706     break;
6707   }
6708
6709   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6710   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6711
6712   if(bookHit) { // [HGM] book: simulate book reply
6713         static char bookMove[MSG_SIZ]; // a bit generous?
6714
6715         programStats.nodes = programStats.depth = programStats.time =
6716         programStats.score = programStats.got_only_move = 0;
6717         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6718
6719         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6720         strcat(bookMove, bookHit);
6721         HandleMachineMove(bookMove, &first);
6722   }
6723   return 1;
6724 }
6725
6726 void
6727 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6728      Board board;
6729      int flags;
6730      ChessMove kind;
6731      int rf, ff, rt, ft;
6732      VOIDSTAR closure;
6733 {
6734     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6735     Markers *m = (Markers *) closure;
6736     if(rf == fromY && ff == fromX)
6737         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6738                          || kind == WhiteCapturesEnPassant
6739                          || kind == BlackCapturesEnPassant);
6740     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6741 }
6742
6743 void
6744 MarkTargetSquares(int clear)
6745 {
6746   int x, y;
6747   if(!appData.markers || !appData.highlightDragging ||
6748      !appData.testLegality || gameMode == EditPosition) return;
6749   if(clear) {
6750     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6751   } else {
6752     int capt = 0;
6753     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6754     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6755       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6756       if(capt)
6757       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6758     }
6759   }
6760   DrawPosition(TRUE, NULL);
6761 }
6762
6763 int
6764 Explode(Board board, int fromX, int fromY, int toX, int toY)
6765 {
6766     if(gameInfo.variant == VariantAtomic &&
6767        (board[toY][toX] != EmptySquare ||                     // capture?
6768         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6769                          board[fromY][fromX] == BlackPawn   )
6770       )) {
6771         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6772         return TRUE;
6773     }
6774     return FALSE;
6775 }
6776
6777 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6778
6779 int CanPromote(ChessSquare piece, int y)
6780 {
6781         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6782         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6783         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6784            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6785            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6786                                                   gameInfo.variant == VariantMakruk) return FALSE;
6787         return (piece == BlackPawn && y == 1 ||
6788                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6789                 piece == BlackLance && y == 1 ||
6790                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6791 }
6792
6793 void LeftClick(ClickType clickType, int xPix, int yPix)
6794 {
6795     int x, y;
6796     Boolean saveAnimate;
6797     static int second = 0, promotionChoice = 0, clearFlag = 0;
6798     char promoChoice = NULLCHAR;
6799     ChessSquare piece;
6800
6801     if(appData.seekGraph && appData.icsActive && loggedOn &&
6802         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6803         SeekGraphClick(clickType, xPix, yPix, 0);
6804         return;
6805     }
6806
6807     if (clickType == Press) ErrorPopDown();
6808
6809     x = EventToSquare(xPix, BOARD_WIDTH);
6810     y = EventToSquare(yPix, BOARD_HEIGHT);
6811     if (!flipView && y >= 0) {
6812         y = BOARD_HEIGHT - 1 - y;
6813     }
6814     if (flipView && x >= 0) {
6815         x = BOARD_WIDTH - 1 - x;
6816     }
6817
6818     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6819         defaultPromoChoice = promoSweep;
6820         promoSweep = EmptySquare;   // terminate sweep
6821         promoDefaultAltered = TRUE;
6822         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6823     }
6824
6825     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6826         if(clickType == Release) return; // ignore upclick of click-click destination
6827         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6828         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6829         if(gameInfo.holdingsWidth &&
6830                 (WhiteOnMove(currentMove)
6831                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6832                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6833             // click in right holdings, for determining promotion piece
6834             ChessSquare p = boards[currentMove][y][x];
6835             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6836             if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6837             if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6838                 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6839                 fromX = fromY = -1;
6840                 return;
6841             }
6842         }
6843         DrawPosition(FALSE, boards[currentMove]);
6844         return;
6845     }
6846
6847     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6848     if(clickType == Press
6849             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6850               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6851               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6852         return;
6853
6854     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6855         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6856
6857     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6858         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6859                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6860         defaultPromoChoice = DefaultPromoChoice(side);
6861     }
6862
6863     autoQueen = appData.alwaysPromoteToQueen;
6864
6865     if (fromX == -1) {
6866       int originalY = y;
6867       gatingPiece = EmptySquare;
6868       if (clickType != Press) {
6869         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6870             DragPieceEnd(xPix, yPix); dragging = 0;
6871             DrawPosition(FALSE, NULL);
6872         }
6873         return;
6874       }
6875       fromX = x; fromY = y;
6876       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6877          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6878          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6879             /* First square */
6880             if (OKToStartUserMove(fromX, fromY)) {
6881                 second = 0;
6882                 MarkTargetSquares(0);
6883                 DragPieceBegin(xPix, yPix); dragging = 1;
6884                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6885                     promoSweep = defaultPromoChoice;
6886                     selectFlag = 0; lastX = xPix; lastY = yPix;
6887                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6888                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6889                 }
6890                 if (appData.highlightDragging) {
6891                     SetHighlights(fromX, fromY, -1, -1);
6892                 }
6893             } else fromX = fromY = -1;
6894             return;
6895         }
6896     }
6897
6898     /* fromX != -1 */
6899     if (clickType == Press && gameMode != EditPosition) {
6900         ChessSquare fromP;
6901         ChessSquare toP;
6902         int frc;
6903
6904         // ignore off-board to clicks
6905         if(y < 0 || x < 0) return;
6906
6907         /* Check if clicking again on the same color piece */
6908         fromP = boards[currentMove][fromY][fromX];
6909         toP = boards[currentMove][y][x];
6910         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6911         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6912              WhitePawn <= toP && toP <= WhiteKing &&
6913              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6914              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6915             (BlackPawn <= fromP && fromP <= BlackKing &&
6916              BlackPawn <= toP && toP <= BlackKing &&
6917              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6918              !(fromP == BlackKing && toP == BlackRook && frc))) {
6919             /* Clicked again on same color piece -- changed his mind */
6920             second = (x == fromX && y == fromY);
6921             promoDefaultAltered = FALSE;
6922             MarkTargetSquares(1);
6923            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6924             if (appData.highlightDragging) {
6925                 SetHighlights(x, y, -1, -1);
6926             } else {
6927                 ClearHighlights();
6928             }
6929             if (OKToStartUserMove(x, y)) {
6930                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6931                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6932                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6933                  gatingPiece = boards[currentMove][fromY][fromX];
6934                 else gatingPiece = EmptySquare;
6935                 fromX = x;
6936                 fromY = y; dragging = 1;
6937                 MarkTargetSquares(0);
6938                 DragPieceBegin(xPix, yPix);
6939                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6940                     promoSweep = defaultPromoChoice;
6941                     selectFlag = 0; lastX = xPix; lastY = yPix;
6942                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6943                 }
6944             }
6945            }
6946            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6947            second = FALSE; 
6948         }
6949         // ignore clicks on holdings
6950         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6951     }
6952
6953     if (clickType == Release && x == fromX && y == fromY) {
6954         DragPieceEnd(xPix, yPix); dragging = 0;
6955         if(clearFlag) {
6956             // a deferred attempt to click-click move an empty square on top of a piece
6957             boards[currentMove][y][x] = EmptySquare;
6958             ClearHighlights();
6959             DrawPosition(FALSE, boards[currentMove]);
6960             fromX = fromY = -1; clearFlag = 0;
6961             return;
6962         }
6963         if (appData.animateDragging) {
6964             /* Undo animation damage if any */
6965             DrawPosition(FALSE, NULL);
6966         }
6967         if (second) {
6968             /* Second up/down in same square; just abort move */
6969             second = 0;
6970             fromX = fromY = -1;
6971             gatingPiece = EmptySquare;
6972             ClearHighlights();
6973             gotPremove = 0;
6974             ClearPremoveHighlights();
6975         } else {
6976             /* First upclick in same square; start click-click mode */
6977             SetHighlights(x, y, -1, -1);
6978         }
6979         return;
6980     }
6981
6982     clearFlag = 0;
6983
6984     /* we now have a different from- and (possibly off-board) to-square */
6985     /* Completed move */
6986     toX = x;
6987     toY = y;
6988     saveAnimate = appData.animate;
6989     MarkTargetSquares(1);
6990     if (clickType == Press) {
6991         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6992             // must be Edit Position mode with empty-square selected
6993             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6994             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6995             return;
6996         }
6997         /* Finish clickclick move */
6998         if (appData.animate || appData.highlightLastMove) {
6999             SetHighlights(fromX, fromY, toX, toY);
7000         } else {
7001             ClearHighlights();
7002         }
7003     } else {
7004         /* Finish drag move */
7005         if (appData.highlightLastMove) {
7006             SetHighlights(fromX, fromY, toX, toY);
7007         } else {
7008             ClearHighlights();
7009         }
7010         DragPieceEnd(xPix, yPix); dragging = 0;
7011         /* Don't animate move and drag both */
7012         appData.animate = FALSE;
7013     }
7014
7015     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7016     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7017         ChessSquare piece = boards[currentMove][fromY][fromX];
7018         if(gameMode == EditPosition && piece != EmptySquare &&
7019            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7020             int n;
7021
7022             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7023                 n = PieceToNumber(piece - (int)BlackPawn);
7024                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7025                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7026                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7027             } else
7028             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7029                 n = PieceToNumber(piece);
7030                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7031                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7032                 boards[currentMove][n][BOARD_WIDTH-2]++;
7033             }
7034             boards[currentMove][fromY][fromX] = EmptySquare;
7035         }
7036         ClearHighlights();
7037         fromX = fromY = -1;
7038         DrawPosition(TRUE, boards[currentMove]);
7039         return;
7040     }
7041
7042     // off-board moves should not be highlighted
7043     if(x < 0 || y < 0) ClearHighlights();
7044
7045     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7046
7047     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
7048         SetHighlights(fromX, fromY, toX, toY);
7049         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7050             // [HGM] super: promotion to captured piece selected from holdings
7051             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7052             promotionChoice = TRUE;
7053             // kludge follows to temporarily execute move on display, without promoting yet
7054             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7055             boards[currentMove][toY][toX] = p;
7056             DrawPosition(FALSE, boards[currentMove]);
7057             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7058             boards[currentMove][toY][toX] = q;
7059             DisplayMessage("Click in holdings to choose piece", "");
7060             return;
7061         }
7062         PromotionPopUp();
7063     } else {
7064         int oldMove = currentMove;
7065         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7066         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7067         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7068         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7069            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7070             DrawPosition(TRUE, boards[currentMove]);
7071         fromX = fromY = -1;
7072     }
7073     appData.animate = saveAnimate;
7074     if (appData.animate || appData.animateDragging) {
7075         /* Undo animation damage if needed */
7076         DrawPosition(FALSE, NULL);
7077     }
7078 }
7079
7080 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7081 {   // front-end-free part taken out of PieceMenuPopup
7082     int whichMenu; int xSqr, ySqr;
7083
7084     if(seekGraphUp) { // [HGM] seekgraph
7085         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7086         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7087         return -2;
7088     }
7089
7090     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7091          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7092         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7093         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7094         if(action == Press)   {
7095             originalFlip = flipView;
7096             flipView = !flipView; // temporarily flip board to see game from partners perspective
7097             DrawPosition(TRUE, partnerBoard);
7098             DisplayMessage(partnerStatus, "");
7099             partnerUp = TRUE;
7100         } else if(action == Release) {
7101             flipView = originalFlip;
7102             DrawPosition(TRUE, boards[currentMove]);
7103             partnerUp = FALSE;
7104         }
7105         return -2;
7106     }
7107
7108     xSqr = EventToSquare(x, BOARD_WIDTH);
7109     ySqr = EventToSquare(y, BOARD_HEIGHT);
7110     if (action == Release) {
7111         if(pieceSweep != EmptySquare) {
7112             EditPositionMenuEvent(pieceSweep, toX, toY);
7113             pieceSweep = EmptySquare;
7114         } else UnLoadPV(); // [HGM] pv
7115     }
7116     if (action != Press) return -2; // return code to be ignored
7117     switch (gameMode) {
7118       case IcsExamining:
7119         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7120       case EditPosition:
7121         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7122         if (xSqr < 0 || ySqr < 0) return -1;
7123         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7124         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7125         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7126         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7127         NextPiece(0);
7128         return -2;
7129       case IcsObserving:
7130         if(!appData.icsEngineAnalyze) return -1;
7131       case IcsPlayingWhite:
7132       case IcsPlayingBlack:
7133         if(!appData.zippyPlay) goto noZip;
7134       case AnalyzeMode:
7135       case AnalyzeFile:
7136       case MachinePlaysWhite:
7137       case MachinePlaysBlack:
7138       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7139         if (!appData.dropMenu) {
7140           LoadPV(x, y);
7141           return 2; // flag front-end to grab mouse events
7142         }
7143         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7144            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7145       case EditGame:
7146       noZip:
7147         if (xSqr < 0 || ySqr < 0) return -1;
7148         if (!appData.dropMenu || appData.testLegality &&
7149             gameInfo.variant != VariantBughouse &&
7150             gameInfo.variant != VariantCrazyhouse) return -1;
7151         whichMenu = 1; // drop menu
7152         break;
7153       default:
7154         return -1;
7155     }
7156
7157     if (((*fromX = xSqr) < 0) ||
7158         ((*fromY = ySqr) < 0)) {
7159         *fromX = *fromY = -1;
7160         return -1;
7161     }
7162     if (flipView)
7163       *fromX = BOARD_WIDTH - 1 - *fromX;
7164     else
7165       *fromY = BOARD_HEIGHT - 1 - *fromY;
7166
7167     return whichMenu;
7168 }
7169
7170 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7171 {
7172 //    char * hint = lastHint;
7173     FrontEndProgramStats stats;
7174
7175     stats.which = cps == &first ? 0 : 1;
7176     stats.depth = cpstats->depth;
7177     stats.nodes = cpstats->nodes;
7178     stats.score = cpstats->score;
7179     stats.time = cpstats->time;
7180     stats.pv = cpstats->movelist;
7181     stats.hint = lastHint;
7182     stats.an_move_index = 0;
7183     stats.an_move_count = 0;
7184
7185     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7186         stats.hint = cpstats->move_name;
7187         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7188         stats.an_move_count = cpstats->nr_moves;
7189     }
7190
7191     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
7192
7193     SetProgramStats( &stats );
7194 }
7195
7196 void
7197 ClearEngineOutputPane(int which)
7198 {
7199     static FrontEndProgramStats dummyStats;
7200     dummyStats.which = which;
7201     dummyStats.pv = "#";
7202     SetProgramStats( &dummyStats );
7203 }
7204
7205 #define MAXPLAYERS 500
7206
7207 char *
7208 TourneyStandings(int display)
7209 {
7210     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7211     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7212     char result, *p, *names[MAXPLAYERS];
7213
7214     if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7215         return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7216     names[0] = p = strdup(appData.participants);
7217     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7218
7219     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7220
7221     while(result = appData.results[nr]) {
7222         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7223         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7224         wScore = bScore = 0;
7225         switch(result) {
7226           case '+': wScore = 2; break;
7227           case '-': bScore = 2; break;
7228           case '=': wScore = bScore = 1; break;
7229           case ' ':
7230           case '*': return strdup("busy"); // tourney not finished
7231         }
7232         score[w] += wScore;
7233         score[b] += bScore;
7234         games[w]++;
7235         games[b]++;
7236         nr++;
7237     }
7238     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7239     for(w=0; w<nPlayers; w++) {
7240         bScore = -1;
7241         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7242         ranking[w] = b; points[w] = bScore; score[b] = -2;
7243     }
7244     p = malloc(nPlayers*34+1);
7245     for(w=0; w<nPlayers && w<display; w++)
7246         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7247     free(names[0]);
7248     return p;
7249 }
7250
7251 void
7252 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7253 {       // count all piece types
7254         int p, f, r;
7255         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7256         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7257         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7258                 p = board[r][f];
7259                 pCnt[p]++;
7260                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7261                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7262                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7263                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7264                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7265                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7266         }
7267 }
7268
7269 int
7270 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7271 {
7272         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7273         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7274
7275         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7276         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7277         if(myPawns == 2 && nMine == 3) // KPP
7278             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7279         if(myPawns == 1 && nMine == 2) // KP
7280             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7281         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7282             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7283         if(myPawns) return FALSE;
7284         if(pCnt[WhiteRook+side])
7285             return pCnt[BlackRook-side] ||
7286                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7287                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7288                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7289         if(pCnt[WhiteCannon+side]) {
7290             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7291             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7292         }
7293         if(pCnt[WhiteKnight+side])
7294             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7295         return FALSE;
7296 }
7297
7298 int
7299 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7300 {
7301         VariantClass v = gameInfo.variant;
7302
7303         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7304         if(v == VariantShatranj) return TRUE; // always winnable through baring
7305         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7306         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7307
7308         if(v == VariantXiangqi) {
7309                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7310
7311                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7312                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7313                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7314                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7315                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7316                 if(stale) // we have at least one last-rank P plus perhaps C
7317                     return majors // KPKX
7318                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7319                 else // KCA*E*
7320                     return pCnt[WhiteFerz+side] // KCAK
7321                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7322                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7323                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7324
7325         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7326                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7327
7328                 if(nMine == 1) return FALSE; // bare King
7329                 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
7330                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7331                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7332                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7333                 if(pCnt[WhiteKnight+side])
7334                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7335                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7336                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7337                 if(nBishops)
7338                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7339                 if(pCnt[WhiteAlfil+side])
7340                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7341                 if(pCnt[WhiteWazir+side])
7342                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7343         }
7344
7345         return TRUE;
7346 }
7347
7348 int
7349 Adjudicate(ChessProgramState *cps)
7350 {       // [HGM] some adjudications useful with buggy engines
7351         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7352         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7353         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7354         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7355         int k, count = 0; static int bare = 1;
7356         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7357         Boolean canAdjudicate = !appData.icsActive;
7358
7359         // most tests only when we understand the game, i.e. legality-checking on
7360             if( appData.testLegality )
7361             {   /* [HGM] Some more adjudications for obstinate engines */
7362                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7363                 static int moveCount = 6;
7364                 ChessMove result;
7365                 char *reason = NULL;
7366
7367                 /* Count what is on board. */
7368                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7369
7370                 /* Some material-based adjudications that have to be made before stalemate test */
7371                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7372                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7373                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7374                      if(canAdjudicate && appData.checkMates) {
7375                          if(engineOpponent)
7376                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7377                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7378                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7379                          return 1;
7380                      }
7381                 }
7382
7383                 /* Bare King in Shatranj (loses) or Losers (wins) */
7384                 if( nrW == 1 || nrB == 1) {
7385                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7386                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7387                      if(canAdjudicate && appData.checkMates) {
7388                          if(engineOpponent)
7389                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7390                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7391                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7392                          return 1;
7393                      }
7394                   } else
7395                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7396                   {    /* bare King */
7397                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7398                         if(canAdjudicate && appData.checkMates) {
7399                             /* but only adjudicate if adjudication enabled */
7400                             if(engineOpponent)
7401                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7402                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7403                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7404                             return 1;
7405                         }
7406                   }
7407                 } else bare = 1;
7408
7409
7410             // don't wait for engine to announce game end if we can judge ourselves
7411             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7412               case MT_CHECK:
7413                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7414                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7415                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7416                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7417                             checkCnt++;
7418                         if(checkCnt >= 2) {
7419                             reason = "Xboard adjudication: 3rd check";
7420                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7421                             break;
7422                         }
7423                     }
7424                 }
7425               case MT_NONE:
7426               default:
7427                 break;
7428               case MT_STALEMATE:
7429               case MT_STAINMATE:
7430                 reason = "Xboard adjudication: Stalemate";
7431                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7432                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7433                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7434                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7435                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7436                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7437                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7438                                                                         EP_CHECKMATE : EP_WINS);
7439                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7440                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7441                 }
7442                 break;
7443               case MT_CHECKMATE:
7444                 reason = "Xboard adjudication: Checkmate";
7445                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7446                 break;
7447             }
7448
7449                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7450                     case EP_STALEMATE:
7451                         result = GameIsDrawn; break;
7452                     case EP_CHECKMATE:
7453                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7454                     case EP_WINS:
7455                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7456                     default:
7457                         result = EndOfFile;
7458                 }
7459                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7460                     if(engineOpponent)
7461                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7462                     GameEnds( result, reason, GE_XBOARD );
7463                     return 1;
7464                 }
7465
7466                 /* Next absolutely insufficient mating material. */
7467                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7468                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7469                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7470
7471                      /* always flag draws, for judging claims */
7472                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7473
7474                      if(canAdjudicate && appData.materialDraws) {
7475                          /* but only adjudicate them if adjudication enabled */
7476                          if(engineOpponent) {
7477                            SendToProgram("force\n", engineOpponent); // suppress reply
7478                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7479                          }
7480                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7481                          return 1;
7482                      }
7483                 }
7484
7485                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7486                 if(gameInfo.variant == VariantXiangqi ?
7487                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7488                  : nrW + nrB == 4 &&
7489                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7490                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7491                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7492                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7493                    ) ) {
7494                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7495                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7496                           if(engineOpponent) {
7497                             SendToProgram("force\n", engineOpponent); // suppress reply
7498                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7499                           }
7500                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7501                           return 1;
7502                      }
7503                 } else moveCount = 6;
7504             }
7505         if (appData.debugMode) { int i;
7506             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7507                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7508                     appData.drawRepeats);
7509             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7510               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7511
7512         }
7513
7514         // Repetition draws and 50-move rule can be applied independently of legality testing
7515
7516                 /* Check for rep-draws */
7517                 count = 0;
7518                 for(k = forwardMostMove-2;
7519                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7520                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7521                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7522                     k-=2)
7523                 {   int rights=0;
7524                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7525                         /* compare castling rights */
7526                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7527                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7528                                 rights++; /* King lost rights, while rook still had them */
7529                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7530                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7531                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7532                                    rights++; /* but at least one rook lost them */
7533                         }
7534                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7535                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7536                                 rights++;
7537                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7538                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7539                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7540                                    rights++;
7541                         }
7542                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7543                             && appData.drawRepeats > 1) {
7544                              /* adjudicate after user-specified nr of repeats */
7545                              int result = GameIsDrawn;
7546                              char *details = "XBoard adjudication: repetition draw";
7547                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7548                                 // [HGM] xiangqi: check for forbidden perpetuals
7549                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7550                                 for(m=forwardMostMove; m>k; m-=2) {
7551                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7552                                         ourPerpetual = 0; // the current mover did not always check
7553                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7554                                         hisPerpetual = 0; // the opponent did not always check
7555                                 }
7556                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7557                                                                         ourPerpetual, hisPerpetual);
7558                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7559                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7560                                     details = "Xboard adjudication: perpetual checking";
7561                                 } else
7562                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7563                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7564                                 } else
7565                                 // Now check for perpetual chases
7566                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7567                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7568                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7569                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7570                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7571                                         details = "Xboard adjudication: perpetual chasing";
7572                                     } else
7573                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7574                                         break; // Abort repetition-checking loop.
7575                                 }
7576                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7577                              }
7578                              if(engineOpponent) {
7579                                SendToProgram("force\n", engineOpponent); // suppress reply
7580                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7581                              }
7582                              GameEnds( result, details, GE_XBOARD );
7583                              return 1;
7584                         }
7585                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7586                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7587                     }
7588                 }
7589
7590                 /* Now we test for 50-move draws. Determine ply count */
7591                 count = forwardMostMove;
7592                 /* look for last irreversble move */
7593                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7594                     count--;
7595                 /* if we hit starting position, add initial plies */
7596                 if( count == backwardMostMove )
7597                     count -= initialRulePlies;
7598                 count = forwardMostMove - count;
7599                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7600                         // adjust reversible move counter for checks in Xiangqi
7601                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7602                         if(i < backwardMostMove) i = backwardMostMove;
7603                         while(i <= forwardMostMove) {
7604                                 lastCheck = inCheck; // check evasion does not count
7605                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7606                                 if(inCheck || lastCheck) count--; // check does not count
7607                                 i++;
7608                         }
7609                 }
7610                 if( count >= 100)
7611                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7612                          /* this is used to judge if draw claims are legal */
7613                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7614                          if(engineOpponent) {
7615                            SendToProgram("force\n", engineOpponent); // suppress reply
7616                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7617                          }
7618                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7619                          return 1;
7620                 }
7621
7622                 /* if draw offer is pending, treat it as a draw claim
7623                  * when draw condition present, to allow engines a way to
7624                  * claim draws before making their move to avoid a race
7625                  * condition occurring after their move
7626                  */
7627                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7628                          char *p = NULL;
7629                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7630                              p = "Draw claim: 50-move rule";
7631                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7632                              p = "Draw claim: 3-fold repetition";
7633                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7634                              p = "Draw claim: insufficient mating material";
7635                          if( p != NULL && canAdjudicate) {
7636                              if(engineOpponent) {
7637                                SendToProgram("force\n", engineOpponent); // suppress reply
7638                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7639                              }
7640                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7641                              return 1;
7642                          }
7643                 }
7644
7645                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7646                     if(engineOpponent) {
7647                       SendToProgram("force\n", engineOpponent); // suppress reply
7648                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7649                     }
7650                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7651                     return 1;
7652                 }
7653         return 0;
7654 }
7655
7656 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7657 {   // [HGM] book: this routine intercepts moves to simulate book replies
7658     char *bookHit = NULL;
7659
7660     //first determine if the incoming move brings opponent into his book
7661     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7662         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7663     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7664     if(bookHit != NULL && !cps->bookSuspend) {
7665         // make sure opponent is not going to reply after receiving move to book position
7666         SendToProgram("force\n", cps);
7667         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7668     }
7669     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7670     // now arrange restart after book miss
7671     if(bookHit) {
7672         // after a book hit we never send 'go', and the code after the call to this routine
7673         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7674         char buf[MSG_SIZ], *move = bookHit;
7675         if(cps->useSAN) {
7676             int fromX, fromY, toX, toY;
7677             char promoChar;
7678             ChessMove moveType;
7679             move = buf + 30;
7680             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7681                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7682                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7683                                     PosFlags(forwardMostMove),
7684                                     fromY, fromX, toY, toX, promoChar, move);
7685             } else {
7686                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7687                 bookHit = NULL;
7688             }
7689         }
7690         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7691         SendToProgram(buf, cps);
7692         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7693     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7694         SendToProgram("go\n", cps);
7695         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7696     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7697         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7698             SendToProgram("go\n", cps);
7699         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7700     }
7701     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7702 }
7703
7704 char *savedMessage;
7705 ChessProgramState *savedState;
7706 void DeferredBookMove(void)
7707 {
7708         if(savedState->lastPing != savedState->lastPong)
7709                     ScheduleDelayedEvent(DeferredBookMove, 10);
7710         else
7711         HandleMachineMove(savedMessage, savedState);
7712 }
7713
7714 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7715
7716 void
7717 HandleMachineMove(message, cps)
7718      char *message;
7719      ChessProgramState *cps;
7720 {
7721     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7722     char realname[MSG_SIZ];
7723     int fromX, fromY, toX, toY;
7724     ChessMove moveType;
7725     char promoChar;
7726     char *p, *pv=buf1;
7727     int machineWhite;
7728     char *bookHit;
7729
7730     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7731         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7732         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7733             DisplayError(_("Invalid pairing from pairing engine"), 0);
7734             return;
7735         }
7736         pairingReceived = 1;
7737         NextMatchGame();
7738         return; // Skim the pairing messages here.
7739     }
7740
7741     cps->userError = 0;
7742
7743 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7744     /*
7745      * Kludge to ignore BEL characters
7746      */
7747     while (*message == '\007') message++;
7748
7749     /*
7750      * [HGM] engine debug message: ignore lines starting with '#' character
7751      */
7752     if(cps->debug && *message == '#') return;
7753
7754     /*
7755      * Look for book output
7756      */
7757     if (cps == &first && bookRequested) {
7758         if (message[0] == '\t' || message[0] == ' ') {
7759             /* Part of the book output is here; append it */
7760             strcat(bookOutput, message);
7761             strcat(bookOutput, "  \n");
7762             return;
7763         } else if (bookOutput[0] != NULLCHAR) {
7764             /* All of book output has arrived; display it */
7765             char *p = bookOutput;
7766             while (*p != NULLCHAR) {
7767                 if (*p == '\t') *p = ' ';
7768                 p++;
7769             }
7770             DisplayInformation(bookOutput);
7771             bookRequested = FALSE;
7772             /* Fall through to parse the current output */
7773         }
7774     }
7775
7776     /*
7777      * Look for machine move.
7778      */
7779     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7780         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7781     {
7782         /* This method is only useful on engines that support ping */
7783         if (cps->lastPing != cps->lastPong) {
7784           if (gameMode == BeginningOfGame) {
7785             /* Extra move from before last new; ignore */
7786             if (appData.debugMode) {
7787                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7788             }
7789           } else {
7790             if (appData.debugMode) {
7791                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7792                         cps->which, gameMode);
7793             }
7794
7795             SendToProgram("undo\n", cps);
7796           }
7797           return;
7798         }
7799
7800         switch (gameMode) {
7801           case BeginningOfGame:
7802             /* Extra move from before last reset; ignore */
7803             if (appData.debugMode) {
7804                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7805             }
7806             return;
7807
7808           case EndOfGame:
7809           case IcsIdle:
7810           default:
7811             /* Extra move after we tried to stop.  The mode test is
7812                not a reliable way of detecting this problem, but it's
7813                the best we can do on engines that don't support ping.
7814             */
7815             if (appData.debugMode) {
7816                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7817                         cps->which, gameMode);
7818             }
7819             SendToProgram("undo\n", cps);
7820             return;
7821
7822           case MachinePlaysWhite:
7823           case IcsPlayingWhite:
7824             machineWhite = TRUE;
7825             break;
7826
7827           case MachinePlaysBlack:
7828           case IcsPlayingBlack:
7829             machineWhite = FALSE;
7830             break;
7831
7832           case TwoMachinesPlay:
7833             machineWhite = (cps->twoMachinesColor[0] == 'w');
7834             break;
7835         }
7836         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7837             if (appData.debugMode) {
7838                 fprintf(debugFP,
7839                         "Ignoring move out of turn by %s, gameMode %d"
7840                         ", forwardMost %d\n",
7841                         cps->which, gameMode, forwardMostMove);
7842             }
7843             return;
7844         }
7845
7846     if (appData.debugMode) { int f = forwardMostMove;
7847         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7848                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7849                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7850     }
7851         if(cps->alphaRank) AlphaRank(machineMove, 4);
7852         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7853                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7854             /* Machine move could not be parsed; ignore it. */
7855           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7856                     machineMove, _(cps->which));
7857             DisplayError(buf1, 0);
7858             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7859                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7860             if (gameMode == TwoMachinesPlay) {
7861               GameEnds(machineWhite ? BlackWins : WhiteWins,
7862                        buf1, GE_XBOARD);
7863             }
7864             return;
7865         }
7866
7867         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7868         /* So we have to redo legality test with true e.p. status here,  */
7869         /* to make sure an illegal e.p. capture does not slip through,   */
7870         /* to cause a forfeit on a justified illegal-move complaint      */
7871         /* of the opponent.                                              */
7872         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7873            ChessMove moveType;
7874            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7875                              fromY, fromX, toY, toX, promoChar);
7876             if (appData.debugMode) {
7877                 int i;
7878                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7879                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7880                 fprintf(debugFP, "castling rights\n");
7881             }
7882             if(moveType == IllegalMove) {
7883               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7884                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7885                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7886                            buf1, GE_XBOARD);
7887                 return;
7888            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7889            /* [HGM] Kludge to handle engines that send FRC-style castling
7890               when they shouldn't (like TSCP-Gothic) */
7891            switch(moveType) {
7892              case WhiteASideCastleFR:
7893              case BlackASideCastleFR:
7894                toX+=2;
7895                currentMoveString[2]++;
7896                break;
7897              case WhiteHSideCastleFR:
7898              case BlackHSideCastleFR:
7899                toX--;
7900                currentMoveString[2]--;
7901                break;
7902              default: ; // nothing to do, but suppresses warning of pedantic compilers
7903            }
7904         }
7905         hintRequested = FALSE;
7906         lastHint[0] = NULLCHAR;
7907         bookRequested = FALSE;
7908         /* Program may be pondering now */
7909         cps->maybeThinking = TRUE;
7910         if (cps->sendTime == 2) cps->sendTime = 1;
7911         if (cps->offeredDraw) cps->offeredDraw--;
7912
7913         /* [AS] Save move info*/
7914         pvInfoList[ forwardMostMove ].score = programStats.score;
7915         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7916         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7917
7918         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7919
7920         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7921         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7922             int count = 0;
7923
7924             while( count < adjudicateLossPlies ) {
7925                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7926
7927                 if( count & 1 ) {
7928                     score = -score; /* Flip score for winning side */
7929                 }
7930
7931                 if( score > adjudicateLossThreshold ) {
7932                     break;
7933                 }
7934
7935                 count++;
7936             }
7937
7938             if( count >= adjudicateLossPlies ) {
7939                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7940
7941                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7942                     "Xboard adjudication",
7943                     GE_XBOARD );
7944
7945                 return;
7946             }
7947         }
7948
7949         if(Adjudicate(cps)) {
7950             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7951             return; // [HGM] adjudicate: for all automatic game ends
7952         }
7953
7954 #if ZIPPY
7955         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7956             first.initDone) {
7957           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7958                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7959                 SendToICS("draw ");
7960                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7961           }
7962           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7963           ics_user_moved = 1;
7964           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7965                 char buf[3*MSG_SIZ];
7966
7967                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7968                         programStats.score / 100.,
7969                         programStats.depth,
7970                         programStats.time / 100.,
7971                         (unsigned int)programStats.nodes,
7972                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7973                         programStats.movelist);
7974                 SendToICS(buf);
7975 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7976           }
7977         }
7978 #endif
7979
7980         /* [AS] Clear stats for next move */
7981         ClearProgramStats();
7982         thinkOutput[0] = NULLCHAR;
7983         hiddenThinkOutputState = 0;
7984
7985         bookHit = NULL;
7986         if (gameMode == TwoMachinesPlay) {
7987             /* [HGM] relaying draw offers moved to after reception of move */
7988             /* and interpreting offer as claim if it brings draw condition */
7989             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7990                 SendToProgram("draw\n", cps->other);
7991             }
7992             if (cps->other->sendTime) {
7993                 SendTimeRemaining(cps->other,
7994                                   cps->other->twoMachinesColor[0] == 'w');
7995             }
7996             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7997             if (firstMove && !bookHit) {
7998                 firstMove = FALSE;
7999                 if (cps->other->useColors) {
8000                   SendToProgram(cps->other->twoMachinesColor, cps->other);
8001                 }
8002                 SendToProgram("go\n", cps->other);
8003             }
8004             cps->other->maybeThinking = TRUE;
8005         }
8006
8007         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8008
8009         if (!pausing && appData.ringBellAfterMoves) {
8010             RingBell();
8011         }
8012
8013         /*
8014          * Reenable menu items that were disabled while
8015          * machine was thinking
8016          */
8017         if (gameMode != TwoMachinesPlay)
8018             SetUserThinkingEnables();
8019
8020         // [HGM] book: after book hit opponent has received move and is now in force mode
8021         // force the book reply into it, and then fake that it outputted this move by jumping
8022         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8023         if(bookHit) {
8024                 static char bookMove[MSG_SIZ]; // a bit generous?
8025
8026                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8027                 strcat(bookMove, bookHit);
8028                 message = bookMove;
8029                 cps = cps->other;
8030                 programStats.nodes = programStats.depth = programStats.time =
8031                 programStats.score = programStats.got_only_move = 0;
8032                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8033
8034                 if(cps->lastPing != cps->lastPong) {
8035                     savedMessage = message; // args for deferred call
8036                     savedState = cps;
8037                     ScheduleDelayedEvent(DeferredBookMove, 10);
8038                     return;
8039                 }
8040                 goto FakeBookMove;
8041         }
8042
8043         return;
8044     }
8045
8046     /* Set special modes for chess engines.  Later something general
8047      *  could be added here; for now there is just one kludge feature,
8048      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
8049      *  when "xboard" is given as an interactive command.
8050      */
8051     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8052         cps->useSigint = FALSE;
8053         cps->useSigterm = FALSE;
8054     }
8055     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8056       ParseFeatures(message+8, cps);
8057       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8058     }
8059
8060     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8061       int dummy, s=6; char buf[MSG_SIZ];
8062       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8063       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8064       ParseFEN(boards[0], &dummy, message+s);
8065       DrawPosition(TRUE, boards[0]);
8066       startedFromSetupPosition = TRUE;
8067       return;
8068     }
8069     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8070      * want this, I was asked to put it in, and obliged.
8071      */
8072     if (!strncmp(message, "setboard ", 9)) {
8073         Board initial_position;
8074
8075         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8076
8077         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8078             DisplayError(_("Bad FEN received from engine"), 0);
8079             return ;
8080         } else {
8081            Reset(TRUE, FALSE);
8082            CopyBoard(boards[0], initial_position);
8083            initialRulePlies = FENrulePlies;
8084            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8085            else gameMode = MachinePlaysBlack;
8086            DrawPosition(FALSE, boards[currentMove]);
8087         }
8088         return;
8089     }
8090
8091     /*
8092      * Look for communication commands
8093      */
8094     if (!strncmp(message, "telluser ", 9)) {
8095         if(message[9] == '\\' && message[10] == '\\')
8096             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8097         PlayTellSound();
8098         DisplayNote(message + 9);
8099         return;
8100     }
8101     if (!strncmp(message, "tellusererror ", 14)) {
8102         cps->userError = 1;
8103         if(message[14] == '\\' && message[15] == '\\')
8104             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8105         PlayTellSound();
8106         DisplayError(message + 14, 0);
8107         return;
8108     }
8109     if (!strncmp(message, "tellopponent ", 13)) {
8110       if (appData.icsActive) {
8111         if (loggedOn) {
8112           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8113           SendToICS(buf1);
8114         }
8115       } else {
8116         DisplayNote(message + 13);
8117       }
8118       return;
8119     }
8120     if (!strncmp(message, "tellothers ", 11)) {
8121       if (appData.icsActive) {
8122         if (loggedOn) {
8123           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8124           SendToICS(buf1);
8125         }
8126       }
8127       return;
8128     }
8129     if (!strncmp(message, "tellall ", 8)) {
8130       if (appData.icsActive) {
8131         if (loggedOn) {
8132           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8133           SendToICS(buf1);
8134         }
8135       } else {
8136         DisplayNote(message + 8);
8137       }
8138       return;
8139     }
8140     if (strncmp(message, "warning", 7) == 0) {
8141         /* Undocumented feature, use tellusererror in new code */
8142         DisplayError(message, 0);
8143         return;
8144     }
8145     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8146         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8147         strcat(realname, " query");
8148         AskQuestion(realname, buf2, buf1, cps->pr);
8149         return;
8150     }
8151     /* Commands from the engine directly to ICS.  We don't allow these to be
8152      *  sent until we are logged on. Crafty kibitzes have been known to
8153      *  interfere with the login process.
8154      */
8155     if (loggedOn) {
8156         if (!strncmp(message, "tellics ", 8)) {
8157             SendToICS(message + 8);
8158             SendToICS("\n");
8159             return;
8160         }
8161         if (!strncmp(message, "tellicsnoalias ", 15)) {
8162             SendToICS(ics_prefix);
8163             SendToICS(message + 15);
8164             SendToICS("\n");
8165             return;
8166         }
8167         /* The following are for backward compatibility only */
8168         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8169             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8170             SendToICS(ics_prefix);
8171             SendToICS(message);
8172             SendToICS("\n");
8173             return;
8174         }
8175     }
8176     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8177         return;
8178     }
8179     /*
8180      * If the move is illegal, cancel it and redraw the board.
8181      * Also deal with other error cases.  Matching is rather loose
8182      * here to accommodate engines written before the spec.
8183      */
8184     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8185         strncmp(message, "Error", 5) == 0) {
8186         if (StrStr(message, "name") ||
8187             StrStr(message, "rating") || StrStr(message, "?") ||
8188             StrStr(message, "result") || StrStr(message, "board") ||
8189             StrStr(message, "bk") || StrStr(message, "computer") ||
8190             StrStr(message, "variant") || StrStr(message, "hint") ||
8191             StrStr(message, "random") || StrStr(message, "depth") ||
8192             StrStr(message, "accepted")) {
8193             return;
8194         }
8195         if (StrStr(message, "protover")) {
8196           /* Program is responding to input, so it's apparently done
8197              initializing, and this error message indicates it is
8198              protocol version 1.  So we don't need to wait any longer
8199              for it to initialize and send feature commands. */
8200           FeatureDone(cps, 1);
8201           cps->protocolVersion = 1;
8202           return;
8203         }
8204         cps->maybeThinking = FALSE;
8205
8206         if (StrStr(message, "draw")) {
8207             /* Program doesn't have "draw" command */
8208             cps->sendDrawOffers = 0;
8209             return;
8210         }
8211         if (cps->sendTime != 1 &&
8212             (StrStr(message, "time") || StrStr(message, "otim"))) {
8213           /* Program apparently doesn't have "time" or "otim" command */
8214           cps->sendTime = 0;
8215           return;
8216         }
8217         if (StrStr(message, "analyze")) {
8218             cps->analysisSupport = FALSE;
8219             cps->analyzing = FALSE;
8220             Reset(FALSE, TRUE);
8221             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8222             DisplayError(buf2, 0);
8223             return;
8224         }
8225         if (StrStr(message, "(no matching move)st")) {
8226           /* Special kludge for GNU Chess 4 only */
8227           cps->stKludge = TRUE;
8228           SendTimeControl(cps, movesPerSession, timeControl,
8229                           timeIncrement, appData.searchDepth,
8230                           searchTime);
8231           return;
8232         }
8233         if (StrStr(message, "(no matching move)sd")) {
8234           /* Special kludge for GNU Chess 4 only */
8235           cps->sdKludge = TRUE;
8236           SendTimeControl(cps, movesPerSession, timeControl,
8237                           timeIncrement, appData.searchDepth,
8238                           searchTime);
8239           return;
8240         }
8241         if (!StrStr(message, "llegal")) {
8242             return;
8243         }
8244         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8245             gameMode == IcsIdle) return;
8246         if (forwardMostMove <= backwardMostMove) return;
8247         if (pausing) PauseEvent();
8248       if(appData.forceIllegal) {
8249             // [HGM] illegal: machine refused move; force position after move into it
8250           SendToProgram("force\n", cps);
8251           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8252                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8253                 // when black is to move, while there might be nothing on a2 or black
8254                 // might already have the move. So send the board as if white has the move.
8255                 // But first we must change the stm of the engine, as it refused the last move
8256                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8257                 if(WhiteOnMove(forwardMostMove)) {
8258                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8259                     SendBoard(cps, forwardMostMove); // kludgeless board
8260                 } else {
8261                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8262                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8263                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8264                 }
8265           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8266             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8267                  gameMode == TwoMachinesPlay)
8268               SendToProgram("go\n", cps);
8269             return;
8270       } else
8271         if (gameMode == PlayFromGameFile) {
8272             /* Stop reading this game file */
8273             gameMode = EditGame;
8274             ModeHighlight();
8275         }
8276         /* [HGM] illegal-move claim should forfeit game when Xboard */
8277         /* only passes fully legal moves                            */
8278         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8279             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8280                                 "False illegal-move claim", GE_XBOARD );
8281             return; // do not take back move we tested as valid
8282         }
8283         currentMove = forwardMostMove-1;
8284         DisplayMove(currentMove-1); /* before DisplayMoveError */
8285         SwitchClocks(forwardMostMove-1); // [HGM] race
8286         DisplayBothClocks();
8287         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8288                 parseList[currentMove], _(cps->which));
8289         DisplayMoveError(buf1);
8290         DrawPosition(FALSE, boards[currentMove]);
8291         return;
8292     }
8293     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8294         /* Program has a broken "time" command that
8295            outputs a string not ending in newline.
8296            Don't use it. */
8297         cps->sendTime = 0;
8298     }
8299
8300     /*
8301      * If chess program startup fails, exit with an error message.
8302      * Attempts to recover here are futile.
8303      */
8304     if ((StrStr(message, "unknown host") != NULL)
8305         || (StrStr(message, "No remote directory") != NULL)
8306         || (StrStr(message, "not found") != NULL)
8307         || (StrStr(message, "No such file") != NULL)
8308         || (StrStr(message, "can't alloc") != NULL)
8309         || (StrStr(message, "Permission denied") != NULL)) {
8310
8311         cps->maybeThinking = FALSE;
8312         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8313                 _(cps->which), cps->program, cps->host, message);
8314         RemoveInputSource(cps->isr);
8315         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8316             if(cps == &first) appData.noChessProgram = TRUE;
8317             DisplayError(buf1, 0);
8318         }
8319         return;
8320     }
8321
8322     /*
8323      * Look for hint output
8324      */
8325     if (sscanf(message, "Hint: %s", buf1) == 1) {
8326         if (cps == &first && hintRequested) {
8327             hintRequested = FALSE;
8328             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8329                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8330                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8331                                     PosFlags(forwardMostMove),
8332                                     fromY, fromX, toY, toX, promoChar, buf1);
8333                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8334                 DisplayInformation(buf2);
8335             } else {
8336                 /* Hint move could not be parsed!? */
8337               snprintf(buf2, sizeof(buf2),
8338                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8339                         buf1, _(cps->which));
8340                 DisplayError(buf2, 0);
8341             }
8342         } else {
8343           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8344         }
8345         return;
8346     }
8347
8348     /*
8349      * Ignore other messages if game is not in progress
8350      */
8351     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8352         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8353
8354     /*
8355      * look for win, lose, draw, or draw offer
8356      */
8357     if (strncmp(message, "1-0", 3) == 0) {
8358         char *p, *q, *r = "";
8359         p = strchr(message, '{');
8360         if (p) {
8361             q = strchr(p, '}');
8362             if (q) {
8363                 *q = NULLCHAR;
8364                 r = p + 1;
8365             }
8366         }
8367         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8368         return;
8369     } else if (strncmp(message, "0-1", 3) == 0) {
8370         char *p, *q, *r = "";
8371         p = strchr(message, '{');
8372         if (p) {
8373             q = strchr(p, '}');
8374             if (q) {
8375                 *q = NULLCHAR;
8376                 r = p + 1;
8377             }
8378         }
8379         /* Kludge for Arasan 4.1 bug */
8380         if (strcmp(r, "Black resigns") == 0) {
8381             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8382             return;
8383         }
8384         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8385         return;
8386     } else if (strncmp(message, "1/2", 3) == 0) {
8387         char *p, *q, *r = "";
8388         p = strchr(message, '{');
8389         if (p) {
8390             q = strchr(p, '}');
8391             if (q) {
8392                 *q = NULLCHAR;
8393                 r = p + 1;
8394             }
8395         }
8396
8397         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8398         return;
8399
8400     } else if (strncmp(message, "White resign", 12) == 0) {
8401         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8402         return;
8403     } else if (strncmp(message, "Black resign", 12) == 0) {
8404         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8405         return;
8406     } else if (strncmp(message, "White matches", 13) == 0 ||
8407                strncmp(message, "Black matches", 13) == 0   ) {
8408         /* [HGM] ignore GNUShogi noises */
8409         return;
8410     } else if (strncmp(message, "White", 5) == 0 &&
8411                message[5] != '(' &&
8412                StrStr(message, "Black") == NULL) {
8413         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8414         return;
8415     } else if (strncmp(message, "Black", 5) == 0 &&
8416                message[5] != '(') {
8417         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8418         return;
8419     } else if (strcmp(message, "resign") == 0 ||
8420                strcmp(message, "computer resigns") == 0) {
8421         switch (gameMode) {
8422           case MachinePlaysBlack:
8423           case IcsPlayingBlack:
8424             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8425             break;
8426           case MachinePlaysWhite:
8427           case IcsPlayingWhite:
8428             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8429             break;
8430           case TwoMachinesPlay:
8431             if (cps->twoMachinesColor[0] == 'w')
8432               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8433             else
8434               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8435             break;
8436           default:
8437             /* can't happen */
8438             break;
8439         }
8440         return;
8441     } else if (strncmp(message, "opponent mates", 14) == 0) {
8442         switch (gameMode) {
8443           case MachinePlaysBlack:
8444           case IcsPlayingBlack:
8445             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8446             break;
8447           case MachinePlaysWhite:
8448           case IcsPlayingWhite:
8449             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8450             break;
8451           case TwoMachinesPlay:
8452             if (cps->twoMachinesColor[0] == 'w')
8453               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8454             else
8455               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8456             break;
8457           default:
8458             /* can't happen */
8459             break;
8460         }
8461         return;
8462     } else if (strncmp(message, "computer mates", 14) == 0) {
8463         switch (gameMode) {
8464           case MachinePlaysBlack:
8465           case IcsPlayingBlack:
8466             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8467             break;
8468           case MachinePlaysWhite:
8469           case IcsPlayingWhite:
8470             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8471             break;
8472           case TwoMachinesPlay:
8473             if (cps->twoMachinesColor[0] == 'w')
8474               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8475             else
8476               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8477             break;
8478           default:
8479             /* can't happen */
8480             break;
8481         }
8482         return;
8483     } else if (strncmp(message, "checkmate", 9) == 0) {
8484         if (WhiteOnMove(forwardMostMove)) {
8485             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8486         } else {
8487             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8488         }
8489         return;
8490     } else if (strstr(message, "Draw") != NULL ||
8491                strstr(message, "game is a draw") != NULL) {
8492         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8493         return;
8494     } else if (strstr(message, "offer") != NULL &&
8495                strstr(message, "draw") != NULL) {
8496 #if ZIPPY
8497         if (appData.zippyPlay && first.initDone) {
8498             /* Relay offer to ICS */
8499             SendToICS(ics_prefix);
8500             SendToICS("draw\n");
8501         }
8502 #endif
8503         cps->offeredDraw = 2; /* valid until this engine moves twice */
8504         if (gameMode == TwoMachinesPlay) {
8505             if (cps->other->offeredDraw) {
8506                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8507             /* [HGM] in two-machine mode we delay relaying draw offer      */
8508             /* until after we also have move, to see if it is really claim */
8509             }
8510         } else if (gameMode == MachinePlaysWhite ||
8511                    gameMode == MachinePlaysBlack) {
8512           if (userOfferedDraw) {
8513             DisplayInformation(_("Machine accepts your draw offer"));
8514             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8515           } else {
8516             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8517           }
8518         }
8519     }
8520
8521
8522     /*
8523      * Look for thinking output
8524      */
8525     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8526           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8527                                 ) {
8528         int plylev, mvleft, mvtot, curscore, time;
8529         char mvname[MOVE_LEN];
8530         u64 nodes; // [DM]
8531         char plyext;
8532         int ignore = FALSE;
8533         int prefixHint = FALSE;
8534         mvname[0] = NULLCHAR;
8535
8536         switch (gameMode) {
8537           case MachinePlaysBlack:
8538           case IcsPlayingBlack:
8539             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8540             break;
8541           case MachinePlaysWhite:
8542           case IcsPlayingWhite:
8543             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8544             break;
8545           case AnalyzeMode:
8546           case AnalyzeFile:
8547             break;
8548           case IcsObserving: /* [DM] icsEngineAnalyze */
8549             if (!appData.icsEngineAnalyze) ignore = TRUE;
8550             break;
8551           case TwoMachinesPlay:
8552             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8553                 ignore = TRUE;
8554             }
8555             break;
8556           default:
8557             ignore = TRUE;
8558             break;
8559         }
8560
8561         if (!ignore) {
8562             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8563             buf1[0] = NULLCHAR;
8564             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8565                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8566
8567                 if (plyext != ' ' && plyext != '\t') {
8568                     time *= 100;
8569                 }
8570
8571                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8572                 if( cps->scoreIsAbsolute &&
8573                     ( gameMode == MachinePlaysBlack ||
8574                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8575                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8576                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8577                      !WhiteOnMove(currentMove)
8578                     ) )
8579                 {
8580                     curscore = -curscore;
8581                 }
8582
8583                 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8584
8585                 tempStats.depth = plylev;
8586                 tempStats.nodes = nodes;
8587                 tempStats.time = time;
8588                 tempStats.score = curscore;
8589                 tempStats.got_only_move = 0;
8590
8591                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8592                         int ticklen;
8593
8594                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8595                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8596                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8597                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8598                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8599                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8600                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8601                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8602                 }
8603
8604                 /* Buffer overflow protection */
8605                 if (pv[0] != NULLCHAR) {
8606                     if (strlen(pv) >= sizeof(tempStats.movelist)
8607                         && appData.debugMode) {
8608                         fprintf(debugFP,
8609                                 "PV is too long; using the first %u bytes.\n",
8610                                 (unsigned) sizeof(tempStats.movelist) - 1);
8611                     }
8612
8613                     safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8614                 } else {
8615                     sprintf(tempStats.movelist, " no PV\n");
8616                 }
8617
8618                 if (tempStats.seen_stat) {
8619                     tempStats.ok_to_send = 1;
8620                 }
8621
8622                 if (strchr(tempStats.movelist, '(') != NULL) {
8623                     tempStats.line_is_book = 1;
8624                     tempStats.nr_moves = 0;
8625                     tempStats.moves_left = 0;
8626                 } else {
8627                     tempStats.line_is_book = 0;
8628                 }
8629
8630                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8631                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8632
8633                 SendProgramStatsToFrontend( cps, &tempStats );
8634
8635                 /*
8636                     [AS] Protect the thinkOutput buffer from overflow... this
8637                     is only useful if buf1 hasn't overflowed first!
8638                 */
8639                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8640                          plylev,
8641                          (gameMode == TwoMachinesPlay ?
8642                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8643                          ((double) curscore) / 100.0,
8644                          prefixHint ? lastHint : "",
8645                          prefixHint ? " " : "" );
8646
8647                 if( buf1[0] != NULLCHAR ) {
8648                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8649
8650                     if( strlen(pv) > max_len ) {
8651                         if( appData.debugMode) {
8652                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8653                         }
8654                         pv[max_len+1] = '\0';
8655                     }
8656
8657                     strcat( thinkOutput, pv);
8658                 }
8659
8660                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8661                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8662                     DisplayMove(currentMove - 1);
8663                 }
8664                 return;
8665
8666             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8667                 /* crafty (9.25+) says "(only move) <move>"
8668                  * if there is only 1 legal move
8669                  */
8670                 sscanf(p, "(only move) %s", buf1);
8671                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8672                 sprintf(programStats.movelist, "%s (only move)", buf1);
8673                 programStats.depth = 1;
8674                 programStats.nr_moves = 1;
8675                 programStats.moves_left = 1;
8676                 programStats.nodes = 1;
8677                 programStats.time = 1;
8678                 programStats.got_only_move = 1;
8679
8680                 /* Not really, but we also use this member to
8681                    mean "line isn't going to change" (Crafty
8682                    isn't searching, so stats won't change) */
8683                 programStats.line_is_book = 1;
8684
8685                 SendProgramStatsToFrontend( cps, &programStats );
8686
8687                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8688                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8689                     DisplayMove(currentMove - 1);
8690                 }
8691                 return;
8692             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8693                               &time, &nodes, &plylev, &mvleft,
8694                               &mvtot, mvname) >= 5) {
8695                 /* The stat01: line is from Crafty (9.29+) in response
8696                    to the "." command */
8697                 programStats.seen_stat = 1;
8698                 cps->maybeThinking = TRUE;
8699
8700                 if (programStats.got_only_move || !appData.periodicUpdates)
8701                   return;
8702
8703                 programStats.depth = plylev;
8704                 programStats.time = time;
8705                 programStats.nodes = nodes;
8706                 programStats.moves_left = mvleft;
8707                 programStats.nr_moves = mvtot;
8708                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8709                 programStats.ok_to_send = 1;
8710                 programStats.movelist[0] = '\0';
8711
8712                 SendProgramStatsToFrontend( cps, &programStats );
8713
8714                 return;
8715
8716             } else if (strncmp(message,"++",2) == 0) {
8717                 /* Crafty 9.29+ outputs this */
8718                 programStats.got_fail = 2;
8719                 return;
8720
8721             } else if (strncmp(message,"--",2) == 0) {
8722                 /* Crafty 9.29+ outputs this */
8723                 programStats.got_fail = 1;
8724                 return;
8725
8726             } else if (thinkOutput[0] != NULLCHAR &&
8727                        strncmp(message, "    ", 4) == 0) {
8728                 unsigned message_len;
8729
8730                 p = message;
8731                 while (*p && *p == ' ') p++;
8732
8733                 message_len = strlen( p );
8734
8735                 /* [AS] Avoid buffer overflow */
8736                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8737                     strcat(thinkOutput, " ");
8738                     strcat(thinkOutput, p);
8739                 }
8740
8741                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8742                     strcat(programStats.movelist, " ");
8743                     strcat(programStats.movelist, p);
8744                 }
8745
8746                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8747                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8748                     DisplayMove(currentMove - 1);
8749                 }
8750                 return;
8751             }
8752         }
8753         else {
8754             buf1[0] = NULLCHAR;
8755
8756             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8757                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8758             {
8759                 ChessProgramStats cpstats;
8760
8761                 if (plyext != ' ' && plyext != '\t') {
8762                     time *= 100;
8763                 }
8764
8765                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8766                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8767                     curscore = -curscore;
8768                 }
8769
8770                 cpstats.depth = plylev;
8771                 cpstats.nodes = nodes;
8772                 cpstats.time = time;
8773                 cpstats.score = curscore;
8774                 cpstats.got_only_move = 0;
8775                 cpstats.movelist[0] = '\0';
8776
8777                 if (buf1[0] != NULLCHAR) {
8778                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8779                 }
8780
8781                 cpstats.ok_to_send = 0;
8782                 cpstats.line_is_book = 0;
8783                 cpstats.nr_moves = 0;
8784                 cpstats.moves_left = 0;
8785
8786                 SendProgramStatsToFrontend( cps, &cpstats );
8787             }
8788         }
8789     }
8790 }
8791
8792
8793 /* Parse a game score from the character string "game", and
8794    record it as the history of the current game.  The game
8795    score is NOT assumed to start from the standard position.
8796    The display is not updated in any way.
8797    */
8798 void
8799 ParseGameHistory(game)
8800      char *game;
8801 {
8802     ChessMove moveType;
8803     int fromX, fromY, toX, toY, boardIndex;
8804     char promoChar;
8805     char *p, *q;
8806     char buf[MSG_SIZ];
8807
8808     if (appData.debugMode)
8809       fprintf(debugFP, "Parsing game history: %s\n", game);
8810
8811     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8812     gameInfo.site = StrSave(appData.icsHost);
8813     gameInfo.date = PGNDate();
8814     gameInfo.round = StrSave("-");
8815
8816     /* Parse out names of players */
8817     while (*game == ' ') game++;
8818     p = buf;
8819     while (*game != ' ') *p++ = *game++;
8820     *p = NULLCHAR;
8821     gameInfo.white = StrSave(buf);
8822     while (*game == ' ') game++;
8823     p = buf;
8824     while (*game != ' ' && *game != '\n') *p++ = *game++;
8825     *p = NULLCHAR;
8826     gameInfo.black = StrSave(buf);
8827
8828     /* Parse moves */
8829     boardIndex = blackPlaysFirst ? 1 : 0;
8830     yynewstr(game);
8831     for (;;) {
8832         yyboardindex = boardIndex;
8833         moveType = (ChessMove) Myylex();
8834         switch (moveType) {
8835           case IllegalMove:             /* maybe suicide chess, etc. */
8836   if (appData.debugMode) {
8837     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8838     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8839     setbuf(debugFP, NULL);
8840   }
8841           case WhitePromotion:
8842           case BlackPromotion:
8843           case WhiteNonPromotion:
8844           case BlackNonPromotion:
8845           case NormalMove:
8846           case WhiteCapturesEnPassant:
8847           case BlackCapturesEnPassant:
8848           case WhiteKingSideCastle:
8849           case WhiteQueenSideCastle:
8850           case BlackKingSideCastle:
8851           case BlackQueenSideCastle:
8852           case WhiteKingSideCastleWild:
8853           case WhiteQueenSideCastleWild:
8854           case BlackKingSideCastleWild:
8855           case BlackQueenSideCastleWild:
8856           /* PUSH Fabien */
8857           case WhiteHSideCastleFR:
8858           case WhiteASideCastleFR:
8859           case BlackHSideCastleFR:
8860           case BlackASideCastleFR:
8861           /* POP Fabien */
8862             fromX = currentMoveString[0] - AAA;
8863             fromY = currentMoveString[1] - ONE;
8864             toX = currentMoveString[2] - AAA;
8865             toY = currentMoveString[3] - ONE;
8866             promoChar = currentMoveString[4];
8867             break;
8868           case WhiteDrop:
8869           case BlackDrop:
8870             fromX = moveType == WhiteDrop ?
8871               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8872             (int) CharToPiece(ToLower(currentMoveString[0]));
8873             fromY = DROP_RANK;
8874             toX = currentMoveString[2] - AAA;
8875             toY = currentMoveString[3] - ONE;
8876             promoChar = NULLCHAR;
8877             break;
8878           case AmbiguousMove:
8879             /* bug? */
8880             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8881   if (appData.debugMode) {
8882     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8883     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8884     setbuf(debugFP, NULL);
8885   }
8886             DisplayError(buf, 0);
8887             return;
8888           case ImpossibleMove:
8889             /* bug? */
8890             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8891   if (appData.debugMode) {
8892     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8893     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8894     setbuf(debugFP, NULL);
8895   }
8896             DisplayError(buf, 0);
8897             return;
8898           case EndOfFile:
8899             if (boardIndex < backwardMostMove) {
8900                 /* Oops, gap.  How did that happen? */
8901                 DisplayError(_("Gap in move list"), 0);
8902                 return;
8903             }
8904             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8905             if (boardIndex > forwardMostMove) {
8906                 forwardMostMove = boardIndex;
8907             }
8908             return;
8909           case ElapsedTime:
8910             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8911                 strcat(parseList[boardIndex-1], " ");
8912                 strcat(parseList[boardIndex-1], yy_text);
8913             }
8914             continue;
8915           case Comment:
8916           case PGNTag:
8917           case NAG:
8918           default:
8919             /* ignore */
8920             continue;
8921           case WhiteWins:
8922           case BlackWins:
8923           case GameIsDrawn:
8924           case GameUnfinished:
8925             if (gameMode == IcsExamining) {
8926                 if (boardIndex < backwardMostMove) {
8927                     /* Oops, gap.  How did that happen? */
8928                     return;
8929                 }
8930                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8931                 return;
8932             }
8933             gameInfo.result = moveType;
8934             p = strchr(yy_text, '{');
8935             if (p == NULL) p = strchr(yy_text, '(');
8936             if (p == NULL) {
8937                 p = yy_text;
8938                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8939             } else {
8940                 q = strchr(p, *p == '{' ? '}' : ')');
8941                 if (q != NULL) *q = NULLCHAR;
8942                 p++;
8943             }
8944             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8945             gameInfo.resultDetails = StrSave(p);
8946             continue;
8947         }
8948         if (boardIndex >= forwardMostMove &&
8949             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8950             backwardMostMove = blackPlaysFirst ? 1 : 0;
8951             return;
8952         }
8953         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8954                                  fromY, fromX, toY, toX, promoChar,
8955                                  parseList[boardIndex]);
8956         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8957         /* currentMoveString is set as a side-effect of yylex */
8958         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8959         strcat(moveList[boardIndex], "\n");
8960         boardIndex++;
8961         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8962         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8963           case MT_NONE:
8964           case MT_STALEMATE:
8965           default:
8966             break;
8967           case MT_CHECK:
8968             if(gameInfo.variant != VariantShogi)
8969                 strcat(parseList[boardIndex - 1], "+");
8970             break;
8971           case MT_CHECKMATE:
8972           case MT_STAINMATE:
8973             strcat(parseList[boardIndex - 1], "#");
8974             break;
8975         }
8976     }
8977 }
8978
8979
8980 /* Apply a move to the given board  */
8981 void
8982 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8983      int fromX, fromY, toX, toY;
8984      int promoChar;
8985      Board board;
8986 {
8987   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8988   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
8989
8990     /* [HGM] compute & store e.p. status and castling rights for new position */
8991     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8992
8993       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8994       oldEP = (signed char)board[EP_STATUS];
8995       board[EP_STATUS] = EP_NONE;
8996
8997       if( board[toY][toX] != EmptySquare )
8998            board[EP_STATUS] = EP_CAPTURE;
8999
9000   if (fromY == DROP_RANK) {
9001         /* must be first */
9002         piece = board[toY][toX] = (ChessSquare) fromX;
9003   } else {
9004       int i;
9005
9006       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9007            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9008                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9009       } else
9010       if( board[fromY][fromX] == WhitePawn ) {
9011            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9012                board[EP_STATUS] = EP_PAWN_MOVE;
9013            if( toY-fromY==2) {
9014                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
9015                         gameInfo.variant != VariantBerolina || toX < fromX)
9016                       board[EP_STATUS] = toX | berolina;
9017                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9018                         gameInfo.variant != VariantBerolina || toX > fromX)
9019                       board[EP_STATUS] = toX;
9020            }
9021       } else
9022       if( board[fromY][fromX] == BlackPawn ) {
9023            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9024                board[EP_STATUS] = EP_PAWN_MOVE;
9025            if( toY-fromY== -2) {
9026                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
9027                         gameInfo.variant != VariantBerolina || toX < fromX)
9028                       board[EP_STATUS] = toX | berolina;
9029                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9030                         gameInfo.variant != VariantBerolina || toX > fromX)
9031                       board[EP_STATUS] = toX;
9032            }
9033        }
9034
9035        for(i=0; i<nrCastlingRights; i++) {
9036            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9037               board[CASTLING][i] == toX   && castlingRank[i] == toY
9038              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9039        }
9040
9041      if (fromX == toX && fromY == toY) return;
9042
9043      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9044      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9045      if(gameInfo.variant == VariantKnightmate)
9046          king += (int) WhiteUnicorn - (int) WhiteKing;
9047
9048     /* Code added by Tord: */
9049     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9050     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9051         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9052       board[fromY][fromX] = EmptySquare;
9053       board[toY][toX] = EmptySquare;
9054       if((toX > fromX) != (piece == WhiteRook)) {
9055         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9056       } else {
9057         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9058       }
9059     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9060                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9061       board[fromY][fromX] = EmptySquare;
9062       board[toY][toX] = EmptySquare;
9063       if((toX > fromX) != (piece == BlackRook)) {
9064         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9065       } else {
9066         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9067       }
9068     /* End of code added by Tord */
9069
9070     } else if (board[fromY][fromX] == king
9071         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9072         && toY == fromY && toX > fromX+1) {
9073         board[fromY][fromX] = EmptySquare;
9074         board[toY][toX] = king;
9075         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9076         board[fromY][BOARD_RGHT-1] = EmptySquare;
9077     } else if (board[fromY][fromX] == king
9078         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9079                && toY == fromY && toX < fromX-1) {
9080         board[fromY][fromX] = EmptySquare;
9081         board[toY][toX] = king;
9082         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9083         board[fromY][BOARD_LEFT] = EmptySquare;
9084     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9085                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9086                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9087                ) {
9088         /* white pawn promotion */
9089         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9090         if(gameInfo.variant==VariantBughouse ||
9091            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9092             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9093         board[fromY][fromX] = EmptySquare;
9094     } else if ((fromY >= BOARD_HEIGHT>>1)
9095                && (toX != fromX)
9096                && gameInfo.variant != VariantXiangqi
9097                && gameInfo.variant != VariantBerolina
9098                && (board[fromY][fromX] == WhitePawn)
9099                && (board[toY][toX] == EmptySquare)) {
9100         board[fromY][fromX] = EmptySquare;
9101         board[toY][toX] = WhitePawn;
9102         captured = board[toY - 1][toX];
9103         board[toY - 1][toX] = EmptySquare;
9104     } else if ((fromY == BOARD_HEIGHT-4)
9105                && (toX == fromX)
9106                && gameInfo.variant == VariantBerolina
9107                && (board[fromY][fromX] == WhitePawn)
9108                && (board[toY][toX] == EmptySquare)) {
9109         board[fromY][fromX] = EmptySquare;
9110         board[toY][toX] = WhitePawn;
9111         if(oldEP & EP_BEROLIN_A) {
9112                 captured = board[fromY][fromX-1];
9113                 board[fromY][fromX-1] = EmptySquare;
9114         }else{  captured = board[fromY][fromX+1];
9115                 board[fromY][fromX+1] = EmptySquare;
9116         }
9117     } else if (board[fromY][fromX] == king
9118         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9119                && toY == fromY && toX > fromX+1) {
9120         board[fromY][fromX] = EmptySquare;
9121         board[toY][toX] = king;
9122         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9123         board[fromY][BOARD_RGHT-1] = EmptySquare;
9124     } else if (board[fromY][fromX] == king
9125         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9126                && toY == fromY && toX < fromX-1) {
9127         board[fromY][fromX] = EmptySquare;
9128         board[toY][toX] = king;
9129         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9130         board[fromY][BOARD_LEFT] = EmptySquare;
9131     } else if (fromY == 7 && fromX == 3
9132                && board[fromY][fromX] == BlackKing
9133                && toY == 7 && toX == 5) {
9134         board[fromY][fromX] = EmptySquare;
9135         board[toY][toX] = BlackKing;
9136         board[fromY][7] = EmptySquare;
9137         board[toY][4] = BlackRook;
9138     } else if (fromY == 7 && fromX == 3
9139                && board[fromY][fromX] == BlackKing
9140                && toY == 7 && toX == 1) {
9141         board[fromY][fromX] = EmptySquare;
9142         board[toY][toX] = BlackKing;
9143         board[fromY][0] = EmptySquare;
9144         board[toY][2] = BlackRook;
9145     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9146                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9147                && toY < promoRank && promoChar
9148                ) {
9149         /* black pawn promotion */
9150         board[toY][toX] = CharToPiece(ToLower(promoChar));
9151         if(gameInfo.variant==VariantBughouse ||
9152            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9153             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9154         board[fromY][fromX] = EmptySquare;
9155     } else if ((fromY < BOARD_HEIGHT>>1)
9156                && (toX != fromX)
9157                && gameInfo.variant != VariantXiangqi
9158                && gameInfo.variant != VariantBerolina
9159                && (board[fromY][fromX] == BlackPawn)
9160                && (board[toY][toX] == EmptySquare)) {
9161         board[fromY][fromX] = EmptySquare;
9162         board[toY][toX] = BlackPawn;
9163         captured = board[toY + 1][toX];
9164         board[toY + 1][toX] = EmptySquare;
9165     } else if ((fromY == 3)
9166                && (toX == fromX)
9167                && gameInfo.variant == VariantBerolina
9168                && (board[fromY][fromX] == BlackPawn)
9169                && (board[toY][toX] == EmptySquare)) {
9170         board[fromY][fromX] = EmptySquare;
9171         board[toY][toX] = BlackPawn;
9172         if(oldEP & EP_BEROLIN_A) {
9173                 captured = board[fromY][fromX-1];
9174                 board[fromY][fromX-1] = EmptySquare;
9175         }else{  captured = board[fromY][fromX+1];
9176                 board[fromY][fromX+1] = EmptySquare;
9177         }
9178     } else {
9179         board[toY][toX] = board[fromY][fromX];
9180         board[fromY][fromX] = EmptySquare;
9181     }
9182   }
9183
9184     if (gameInfo.holdingsWidth != 0) {
9185
9186       /* !!A lot more code needs to be written to support holdings  */
9187       /* [HGM] OK, so I have written it. Holdings are stored in the */
9188       /* penultimate board files, so they are automaticlly stored   */
9189       /* in the game history.                                       */
9190       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9191                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9192         /* Delete from holdings, by decreasing count */
9193         /* and erasing image if necessary            */
9194         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9195         if(p < (int) BlackPawn) { /* white drop */
9196              p -= (int)WhitePawn;
9197                  p = PieceToNumber((ChessSquare)p);
9198              if(p >= gameInfo.holdingsSize) p = 0;
9199              if(--board[p][BOARD_WIDTH-2] <= 0)
9200                   board[p][BOARD_WIDTH-1] = EmptySquare;
9201              if((int)board[p][BOARD_WIDTH-2] < 0)
9202                         board[p][BOARD_WIDTH-2] = 0;
9203         } else {                  /* black drop */
9204              p -= (int)BlackPawn;
9205                  p = PieceToNumber((ChessSquare)p);
9206              if(p >= gameInfo.holdingsSize) p = 0;
9207              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9208                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9209              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9210                         board[BOARD_HEIGHT-1-p][1] = 0;
9211         }
9212       }
9213       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9214           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9215         /* [HGM] holdings: Add to holdings, if holdings exist */
9216         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9217                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9218                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9219         }
9220         p = (int) captured;
9221         if (p >= (int) BlackPawn) {
9222           p -= (int)BlackPawn;
9223           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9224                   /* in Shogi restore piece to its original  first */
9225                   captured = (ChessSquare) (DEMOTED captured);
9226                   p = DEMOTED p;
9227           }
9228           p = PieceToNumber((ChessSquare)p);
9229           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9230           board[p][BOARD_WIDTH-2]++;
9231           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9232         } else {
9233           p -= (int)WhitePawn;
9234           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9235                   captured = (ChessSquare) (DEMOTED captured);
9236                   p = DEMOTED p;
9237           }
9238           p = PieceToNumber((ChessSquare)p);
9239           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9240           board[BOARD_HEIGHT-1-p][1]++;
9241           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9242         }
9243       }
9244     } else if (gameInfo.variant == VariantAtomic) {
9245       if (captured != EmptySquare) {
9246         int y, x;
9247         for (y = toY-1; y <= toY+1; y++) {
9248           for (x = toX-1; x <= toX+1; x++) {
9249             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9250                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9251               board[y][x] = EmptySquare;
9252             }
9253           }
9254         }
9255         board[toY][toX] = EmptySquare;
9256       }
9257     }
9258     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9259         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9260     } else
9261     if(promoChar == '+') {
9262         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9263         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9264     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9265         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9266     }
9267     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9268                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9269         // [HGM] superchess: take promotion piece out of holdings
9270         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9271         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9272             if(!--board[k][BOARD_WIDTH-2])
9273                 board[k][BOARD_WIDTH-1] = EmptySquare;
9274         } else {
9275             if(!--board[BOARD_HEIGHT-1-k][1])
9276                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9277         }
9278     }
9279
9280 }
9281
9282 /* Updates forwardMostMove */
9283 void
9284 MakeMove(fromX, fromY, toX, toY, promoChar)
9285      int fromX, fromY, toX, toY;
9286      int promoChar;
9287 {
9288 //    forwardMostMove++; // [HGM] bare: moved downstream
9289
9290     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9291         int timeLeft; static int lastLoadFlag=0; int king, piece;
9292         piece = boards[forwardMostMove][fromY][fromX];
9293         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9294         if(gameInfo.variant == VariantKnightmate)
9295             king += (int) WhiteUnicorn - (int) WhiteKing;
9296         if(forwardMostMove == 0) {
9297             if(blackPlaysFirst)
9298                 fprintf(serverMoves, "%s;", second.tidy);
9299             fprintf(serverMoves, "%s;", first.tidy);
9300             if(!blackPlaysFirst)
9301                 fprintf(serverMoves, "%s;", second.tidy);
9302         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9303         lastLoadFlag = loadFlag;
9304         // print base move
9305         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9306         // print castling suffix
9307         if( toY == fromY && piece == king ) {
9308             if(toX-fromX > 1)
9309                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9310             if(fromX-toX >1)
9311                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9312         }
9313         // e.p. suffix
9314         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9315              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9316              boards[forwardMostMove][toY][toX] == EmptySquare
9317              && fromX != toX && fromY != toY)
9318                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9319         // promotion suffix
9320         if(promoChar != NULLCHAR)
9321                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9322         if(!loadFlag) {
9323             fprintf(serverMoves, "/%d/%d",
9324                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9325             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9326             else                      timeLeft = blackTimeRemaining/1000;
9327             fprintf(serverMoves, "/%d", timeLeft);
9328         }
9329         fflush(serverMoves);
9330     }
9331
9332     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9333       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9334                         0, 1);
9335       return;
9336     }
9337     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9338     if (commentList[forwardMostMove+1] != NULL) {
9339         free(commentList[forwardMostMove+1]);
9340         commentList[forwardMostMove+1] = NULL;
9341     }
9342     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9343     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9344     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9345     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9346     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9347     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9348     gameInfo.result = GameUnfinished;
9349     if (gameInfo.resultDetails != NULL) {
9350         free(gameInfo.resultDetails);
9351         gameInfo.resultDetails = NULL;
9352     }
9353     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9354                               moveList[forwardMostMove - 1]);
9355     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9356                              PosFlags(forwardMostMove - 1),
9357                              fromY, fromX, toY, toX, promoChar,
9358                              parseList[forwardMostMove - 1]);
9359     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9360       case MT_NONE:
9361       case MT_STALEMATE:
9362       default:
9363         break;
9364       case MT_CHECK:
9365         if(gameInfo.variant != VariantShogi)
9366             strcat(parseList[forwardMostMove - 1], "+");
9367         break;
9368       case MT_CHECKMATE:
9369       case MT_STAINMATE:
9370         strcat(parseList[forwardMostMove - 1], "#");
9371         break;
9372     }
9373     if (appData.debugMode) {
9374         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9375     }
9376
9377 }
9378
9379 /* Updates currentMove if not pausing */
9380 void
9381 ShowMove(fromX, fromY, toX, toY)
9382 {
9383     int instant = (gameMode == PlayFromGameFile) ?
9384         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9385     if(appData.noGUI) return;
9386     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9387         if (!instant) {
9388             if (forwardMostMove == currentMove + 1) {
9389                 AnimateMove(boards[forwardMostMove - 1],
9390                             fromX, fromY, toX, toY);
9391             }
9392             if (appData.highlightLastMove) {
9393                 SetHighlights(fromX, fromY, toX, toY);
9394             }
9395         }
9396         currentMove = forwardMostMove;
9397     }
9398
9399     if (instant) return;
9400
9401     DisplayMove(currentMove - 1);
9402     DrawPosition(FALSE, boards[currentMove]);
9403     DisplayBothClocks();
9404     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9405     DisplayBook(currentMove);
9406 }
9407
9408 void SendEgtPath(ChessProgramState *cps)
9409 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9410         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9411
9412         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9413
9414         while(*p) {
9415             char c, *q = name+1, *r, *s;
9416
9417             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9418             while(*p && *p != ',') *q++ = *p++;
9419             *q++ = ':'; *q = 0;
9420             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9421                 strcmp(name, ",nalimov:") == 0 ) {
9422                 // take nalimov path from the menu-changeable option first, if it is defined
9423               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9424                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9425             } else
9426             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9427                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9428                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9429                 s = r = StrStr(s, ":") + 1; // beginning of path info
9430                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9431                 c = *r; *r = 0;             // temporarily null-terminate path info
9432                     *--q = 0;               // strip of trailig ':' from name
9433                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9434                 *r = c;
9435                 SendToProgram(buf,cps);     // send egtbpath command for this format
9436             }
9437             if(*p == ',') p++; // read away comma to position for next format name
9438         }
9439 }
9440
9441 void
9442 InitChessProgram(cps, setup)
9443      ChessProgramState *cps;
9444      int setup; /* [HGM] needed to setup FRC opening position */
9445 {
9446     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9447     if (appData.noChessProgram) return;
9448     hintRequested = FALSE;
9449     bookRequested = FALSE;
9450
9451     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9452     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9453     if(cps->memSize) { /* [HGM] memory */
9454       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9455         SendToProgram(buf, cps);
9456     }
9457     SendEgtPath(cps); /* [HGM] EGT */
9458     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9459       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9460         SendToProgram(buf, cps);
9461     }
9462
9463     SendToProgram(cps->initString, cps);
9464     if (gameInfo.variant != VariantNormal &&
9465         gameInfo.variant != VariantLoadable
9466         /* [HGM] also send variant if board size non-standard */
9467         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9468                                             ) {
9469       char *v = VariantName(gameInfo.variant);
9470       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9471         /* [HGM] in protocol 1 we have to assume all variants valid */
9472         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9473         DisplayFatalError(buf, 0, 1);
9474         return;
9475       }
9476
9477       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9478       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9479       if( gameInfo.variant == VariantXiangqi )
9480            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9481       if( gameInfo.variant == VariantShogi )
9482            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9483       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9484            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9485       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9486           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9487            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9488       if( gameInfo.variant == VariantCourier )
9489            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9490       if( gameInfo.variant == VariantSuper )
9491            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9492       if( gameInfo.variant == VariantGreat )
9493            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9494       if( gameInfo.variant == VariantSChess )
9495            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9496       if( gameInfo.variant == VariantGrand )
9497            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9498
9499       if(overruled) {
9500         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9501                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9502            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9503            if(StrStr(cps->variants, b) == NULL) {
9504                // specific sized variant not known, check if general sizing allowed
9505                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9506                    if(StrStr(cps->variants, "boardsize") == NULL) {
9507                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9508                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9509                        DisplayFatalError(buf, 0, 1);
9510                        return;
9511                    }
9512                    /* [HGM] here we really should compare with the maximum supported board size */
9513                }
9514            }
9515       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9516       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9517       SendToProgram(buf, cps);
9518     }
9519     currentlyInitializedVariant = gameInfo.variant;
9520
9521     /* [HGM] send opening position in FRC to first engine */
9522     if(setup) {
9523           SendToProgram("force\n", cps);
9524           SendBoard(cps, 0);
9525           /* engine is now in force mode! Set flag to wake it up after first move. */
9526           setboardSpoiledMachineBlack = 1;
9527     }
9528
9529     if (cps->sendICS) {
9530       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9531       SendToProgram(buf, cps);
9532     }
9533     cps->maybeThinking = FALSE;
9534     cps->offeredDraw = 0;
9535     if (!appData.icsActive) {
9536         SendTimeControl(cps, movesPerSession, timeControl,
9537                         timeIncrement, appData.searchDepth,
9538                         searchTime);
9539     }
9540     if (appData.showThinking
9541         // [HGM] thinking: four options require thinking output to be sent
9542         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9543                                 ) {
9544         SendToProgram("post\n", cps);
9545     }
9546     SendToProgram("hard\n", cps);
9547     if (!appData.ponderNextMove) {
9548         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9549            it without being sure what state we are in first.  "hard"
9550            is not a toggle, so that one is OK.
9551          */
9552         SendToProgram("easy\n", cps);
9553     }
9554     if (cps->usePing) {
9555       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9556       SendToProgram(buf, cps);
9557     }
9558     cps->initDone = TRUE;
9559     ClearEngineOutputPane(cps == &second);
9560 }
9561
9562
9563 void
9564 StartChessProgram(cps)
9565      ChessProgramState *cps;
9566 {
9567     char buf[MSG_SIZ];
9568     int err;
9569
9570     if (appData.noChessProgram) return;
9571     cps->initDone = FALSE;
9572
9573     if (strcmp(cps->host, "localhost") == 0) {
9574         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9575     } else if (*appData.remoteShell == NULLCHAR) {
9576         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9577     } else {
9578         if (*appData.remoteUser == NULLCHAR) {
9579           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9580                     cps->program);
9581         } else {
9582           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9583                     cps->host, appData.remoteUser, cps->program);
9584         }
9585         err = StartChildProcess(buf, "", &cps->pr);
9586     }
9587
9588     if (err != 0) {
9589       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9590         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9591         if(cps != &first) return;
9592         appData.noChessProgram = TRUE;
9593         ThawUI();
9594         SetNCPMode();
9595 //      DisplayFatalError(buf, err, 1);
9596 //      cps->pr = NoProc;
9597 //      cps->isr = NULL;
9598         return;
9599     }
9600
9601     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9602     if (cps->protocolVersion > 1) {
9603       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9604       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9605       cps->comboCnt = 0;  //                and values of combo boxes
9606       SendToProgram(buf, cps);
9607     } else {
9608       SendToProgram("xboard\n", cps);
9609     }
9610 }
9611
9612 void
9613 TwoMachinesEventIfReady P((void))
9614 {
9615   static int curMess = 0;
9616   if (first.lastPing != first.lastPong) {
9617     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9618     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9619     return;
9620   }
9621   if (second.lastPing != second.lastPong) {
9622     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9623     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9624     return;
9625   }
9626   DisplayMessage("", ""); curMess = 0;
9627   ThawUI();
9628   TwoMachinesEvent();
9629 }
9630
9631 char *
9632 MakeName(char *template)
9633 {
9634     time_t clock;
9635     struct tm *tm;
9636     static char buf[MSG_SIZ];
9637     char *p = buf;
9638     int i;
9639
9640     clock = time((time_t *)NULL);
9641     tm = localtime(&clock);
9642
9643     while(*p++ = *template++) if(p[-1] == '%') {
9644         switch(*template++) {
9645           case 0:   *p = 0; return buf;
9646           case 'Y': i = tm->tm_year+1900; break;
9647           case 'y': i = tm->tm_year-100; break;
9648           case 'M': i = tm->tm_mon+1; break;
9649           case 'd': i = tm->tm_mday; break;
9650           case 'h': i = tm->tm_hour; break;
9651           case 'm': i = tm->tm_min; break;
9652           case 's': i = tm->tm_sec; break;
9653           default:  i = 0;
9654         }
9655         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9656     }
9657     return buf;
9658 }
9659
9660 int
9661 CountPlayers(char *p)
9662 {
9663     int n = 0;
9664     while(p = strchr(p, '\n')) p++, n++; // count participants
9665     return n;
9666 }
9667
9668 FILE *
9669 WriteTourneyFile(char *results)
9670 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9671     FILE *f = fopen(appData.tourneyFile, "w");
9672     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9673         // create a file with tournament description
9674         fprintf(f, "-participants {%s}\n", appData.participants);
9675         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9676         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9677         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9678         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9679         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9680         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9681         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9682         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9683         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9684         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9685         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9686         if(searchTime > 0)
9687                 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9688         else {
9689                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9690                 fprintf(f, "-tc %s\n", appData.timeControl);
9691                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9692         }
9693         fprintf(f, "-results \"%s\"\n", results);
9694     }
9695     return f;
9696 }
9697
9698 int
9699 CreateTourney(char *name)
9700 {
9701         FILE *f;
9702         if(name[0] == NULLCHAR) {
9703             if(appData.participants[0])
9704                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9705             return 0;
9706         }
9707         f = fopen(name, "r");
9708         if(f) { // file exists
9709             ASSIGN(appData.tourneyFile, name);
9710             ParseArgsFromFile(f); // parse it
9711         } else {
9712             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9713             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9714                 DisplayError(_("Not enough participants"), 0);
9715                 return 0;
9716             }
9717             ASSIGN(appData.tourneyFile, name);
9718             if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9719             if((f = WriteTourneyFile("")) == NULL) return 0;
9720         }
9721         fclose(f);
9722         appData.noChessProgram = FALSE;
9723         appData.clockMode = TRUE;
9724         SetGNUMode();
9725         return 1;
9726 }
9727
9728 #define MAXENGINES 1000
9729 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9730
9731 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9732 {
9733     char buf[MSG_SIZ], *p, *q;
9734     int i=1;
9735     while(*names) {
9736         p = names; q = buf;
9737         while(*p && *p != '\n') *q++ = *p++;
9738         *q = 0;
9739         if(engineList[i]) free(engineList[i]);
9740         engineList[i] = strdup(buf);
9741         if(*p == '\n') p++;
9742         TidyProgramName(engineList[i], "localhost", buf);
9743         if(engineMnemonic[i]) free(engineMnemonic[i]);
9744         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9745             strcat(buf, " (");
9746             sscanf(q + 8, "%s", buf + strlen(buf));
9747             strcat(buf, ")");
9748         }
9749         engineMnemonic[i] = strdup(buf);
9750         names = p; i++;
9751       if(i > MAXENGINES - 2) break;
9752     }
9753     engineList[i] = NULL;
9754 }
9755
9756 // following implemented as macro to avoid type limitations
9757 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9758
9759 void SwapEngines(int n)
9760 {   // swap settings for first engine and other engine (so far only some selected options)
9761     int h;
9762     char *p;
9763     if(n == 0) return;
9764     SWAP(directory, p)
9765     SWAP(chessProgram, p)
9766     SWAP(isUCI, h)
9767     SWAP(hasOwnBookUCI, h)
9768     SWAP(protocolVersion, h)
9769     SWAP(reuse, h)
9770     SWAP(scoreIsAbsolute, h)
9771     SWAP(timeOdds, h)
9772     SWAP(logo, p)
9773     SWAP(pgnName, p)
9774     SWAP(pvSAN, h)
9775 }
9776
9777 void
9778 SetPlayer(int player)
9779 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9780     int i;
9781     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9782     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9783     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9784     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9785     if(mnemonic[i]) {
9786         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9787         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9788         ParseArgsFromString(buf);
9789     }
9790     free(engineName);
9791 }
9792
9793 int
9794 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9795 {   // determine players from game number
9796     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9797
9798     if(appData.tourneyType == 0) {
9799         roundsPerCycle = (nPlayers - 1) | 1;
9800         pairingsPerRound = nPlayers / 2;
9801     } else if(appData.tourneyType > 0) {
9802         roundsPerCycle = nPlayers - appData.tourneyType;
9803         pairingsPerRound = appData.tourneyType;
9804     }
9805     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9806     gamesPerCycle = gamesPerRound * roundsPerCycle;
9807     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9808     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9809     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9810     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9811     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9812     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9813
9814     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9815     if(appData.roundSync) *syncInterval = gamesPerRound;
9816
9817     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9818
9819     if(appData.tourneyType == 0) {
9820         if(curPairing == (nPlayers-1)/2 ) {
9821             *whitePlayer = curRound;
9822             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9823         } else {
9824             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9825             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9826             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9827             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9828         }
9829     } else if(appData.tourneyType > 0) {
9830         *whitePlayer = curPairing;
9831         *blackPlayer = curRound + appData.tourneyType;
9832     }
9833
9834     // take care of white/black alternation per round. 
9835     // For cycles and games this is already taken care of by default, derived from matchGame!
9836     return curRound & 1;
9837 }
9838
9839 int
9840 NextTourneyGame(int nr, int *swapColors)
9841 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9842     char *p, *q;
9843     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9844     FILE *tf;
9845     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9846     tf = fopen(appData.tourneyFile, "r");
9847     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9848     ParseArgsFromFile(tf); fclose(tf);
9849     InitTimeControls(); // TC might be altered from tourney file
9850
9851     nPlayers = CountPlayers(appData.participants); // count participants
9852     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9853     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9854
9855     if(syncInterval) {
9856         p = q = appData.results;
9857         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9858         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9859             DisplayMessage(_("Waiting for other game(s)"),"");
9860             waitingForGame = TRUE;
9861             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9862             return 0;
9863         }
9864         waitingForGame = FALSE;
9865     }
9866
9867     if(appData.tourneyType < 0) {
9868         if(nr>=0 && !pairingReceived) {
9869             char buf[1<<16];
9870             if(pairing.pr == NoProc) {
9871                 if(!appData.pairingEngine[0]) {
9872                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9873                     return 0;
9874                 }
9875                 StartChessProgram(&pairing); // starts the pairing engine
9876             }
9877             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9878             SendToProgram(buf, &pairing);
9879             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9880             SendToProgram(buf, &pairing);
9881             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9882         }
9883         pairingReceived = 0;                              // ... so we continue here 
9884         *swapColors = 0;
9885         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9886         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9887         matchGame = 1; roundNr = nr / syncInterval + 1;
9888     }
9889
9890     if(first.pr != NoProc) return 1; // engines already loaded
9891
9892     // redefine engines, engine dir, etc.
9893     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9894     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9895     SwapEngines(1);
9896     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9897     SwapEngines(1);         // and make that valid for second engine by swapping
9898     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9899     InitEngine(&second, 1);
9900     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9901     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9902     return 1;
9903 }
9904
9905 void
9906 NextMatchGame()
9907 {   // performs game initialization that does not invoke engines, and then tries to start the game
9908     int firstWhite, swapColors = 0;
9909     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9910     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9911     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9912     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9913     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9914     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9915     Reset(FALSE, first.pr != NoProc);
9916     appData.noChessProgram = FALSE;
9917     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9918     TwoMachinesEvent();
9919 }
9920
9921 void UserAdjudicationEvent( int result )
9922 {
9923     ChessMove gameResult = GameIsDrawn;
9924
9925     if( result > 0 ) {
9926         gameResult = WhiteWins;
9927     }
9928     else if( result < 0 ) {
9929         gameResult = BlackWins;
9930     }
9931
9932     if( gameMode == TwoMachinesPlay ) {
9933         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9934     }
9935 }
9936
9937
9938 // [HGM] save: calculate checksum of game to make games easily identifiable
9939 int StringCheckSum(char *s)
9940 {
9941         int i = 0;
9942         if(s==NULL) return 0;
9943         while(*s) i = i*259 + *s++;
9944         return i;
9945 }
9946
9947 int GameCheckSum()
9948 {
9949         int i, sum=0;
9950         for(i=backwardMostMove; i<forwardMostMove; i++) {
9951                 sum += pvInfoList[i].depth;
9952                 sum += StringCheckSum(parseList[i]);
9953                 sum += StringCheckSum(commentList[i]);
9954                 sum *= 261;
9955         }
9956         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9957         return sum + StringCheckSum(commentList[i]);
9958 } // end of save patch
9959
9960 void
9961 GameEnds(result, resultDetails, whosays)
9962      ChessMove result;
9963      char *resultDetails;
9964      int whosays;
9965 {
9966     GameMode nextGameMode;
9967     int isIcsGame;
9968     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9969
9970     if(endingGame) return; /* [HGM] crash: forbid recursion */
9971     endingGame = 1;
9972     if(twoBoards) { // [HGM] dual: switch back to one board
9973         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9974         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9975     }
9976     if (appData.debugMode) {
9977       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9978               result, resultDetails ? resultDetails : "(null)", whosays);
9979     }
9980
9981     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9982
9983     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9984         /* If we are playing on ICS, the server decides when the
9985            game is over, but the engine can offer to draw, claim
9986            a draw, or resign.
9987          */
9988 #if ZIPPY
9989         if (appData.zippyPlay && first.initDone) {
9990             if (result == GameIsDrawn) {
9991                 /* In case draw still needs to be claimed */
9992                 SendToICS(ics_prefix);
9993                 SendToICS("draw\n");
9994             } else if (StrCaseStr(resultDetails, "resign")) {
9995                 SendToICS(ics_prefix);
9996                 SendToICS("resign\n");
9997             }
9998         }
9999 #endif
10000         endingGame = 0; /* [HGM] crash */
10001         return;
10002     }
10003
10004     /* If we're loading the game from a file, stop */
10005     if (whosays == GE_FILE) {
10006       (void) StopLoadGameTimer();
10007       gameFileFP = NULL;
10008     }
10009
10010     /* Cancel draw offers */
10011     first.offeredDraw = second.offeredDraw = 0;
10012
10013     /* If this is an ICS game, only ICS can really say it's done;
10014        if not, anyone can. */
10015     isIcsGame = (gameMode == IcsPlayingWhite ||
10016                  gameMode == IcsPlayingBlack ||
10017                  gameMode == IcsObserving    ||
10018                  gameMode == IcsExamining);
10019
10020     if (!isIcsGame || whosays == GE_ICS) {
10021         /* OK -- not an ICS game, or ICS said it was done */
10022         StopClocks();
10023         if (!isIcsGame && !appData.noChessProgram)
10024           SetUserThinkingEnables();
10025
10026         /* [HGM] if a machine claims the game end we verify this claim */
10027         if(gameMode == TwoMachinesPlay && appData.testClaims) {
10028             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10029                 char claimer;
10030                 ChessMove trueResult = (ChessMove) -1;
10031
10032                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
10033                                             first.twoMachinesColor[0] :
10034                                             second.twoMachinesColor[0] ;
10035
10036                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10037                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10038                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10039                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10040                 } else
10041                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10042                     /* [HGM] verify: engine mate claims accepted if they were flagged */
10043                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10044                 } else
10045                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10046                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10047                 }
10048
10049                 // now verify win claims, but not in drop games, as we don't understand those yet
10050                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10051                                                  || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10052                     (result == WhiteWins && claimer == 'w' ||
10053                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
10054                       if (appData.debugMode) {
10055                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
10056                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10057                       }
10058                       if(result != trueResult) {
10059                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10060                               result = claimer == 'w' ? BlackWins : WhiteWins;
10061                               resultDetails = buf;
10062                       }
10063                 } else
10064                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10065                     && (forwardMostMove <= backwardMostMove ||
10066                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10067                         (claimer=='b')==(forwardMostMove&1))
10068                                                                                   ) {
10069                       /* [HGM] verify: draws that were not flagged are false claims */
10070                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10071                       result = claimer == 'w' ? BlackWins : WhiteWins;
10072                       resultDetails = buf;
10073                 }
10074                 /* (Claiming a loss is accepted no questions asked!) */
10075             }
10076             /* [HGM] bare: don't allow bare King to win */
10077             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10078                                             || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10079                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10080                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10081                && result != GameIsDrawn)
10082             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10083                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10084                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10085                         if(p >= 0 && p <= (int)WhiteKing) k++;
10086                 }
10087                 if (appData.debugMode) {
10088                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10089                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10090                 }
10091                 if(k <= 1) {
10092                         result = GameIsDrawn;
10093                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10094                         resultDetails = buf;
10095                 }
10096             }
10097         }
10098
10099
10100         if(serverMoves != NULL && !loadFlag) { char c = '=';
10101             if(result==WhiteWins) c = '+';
10102             if(result==BlackWins) c = '-';
10103             if(resultDetails != NULL)
10104                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10105         }
10106         if (resultDetails != NULL) {
10107             gameInfo.result = result;
10108             gameInfo.resultDetails = StrSave(resultDetails);
10109
10110             /* display last move only if game was not loaded from file */
10111             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10112                 DisplayMove(currentMove - 1);
10113
10114             if (forwardMostMove != 0) {
10115                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10116                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10117                                                                 ) {
10118                     if (*appData.saveGameFile != NULLCHAR) {
10119                         SaveGameToFile(appData.saveGameFile, TRUE);
10120                     } else if (appData.autoSaveGames) {
10121                         AutoSaveGame();
10122                     }
10123                     if (*appData.savePositionFile != NULLCHAR) {
10124                         SavePositionToFile(appData.savePositionFile);
10125                     }
10126                 }
10127             }
10128
10129             /* Tell program how game ended in case it is learning */
10130             /* [HGM] Moved this to after saving the PGN, just in case */
10131             /* engine died and we got here through time loss. In that */
10132             /* case we will get a fatal error writing the pipe, which */
10133             /* would otherwise lose us the PGN.                       */
10134             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10135             /* output during GameEnds should never be fatal anymore   */
10136             if (gameMode == MachinePlaysWhite ||
10137                 gameMode == MachinePlaysBlack ||
10138                 gameMode == TwoMachinesPlay ||
10139                 gameMode == IcsPlayingWhite ||
10140                 gameMode == IcsPlayingBlack ||
10141                 gameMode == BeginningOfGame) {
10142                 char buf[MSG_SIZ];
10143                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10144                         resultDetails);
10145                 if (first.pr != NoProc) {
10146                     SendToProgram(buf, &first);
10147                 }
10148                 if (second.pr != NoProc &&
10149                     gameMode == TwoMachinesPlay) {
10150                     SendToProgram(buf, &second);
10151                 }
10152             }
10153         }
10154
10155         if (appData.icsActive) {
10156             if (appData.quietPlay &&
10157                 (gameMode == IcsPlayingWhite ||
10158                  gameMode == IcsPlayingBlack)) {
10159                 SendToICS(ics_prefix);
10160                 SendToICS("set shout 1\n");
10161             }
10162             nextGameMode = IcsIdle;
10163             ics_user_moved = FALSE;
10164             /* clean up premove.  It's ugly when the game has ended and the
10165              * premove highlights are still on the board.
10166              */
10167             if (gotPremove) {
10168               gotPremove = FALSE;
10169               ClearPremoveHighlights();
10170               DrawPosition(FALSE, boards[currentMove]);
10171             }
10172             if (whosays == GE_ICS) {
10173                 switch (result) {
10174                 case WhiteWins:
10175                     if (gameMode == IcsPlayingWhite)
10176                         PlayIcsWinSound();
10177                     else if(gameMode == IcsPlayingBlack)
10178                         PlayIcsLossSound();
10179                     break;
10180                 case BlackWins:
10181                     if (gameMode == IcsPlayingBlack)
10182                         PlayIcsWinSound();
10183                     else if(gameMode == IcsPlayingWhite)
10184                         PlayIcsLossSound();
10185                     break;
10186                 case GameIsDrawn:
10187                     PlayIcsDrawSound();
10188                     break;
10189                 default:
10190                     PlayIcsUnfinishedSound();
10191                 }
10192             }
10193         } else if (gameMode == EditGame ||
10194                    gameMode == PlayFromGameFile ||
10195                    gameMode == AnalyzeMode ||
10196                    gameMode == AnalyzeFile) {
10197             nextGameMode = gameMode;
10198         } else {
10199             nextGameMode = EndOfGame;
10200         }
10201         pausing = FALSE;
10202         ModeHighlight();
10203     } else {
10204         nextGameMode = gameMode;
10205     }
10206
10207     if (appData.noChessProgram) {
10208         gameMode = nextGameMode;
10209         ModeHighlight();
10210         endingGame = 0; /* [HGM] crash */
10211         return;
10212     }
10213
10214     if (first.reuse) {
10215         /* Put first chess program into idle state */
10216         if (first.pr != NoProc &&
10217             (gameMode == MachinePlaysWhite ||
10218              gameMode == MachinePlaysBlack ||
10219              gameMode == TwoMachinesPlay ||
10220              gameMode == IcsPlayingWhite ||
10221              gameMode == IcsPlayingBlack ||
10222              gameMode == BeginningOfGame)) {
10223             SendToProgram("force\n", &first);
10224             if (first.usePing) {
10225               char buf[MSG_SIZ];
10226               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10227               SendToProgram(buf, &first);
10228             }
10229         }
10230     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10231         /* Kill off first chess program */
10232         if (first.isr != NULL)
10233           RemoveInputSource(first.isr);
10234         first.isr = NULL;
10235
10236         if (first.pr != NoProc) {
10237             ExitAnalyzeMode();
10238             DoSleep( appData.delayBeforeQuit );
10239             SendToProgram("quit\n", &first);
10240             DoSleep( appData.delayAfterQuit );
10241             DestroyChildProcess(first.pr, first.useSigterm);
10242         }
10243         first.pr = NoProc;
10244     }
10245     if (second.reuse) {
10246         /* Put second chess program into idle state */
10247         if (second.pr != NoProc &&
10248             gameMode == TwoMachinesPlay) {
10249             SendToProgram("force\n", &second);
10250             if (second.usePing) {
10251               char buf[MSG_SIZ];
10252               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10253               SendToProgram(buf, &second);
10254             }
10255         }
10256     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10257         /* Kill off second chess program */
10258         if (second.isr != NULL)
10259           RemoveInputSource(second.isr);
10260         second.isr = NULL;
10261
10262         if (second.pr != NoProc) {
10263             DoSleep( appData.delayBeforeQuit );
10264             SendToProgram("quit\n", &second);
10265             DoSleep( appData.delayAfterQuit );
10266             DestroyChildProcess(second.pr, second.useSigterm);
10267         }
10268         second.pr = NoProc;
10269     }
10270
10271     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10272         char resChar = '=';
10273         switch (result) {
10274         case WhiteWins:
10275           resChar = '+';
10276           if (first.twoMachinesColor[0] == 'w') {
10277             first.matchWins++;
10278           } else {
10279             second.matchWins++;
10280           }
10281           break;
10282         case BlackWins:
10283           resChar = '-';
10284           if (first.twoMachinesColor[0] == 'b') {
10285             first.matchWins++;
10286           } else {
10287             second.matchWins++;
10288           }
10289           break;
10290         case GameUnfinished:
10291           resChar = ' ';
10292         default:
10293           break;
10294         }
10295
10296         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10297         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10298             ReserveGame(nextGame, resChar); // sets nextGame
10299             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10300             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10301         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10302
10303         if (nextGame <= appData.matchGames && !abortMatch) {
10304             gameMode = nextGameMode;
10305             matchGame = nextGame; // this will be overruled in tourney mode!
10306             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10307             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10308             endingGame = 0; /* [HGM] crash */
10309             return;
10310         } else {
10311             gameMode = nextGameMode;
10312             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10313                      first.tidy, second.tidy,
10314                      first.matchWins, second.matchWins,
10315                      appData.matchGames - (first.matchWins + second.matchWins));
10316             if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10317             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10318             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10319                 first.twoMachinesColor = "black\n";
10320                 second.twoMachinesColor = "white\n";
10321             } else {
10322                 first.twoMachinesColor = "white\n";
10323                 second.twoMachinesColor = "black\n";
10324             }
10325         }
10326     }
10327     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10328         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10329       ExitAnalyzeMode();
10330     gameMode = nextGameMode;
10331     ModeHighlight();
10332     endingGame = 0;  /* [HGM] crash */
10333     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10334         if(matchMode == TRUE) { // match through command line: exit with or without popup
10335             if(ranking) {
10336                 ToNrEvent(forwardMostMove);
10337                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10338                 else ExitEvent(0);
10339             } else DisplayFatalError(buf, 0, 0);
10340         } else { // match through menu; just stop, with or without popup
10341             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10342             ModeHighlight();
10343             if(ranking){
10344                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10345             } else DisplayNote(buf);
10346       }
10347       if(ranking) free(ranking);
10348     }
10349 }
10350
10351 /* Assumes program was just initialized (initString sent).
10352    Leaves program in force mode. */
10353 void
10354 FeedMovesToProgram(cps, upto)
10355      ChessProgramState *cps;
10356      int upto;
10357 {
10358     int i;
10359
10360     if (appData.debugMode)
10361       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10362               startedFromSetupPosition ? "position and " : "",
10363               backwardMostMove, upto, cps->which);
10364     if(currentlyInitializedVariant != gameInfo.variant) {
10365       char buf[MSG_SIZ];
10366         // [HGM] variantswitch: make engine aware of new variant
10367         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10368                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10369         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10370         SendToProgram(buf, cps);
10371         currentlyInitializedVariant = gameInfo.variant;
10372     }
10373     SendToProgram("force\n", cps);
10374     if (startedFromSetupPosition) {
10375         SendBoard(cps, backwardMostMove);
10376     if (appData.debugMode) {
10377         fprintf(debugFP, "feedMoves\n");
10378     }
10379     }
10380     for (i = backwardMostMove; i < upto; i++) {
10381         SendMoveToProgram(i, cps);
10382     }
10383 }
10384
10385
10386 int
10387 ResurrectChessProgram()
10388 {
10389      /* The chess program may have exited.
10390         If so, restart it and feed it all the moves made so far. */
10391     static int doInit = 0;
10392
10393     if (appData.noChessProgram) return 1;
10394
10395     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10396         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10397         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10398         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10399     } else {
10400         if (first.pr != NoProc) return 1;
10401         StartChessProgram(&first);
10402     }
10403     InitChessProgram(&first, FALSE);
10404     FeedMovesToProgram(&first, currentMove);
10405
10406     if (!first.sendTime) {
10407         /* can't tell gnuchess what its clock should read,
10408            so we bow to its notion. */
10409         ResetClocks();
10410         timeRemaining[0][currentMove] = whiteTimeRemaining;
10411         timeRemaining[1][currentMove] = blackTimeRemaining;
10412     }
10413
10414     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10415                 appData.icsEngineAnalyze) && first.analysisSupport) {
10416       SendToProgram("analyze\n", &first);
10417       first.analyzing = TRUE;
10418     }
10419     return 1;
10420 }
10421
10422 /*
10423  * Button procedures
10424  */
10425 void
10426 Reset(redraw, init)
10427      int redraw, init;
10428 {
10429     int i;
10430
10431     if (appData.debugMode) {
10432         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10433                 redraw, init, gameMode);
10434     }
10435     CleanupTail(); // [HGM] vari: delete any stored variations
10436     pausing = pauseExamInvalid = FALSE;
10437     startedFromSetupPosition = blackPlaysFirst = FALSE;
10438     firstMove = TRUE;
10439     whiteFlag = blackFlag = FALSE;
10440     userOfferedDraw = FALSE;
10441     hintRequested = bookRequested = FALSE;
10442     first.maybeThinking = FALSE;
10443     second.maybeThinking = FALSE;
10444     first.bookSuspend = FALSE; // [HGM] book
10445     second.bookSuspend = FALSE;
10446     thinkOutput[0] = NULLCHAR;
10447     lastHint[0] = NULLCHAR;
10448     ClearGameInfo(&gameInfo);
10449     gameInfo.variant = StringToVariant(appData.variant);
10450     ics_user_moved = ics_clock_paused = FALSE;
10451     ics_getting_history = H_FALSE;
10452     ics_gamenum = -1;
10453     white_holding[0] = black_holding[0] = NULLCHAR;
10454     ClearProgramStats();
10455     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10456
10457     ResetFrontEnd();
10458     ClearHighlights();
10459     flipView = appData.flipView;
10460     ClearPremoveHighlights();
10461     gotPremove = FALSE;
10462     alarmSounded = FALSE;
10463
10464     GameEnds(EndOfFile, NULL, GE_PLAYER);
10465     if(appData.serverMovesName != NULL) {
10466         /* [HGM] prepare to make moves file for broadcasting */
10467         clock_t t = clock();
10468         if(serverMoves != NULL) fclose(serverMoves);
10469         serverMoves = fopen(appData.serverMovesName, "r");
10470         if(serverMoves != NULL) {
10471             fclose(serverMoves);
10472             /* delay 15 sec before overwriting, so all clients can see end */
10473             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10474         }
10475         serverMoves = fopen(appData.serverMovesName, "w");
10476     }
10477
10478     ExitAnalyzeMode();
10479     gameMode = BeginningOfGame;
10480     ModeHighlight();
10481     if(appData.icsActive) gameInfo.variant = VariantNormal;
10482     currentMove = forwardMostMove = backwardMostMove = 0;
10483     InitPosition(redraw);
10484     for (i = 0; i < MAX_MOVES; i++) {
10485         if (commentList[i] != NULL) {
10486             free(commentList[i]);
10487             commentList[i] = NULL;
10488         }
10489     }
10490     ResetClocks();
10491     timeRemaining[0][0] = whiteTimeRemaining;
10492     timeRemaining[1][0] = blackTimeRemaining;
10493
10494     if (first.pr == NULL) {
10495         StartChessProgram(&first);
10496     }
10497     if (init) {
10498             InitChessProgram(&first, startedFromSetupPosition);
10499     }
10500     DisplayTitle("");
10501     DisplayMessage("", "");
10502     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10503     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10504 }
10505
10506 void
10507 AutoPlayGameLoop()
10508 {
10509     for (;;) {
10510         if (!AutoPlayOneMove())
10511           return;
10512         if (matchMode || appData.timeDelay == 0)
10513           continue;
10514         if (appData.timeDelay < 0)
10515           return;
10516         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10517         break;
10518     }
10519 }
10520
10521
10522 int
10523 AutoPlayOneMove()
10524 {
10525     int fromX, fromY, toX, toY;
10526
10527     if (appData.debugMode) {
10528       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10529     }
10530
10531     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10532       return FALSE;
10533
10534     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10535       pvInfoList[currentMove].depth = programStats.depth;
10536       pvInfoList[currentMove].score = programStats.score;
10537       pvInfoList[currentMove].time  = 0;
10538       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10539     }
10540
10541     if (currentMove >= forwardMostMove) {
10542       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10543       gameMode = EditGame;
10544       ModeHighlight();
10545
10546       /* [AS] Clear current move marker at the end of a game */
10547       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10548
10549       return FALSE;
10550     }
10551
10552     toX = moveList[currentMove][2] - AAA;
10553     toY = moveList[currentMove][3] - ONE;
10554
10555     if (moveList[currentMove][1] == '@') {
10556         if (appData.highlightLastMove) {
10557             SetHighlights(-1, -1, toX, toY);
10558         }
10559     } else {
10560         fromX = moveList[currentMove][0] - AAA;
10561         fromY = moveList[currentMove][1] - ONE;
10562
10563         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10564
10565         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10566
10567         if (appData.highlightLastMove) {
10568             SetHighlights(fromX, fromY, toX, toY);
10569         }
10570     }
10571     DisplayMove(currentMove);
10572     SendMoveToProgram(currentMove++, &first);
10573     DisplayBothClocks();
10574     DrawPosition(FALSE, boards[currentMove]);
10575     // [HGM] PV info: always display, routine tests if empty
10576     DisplayComment(currentMove - 1, commentList[currentMove]);
10577     return TRUE;
10578 }
10579
10580
10581 int
10582 LoadGameOneMove(readAhead)
10583      ChessMove readAhead;
10584 {
10585     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10586     char promoChar = NULLCHAR;
10587     ChessMove moveType;
10588     char move[MSG_SIZ];
10589     char *p, *q;
10590
10591     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10592         gameMode != AnalyzeMode && gameMode != Training) {
10593         gameFileFP = NULL;
10594         return FALSE;
10595     }
10596
10597     yyboardindex = forwardMostMove;
10598     if (readAhead != EndOfFile) {
10599       moveType = readAhead;
10600     } else {
10601       if (gameFileFP == NULL)
10602           return FALSE;
10603       moveType = (ChessMove) Myylex();
10604     }
10605
10606     done = FALSE;
10607     switch (moveType) {
10608       case Comment:
10609         if (appData.debugMode)
10610           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10611         p = yy_text;
10612
10613         /* append the comment but don't display it */
10614         AppendComment(currentMove, p, FALSE);
10615         return TRUE;
10616
10617       case WhiteCapturesEnPassant:
10618       case BlackCapturesEnPassant:
10619       case WhitePromotion:
10620       case BlackPromotion:
10621       case WhiteNonPromotion:
10622       case BlackNonPromotion:
10623       case NormalMove:
10624       case WhiteKingSideCastle:
10625       case WhiteQueenSideCastle:
10626       case BlackKingSideCastle:
10627       case BlackQueenSideCastle:
10628       case WhiteKingSideCastleWild:
10629       case WhiteQueenSideCastleWild:
10630       case BlackKingSideCastleWild:
10631       case BlackQueenSideCastleWild:
10632       /* PUSH Fabien */
10633       case WhiteHSideCastleFR:
10634       case WhiteASideCastleFR:
10635       case BlackHSideCastleFR:
10636       case BlackASideCastleFR:
10637       /* POP Fabien */
10638         if (appData.debugMode)
10639           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10640         fromX = currentMoveString[0] - AAA;
10641         fromY = currentMoveString[1] - ONE;
10642         toX = currentMoveString[2] - AAA;
10643         toY = currentMoveString[3] - ONE;
10644         promoChar = currentMoveString[4];
10645         break;
10646
10647       case WhiteDrop:
10648       case BlackDrop:
10649         if (appData.debugMode)
10650           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10651         fromX = moveType == WhiteDrop ?
10652           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10653         (int) CharToPiece(ToLower(currentMoveString[0]));
10654         fromY = DROP_RANK;
10655         toX = currentMoveString[2] - AAA;
10656         toY = currentMoveString[3] - ONE;
10657         break;
10658
10659       case WhiteWins:
10660       case BlackWins:
10661       case GameIsDrawn:
10662       case GameUnfinished:
10663         if (appData.debugMode)
10664           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10665         p = strchr(yy_text, '{');
10666         if (p == NULL) p = strchr(yy_text, '(');
10667         if (p == NULL) {
10668             p = yy_text;
10669             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10670         } else {
10671             q = strchr(p, *p == '{' ? '}' : ')');
10672             if (q != NULL) *q = NULLCHAR;
10673             p++;
10674         }
10675         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10676         GameEnds(moveType, p, GE_FILE);
10677         done = TRUE;
10678         if (cmailMsgLoaded) {
10679             ClearHighlights();
10680             flipView = WhiteOnMove(currentMove);
10681             if (moveType == GameUnfinished) flipView = !flipView;
10682             if (appData.debugMode)
10683               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10684         }
10685         break;
10686
10687       case EndOfFile:
10688         if (appData.debugMode)
10689           fprintf(debugFP, "Parser hit end of file\n");
10690         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10691           case MT_NONE:
10692           case MT_CHECK:
10693             break;
10694           case MT_CHECKMATE:
10695           case MT_STAINMATE:
10696             if (WhiteOnMove(currentMove)) {
10697                 GameEnds(BlackWins, "Black mates", GE_FILE);
10698             } else {
10699                 GameEnds(WhiteWins, "White mates", GE_FILE);
10700             }
10701             break;
10702           case MT_STALEMATE:
10703             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10704             break;
10705         }
10706         done = TRUE;
10707         break;
10708
10709       case MoveNumberOne:
10710         if (lastLoadGameStart == GNUChessGame) {
10711             /* GNUChessGames have numbers, but they aren't move numbers */
10712             if (appData.debugMode)
10713               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10714                       yy_text, (int) moveType);
10715             return LoadGameOneMove(EndOfFile); /* tail recursion */
10716         }
10717         /* else fall thru */
10718
10719       case XBoardGame:
10720       case GNUChessGame:
10721       case PGNTag:
10722         /* Reached start of next game in file */
10723         if (appData.debugMode)
10724           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10725         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10726           case MT_NONE:
10727           case MT_CHECK:
10728             break;
10729           case MT_CHECKMATE:
10730           case MT_STAINMATE:
10731             if (WhiteOnMove(currentMove)) {
10732                 GameEnds(BlackWins, "Black mates", GE_FILE);
10733             } else {
10734                 GameEnds(WhiteWins, "White mates", GE_FILE);
10735             }
10736             break;
10737           case MT_STALEMATE:
10738             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10739             break;
10740         }
10741         done = TRUE;
10742         break;
10743
10744       case PositionDiagram:     /* should not happen; ignore */
10745       case ElapsedTime:         /* ignore */
10746       case NAG:                 /* ignore */
10747         if (appData.debugMode)
10748           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10749                   yy_text, (int) moveType);
10750         return LoadGameOneMove(EndOfFile); /* tail recursion */
10751
10752       case IllegalMove:
10753         if (appData.testLegality) {
10754             if (appData.debugMode)
10755               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10756             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10757                     (forwardMostMove / 2) + 1,
10758                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10759             DisplayError(move, 0);
10760             done = TRUE;
10761         } else {
10762             if (appData.debugMode)
10763               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10764                       yy_text, currentMoveString);
10765             fromX = currentMoveString[0] - AAA;
10766             fromY = currentMoveString[1] - ONE;
10767             toX = currentMoveString[2] - AAA;
10768             toY = currentMoveString[3] - ONE;
10769             promoChar = currentMoveString[4];
10770         }
10771         break;
10772
10773       case AmbiguousMove:
10774         if (appData.debugMode)
10775           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10776         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10777                 (forwardMostMove / 2) + 1,
10778                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10779         DisplayError(move, 0);
10780         done = TRUE;
10781         break;
10782
10783       default:
10784       case ImpossibleMove:
10785         if (appData.debugMode)
10786           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10787         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10788                 (forwardMostMove / 2) + 1,
10789                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10790         DisplayError(move, 0);
10791         done = TRUE;
10792         break;
10793     }
10794
10795     if (done) {
10796         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10797             DrawPosition(FALSE, boards[currentMove]);
10798             DisplayBothClocks();
10799             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10800               DisplayComment(currentMove - 1, commentList[currentMove]);
10801         }
10802         (void) StopLoadGameTimer();
10803         gameFileFP = NULL;
10804         cmailOldMove = forwardMostMove;
10805         return FALSE;
10806     } else {
10807         /* currentMoveString is set as a side-effect of yylex */
10808
10809         thinkOutput[0] = NULLCHAR;
10810         MakeMove(fromX, fromY, toX, toY, promoChar);
10811         currentMove = forwardMostMove;
10812         return TRUE;
10813     }
10814 }
10815
10816 /* Load the nth game from the given file */
10817 int
10818 LoadGameFromFile(filename, n, title, useList)
10819      char *filename;
10820      int n;
10821      char *title;
10822      /*Boolean*/ int useList;
10823 {
10824     FILE *f;
10825     char buf[MSG_SIZ];
10826
10827     if (strcmp(filename, "-") == 0) {
10828         f = stdin;
10829         title = "stdin";
10830     } else {
10831         f = fopen(filename, "rb");
10832         if (f == NULL) {
10833           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10834             DisplayError(buf, errno);
10835             return FALSE;
10836         }
10837     }
10838     if (fseek(f, 0, 0) == -1) {
10839         /* f is not seekable; probably a pipe */
10840         useList = FALSE;
10841     }
10842     if (useList && n == 0) {
10843         int error = GameListBuild(f);
10844         if (error) {
10845             DisplayError(_("Cannot build game list"), error);
10846         } else if (!ListEmpty(&gameList) &&
10847                    ((ListGame *) gameList.tailPred)->number > 1) {
10848             GameListPopUp(f, title);
10849             return TRUE;
10850         }
10851         GameListDestroy();
10852         n = 1;
10853     }
10854     if (n == 0) n = 1;
10855     return LoadGame(f, n, title, FALSE);
10856 }
10857
10858
10859 void
10860 MakeRegisteredMove()
10861 {
10862     int fromX, fromY, toX, toY;
10863     char promoChar;
10864     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10865         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10866           case CMAIL_MOVE:
10867           case CMAIL_DRAW:
10868             if (appData.debugMode)
10869               fprintf(debugFP, "Restoring %s for game %d\n",
10870                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10871
10872             thinkOutput[0] = NULLCHAR;
10873             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10874             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10875             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10876             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10877             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10878             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10879             MakeMove(fromX, fromY, toX, toY, promoChar);
10880             ShowMove(fromX, fromY, toX, toY);
10881
10882             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10883               case MT_NONE:
10884               case MT_CHECK:
10885                 break;
10886
10887               case MT_CHECKMATE:
10888               case MT_STAINMATE:
10889                 if (WhiteOnMove(currentMove)) {
10890                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10891                 } else {
10892                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10893                 }
10894                 break;
10895
10896               case MT_STALEMATE:
10897                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10898                 break;
10899             }
10900
10901             break;
10902
10903           case CMAIL_RESIGN:
10904             if (WhiteOnMove(currentMove)) {
10905                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10906             } else {
10907                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10908             }
10909             break;
10910
10911           case CMAIL_ACCEPT:
10912             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10913             break;
10914
10915           default:
10916             break;
10917         }
10918     }
10919
10920     return;
10921 }
10922
10923 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10924 int
10925 CmailLoadGame(f, gameNumber, title, useList)
10926      FILE *f;
10927      int gameNumber;
10928      char *title;
10929      int useList;
10930 {
10931     int retVal;
10932
10933     if (gameNumber > nCmailGames) {
10934         DisplayError(_("No more games in this message"), 0);
10935         return FALSE;
10936     }
10937     if (f == lastLoadGameFP) {
10938         int offset = gameNumber - lastLoadGameNumber;
10939         if (offset == 0) {
10940             cmailMsg[0] = NULLCHAR;
10941             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10942                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10943                 nCmailMovesRegistered--;
10944             }
10945             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10946             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10947                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10948             }
10949         } else {
10950             if (! RegisterMove()) return FALSE;
10951         }
10952     }
10953
10954     retVal = LoadGame(f, gameNumber, title, useList);
10955
10956     /* Make move registered during previous look at this game, if any */
10957     MakeRegisteredMove();
10958
10959     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10960         commentList[currentMove]
10961           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10962         DisplayComment(currentMove - 1, commentList[currentMove]);
10963     }
10964
10965     return retVal;
10966 }
10967
10968 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10969 int
10970 ReloadGame(offset)
10971      int offset;
10972 {
10973     int gameNumber = lastLoadGameNumber + offset;
10974     if (lastLoadGameFP == NULL) {
10975         DisplayError(_("No game has been loaded yet"), 0);
10976         return FALSE;
10977     }
10978     if (gameNumber <= 0) {
10979         DisplayError(_("Can't back up any further"), 0);
10980         return FALSE;
10981     }
10982     if (cmailMsgLoaded) {
10983         return CmailLoadGame(lastLoadGameFP, gameNumber,
10984                              lastLoadGameTitle, lastLoadGameUseList);
10985     } else {
10986         return LoadGame(lastLoadGameFP, gameNumber,
10987                         lastLoadGameTitle, lastLoadGameUseList);
10988     }
10989 }
10990
10991
10992
10993 /* Load the nth game from open file f */
10994 int
10995 LoadGame(f, gameNumber, title, useList)
10996      FILE *f;
10997      int gameNumber;
10998      char *title;
10999      int useList;
11000 {
11001     ChessMove cm;
11002     char buf[MSG_SIZ];
11003     int gn = gameNumber;
11004     ListGame *lg = NULL;
11005     int numPGNTags = 0;
11006     int err;
11007     GameMode oldGameMode;
11008     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11009
11010     if (appData.debugMode)
11011         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11012
11013     if (gameMode == Training )
11014         SetTrainingModeOff();
11015
11016     oldGameMode = gameMode;
11017     if (gameMode != BeginningOfGame) {
11018       Reset(FALSE, TRUE);
11019     }
11020
11021     gameFileFP = f;
11022     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11023         fclose(lastLoadGameFP);
11024     }
11025
11026     if (useList) {
11027         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11028
11029         if (lg) {
11030             fseek(f, lg->offset, 0);
11031             GameListHighlight(gameNumber);
11032             gn = 1;
11033         }
11034         else {
11035             DisplayError(_("Game number out of range"), 0);
11036             return FALSE;
11037         }
11038     } else {
11039         GameListDestroy();
11040         if (fseek(f, 0, 0) == -1) {
11041             if (f == lastLoadGameFP ?
11042                 gameNumber == lastLoadGameNumber + 1 :
11043                 gameNumber == 1) {
11044                 gn = 1;
11045             } else {
11046                 DisplayError(_("Can't seek on game file"), 0);
11047                 return FALSE;
11048             }
11049         }
11050     }
11051     lastLoadGameFP = f;
11052     lastLoadGameNumber = gameNumber;
11053     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11054     lastLoadGameUseList = useList;
11055
11056     yynewfile(f);
11057
11058     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11059       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11060                 lg->gameInfo.black);
11061             DisplayTitle(buf);
11062     } else if (*title != NULLCHAR) {
11063         if (gameNumber > 1) {
11064           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11065             DisplayTitle(buf);
11066         } else {
11067             DisplayTitle(title);
11068         }
11069     }
11070
11071     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11072         gameMode = PlayFromGameFile;
11073         ModeHighlight();
11074     }
11075
11076     currentMove = forwardMostMove = backwardMostMove = 0;
11077     CopyBoard(boards[0], initialPosition);
11078     StopClocks();
11079
11080     /*
11081      * Skip the first gn-1 games in the file.
11082      * Also skip over anything that precedes an identifiable
11083      * start of game marker, to avoid being confused by
11084      * garbage at the start of the file.  Currently
11085      * recognized start of game markers are the move number "1",
11086      * the pattern "gnuchess .* game", the pattern
11087      * "^[#;%] [^ ]* game file", and a PGN tag block.
11088      * A game that starts with one of the latter two patterns
11089      * will also have a move number 1, possibly
11090      * following a position diagram.
11091      * 5-4-02: Let's try being more lenient and allowing a game to
11092      * start with an unnumbered move.  Does that break anything?
11093      */
11094     cm = lastLoadGameStart = EndOfFile;
11095     while (gn > 0) {
11096         yyboardindex = forwardMostMove;
11097         cm = (ChessMove) Myylex();
11098         switch (cm) {
11099           case EndOfFile:
11100             if (cmailMsgLoaded) {
11101                 nCmailGames = CMAIL_MAX_GAMES - gn;
11102             } else {
11103                 Reset(TRUE, TRUE);
11104                 DisplayError(_("Game not found in file"), 0);
11105             }
11106             return FALSE;
11107
11108           case GNUChessGame:
11109           case XBoardGame:
11110             gn--;
11111             lastLoadGameStart = cm;
11112             break;
11113
11114           case MoveNumberOne:
11115             switch (lastLoadGameStart) {
11116               case GNUChessGame:
11117               case XBoardGame:
11118               case PGNTag:
11119                 break;
11120               case MoveNumberOne:
11121               case EndOfFile:
11122                 gn--;           /* count this game */
11123                 lastLoadGameStart = cm;
11124                 break;
11125               default:
11126                 /* impossible */
11127                 break;
11128             }
11129             break;
11130
11131           case PGNTag:
11132             switch (lastLoadGameStart) {
11133               case GNUChessGame:
11134               case PGNTag:
11135               case MoveNumberOne:
11136               case EndOfFile:
11137                 gn--;           /* count this game */
11138                 lastLoadGameStart = cm;
11139                 break;
11140               case XBoardGame:
11141                 lastLoadGameStart = cm; /* game counted already */
11142                 break;
11143               default:
11144                 /* impossible */
11145                 break;
11146             }
11147             if (gn > 0) {
11148                 do {
11149                     yyboardindex = forwardMostMove;
11150                     cm = (ChessMove) Myylex();
11151                 } while (cm == PGNTag || cm == Comment);
11152             }
11153             break;
11154
11155           case WhiteWins:
11156           case BlackWins:
11157           case GameIsDrawn:
11158             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11159                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11160                     != CMAIL_OLD_RESULT) {
11161                     nCmailResults ++ ;
11162                     cmailResult[  CMAIL_MAX_GAMES
11163                                 - gn - 1] = CMAIL_OLD_RESULT;
11164                 }
11165             }
11166             break;
11167
11168           case NormalMove:
11169             /* Only a NormalMove can be at the start of a game
11170              * without a position diagram. */
11171             if (lastLoadGameStart == EndOfFile ) {
11172               gn--;
11173               lastLoadGameStart = MoveNumberOne;
11174             }
11175             break;
11176
11177           default:
11178             break;
11179         }
11180     }
11181
11182     if (appData.debugMode)
11183       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11184
11185     if (cm == XBoardGame) {
11186         /* Skip any header junk before position diagram and/or move 1 */
11187         for (;;) {
11188             yyboardindex = forwardMostMove;
11189             cm = (ChessMove) Myylex();
11190
11191             if (cm == EndOfFile ||
11192                 cm == GNUChessGame || cm == XBoardGame) {
11193                 /* Empty game; pretend end-of-file and handle later */
11194                 cm = EndOfFile;
11195                 break;
11196             }
11197
11198             if (cm == MoveNumberOne || cm == PositionDiagram ||
11199                 cm == PGNTag || cm == Comment)
11200               break;
11201         }
11202     } else if (cm == GNUChessGame) {
11203         if (gameInfo.event != NULL) {
11204             free(gameInfo.event);
11205         }
11206         gameInfo.event = StrSave(yy_text);
11207     }
11208
11209     startedFromSetupPosition = FALSE;
11210     while (cm == PGNTag) {
11211         if (appData.debugMode)
11212           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11213         err = ParsePGNTag(yy_text, &gameInfo);
11214         if (!err) numPGNTags++;
11215
11216         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11217         if(gameInfo.variant != oldVariant) {
11218             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11219             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11220             InitPosition(TRUE);
11221             oldVariant = gameInfo.variant;
11222             if (appData.debugMode)
11223               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11224         }
11225
11226
11227         if (gameInfo.fen != NULL) {
11228           Board initial_position;
11229           startedFromSetupPosition = TRUE;
11230           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11231             Reset(TRUE, TRUE);
11232             DisplayError(_("Bad FEN position in file"), 0);
11233             return FALSE;
11234           }
11235           CopyBoard(boards[0], initial_position);
11236           if (blackPlaysFirst) {
11237             currentMove = forwardMostMove = backwardMostMove = 1;
11238             CopyBoard(boards[1], initial_position);
11239             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11240             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11241             timeRemaining[0][1] = whiteTimeRemaining;
11242             timeRemaining[1][1] = blackTimeRemaining;
11243             if (commentList[0] != NULL) {
11244               commentList[1] = commentList[0];
11245               commentList[0] = NULL;
11246             }
11247           } else {
11248             currentMove = forwardMostMove = backwardMostMove = 0;
11249           }
11250           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11251           {   int i;
11252               initialRulePlies = FENrulePlies;
11253               for( i=0; i< nrCastlingRights; i++ )
11254                   initialRights[i] = initial_position[CASTLING][i];
11255           }
11256           yyboardindex = forwardMostMove;
11257           free(gameInfo.fen);
11258           gameInfo.fen = NULL;
11259         }
11260
11261         yyboardindex = forwardMostMove;
11262         cm = (ChessMove) Myylex();
11263
11264         /* Handle comments interspersed among the tags */
11265         while (cm == Comment) {
11266             char *p;
11267             if (appData.debugMode)
11268               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11269             p = yy_text;
11270             AppendComment(currentMove, p, FALSE);
11271             yyboardindex = forwardMostMove;
11272             cm = (ChessMove) Myylex();
11273         }
11274     }
11275
11276     /* don't rely on existence of Event tag since if game was
11277      * pasted from clipboard the Event tag may not exist
11278      */
11279     if (numPGNTags > 0){
11280         char *tags;
11281         if (gameInfo.variant == VariantNormal) {
11282           VariantClass v = StringToVariant(gameInfo.event);
11283           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11284           if(v < VariantShogi) gameInfo.variant = v;
11285         }
11286         if (!matchMode) {
11287           if( appData.autoDisplayTags ) {
11288             tags = PGNTags(&gameInfo);
11289             TagsPopUp(tags, CmailMsg());
11290             free(tags);
11291           }
11292         }
11293     } else {
11294         /* Make something up, but don't display it now */
11295         SetGameInfo();
11296         TagsPopDown();
11297     }
11298
11299     if (cm == PositionDiagram) {
11300         int i, j;
11301         char *p;
11302         Board initial_position;
11303
11304         if (appData.debugMode)
11305           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11306
11307         if (!startedFromSetupPosition) {
11308             p = yy_text;
11309             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11310               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11311                 switch (*p) {
11312                   case '{':
11313                   case '[':
11314                   case '-':
11315                   case ' ':
11316                   case '\t':
11317                   case '\n':
11318                   case '\r':
11319                     break;
11320                   default:
11321                     initial_position[i][j++] = CharToPiece(*p);
11322                     break;
11323                 }
11324             while (*p == ' ' || *p == '\t' ||
11325                    *p == '\n' || *p == '\r') p++;
11326
11327             if (strncmp(p, "black", strlen("black"))==0)
11328               blackPlaysFirst = TRUE;
11329             else
11330               blackPlaysFirst = FALSE;
11331             startedFromSetupPosition = TRUE;
11332
11333             CopyBoard(boards[0], initial_position);
11334             if (blackPlaysFirst) {
11335                 currentMove = forwardMostMove = backwardMostMove = 1;
11336                 CopyBoard(boards[1], initial_position);
11337                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11338                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11339                 timeRemaining[0][1] = whiteTimeRemaining;
11340                 timeRemaining[1][1] = blackTimeRemaining;
11341                 if (commentList[0] != NULL) {
11342                     commentList[1] = commentList[0];
11343                     commentList[0] = NULL;
11344                 }
11345             } else {
11346                 currentMove = forwardMostMove = backwardMostMove = 0;
11347             }
11348         }
11349         yyboardindex = forwardMostMove;
11350         cm = (ChessMove) Myylex();
11351     }
11352
11353     if (first.pr == NoProc) {
11354         StartChessProgram(&first);
11355     }
11356     InitChessProgram(&first, FALSE);
11357     SendToProgram("force\n", &first);
11358     if (startedFromSetupPosition) {
11359         SendBoard(&first, forwardMostMove);
11360     if (appData.debugMode) {
11361         fprintf(debugFP, "Load Game\n");
11362     }
11363         DisplayBothClocks();
11364     }
11365
11366     /* [HGM] server: flag to write setup moves in broadcast file as one */
11367     loadFlag = appData.suppressLoadMoves;
11368
11369     while (cm == Comment) {
11370         char *p;
11371         if (appData.debugMode)
11372           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11373         p = yy_text;
11374         AppendComment(currentMove, p, FALSE);
11375         yyboardindex = forwardMostMove;
11376         cm = (ChessMove) Myylex();
11377     }
11378
11379     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11380         cm == WhiteWins || cm == BlackWins ||
11381         cm == GameIsDrawn || cm == GameUnfinished) {
11382         DisplayMessage("", _("No moves in game"));
11383         if (cmailMsgLoaded) {
11384             if (appData.debugMode)
11385               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11386             ClearHighlights();
11387             flipView = FALSE;
11388         }
11389         DrawPosition(FALSE, boards[currentMove]);
11390         DisplayBothClocks();
11391         gameMode = EditGame;
11392         ModeHighlight();
11393         gameFileFP = NULL;
11394         cmailOldMove = 0;
11395         return TRUE;
11396     }
11397
11398     // [HGM] PV info: routine tests if comment empty
11399     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11400         DisplayComment(currentMove - 1, commentList[currentMove]);
11401     }
11402     if (!matchMode && appData.timeDelay != 0)
11403       DrawPosition(FALSE, boards[currentMove]);
11404
11405     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11406       programStats.ok_to_send = 1;
11407     }
11408
11409     /* if the first token after the PGN tags is a move
11410      * and not move number 1, retrieve it from the parser
11411      */
11412     if (cm != MoveNumberOne)
11413         LoadGameOneMove(cm);
11414
11415     /* load the remaining moves from the file */
11416     while (LoadGameOneMove(EndOfFile)) {
11417       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11418       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11419     }
11420
11421     /* rewind to the start of the game */
11422     currentMove = backwardMostMove;
11423
11424     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11425
11426     if (oldGameMode == AnalyzeFile ||
11427         oldGameMode == AnalyzeMode) {
11428       AnalyzeFileEvent();
11429     }
11430
11431     if (matchMode || appData.timeDelay == 0) {
11432       ToEndEvent();
11433       gameMode = EditGame;
11434       ModeHighlight();
11435     } else if (appData.timeDelay > 0) {
11436       AutoPlayGameLoop();
11437     }
11438
11439     if (appData.debugMode)
11440         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11441
11442     loadFlag = 0; /* [HGM] true game starts */
11443     return TRUE;
11444 }
11445
11446 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11447 int
11448 ReloadPosition(offset)
11449      int offset;
11450 {
11451     int positionNumber = lastLoadPositionNumber + offset;
11452     if (lastLoadPositionFP == NULL) {
11453         DisplayError(_("No position has been loaded yet"), 0);
11454         return FALSE;
11455     }
11456     if (positionNumber <= 0) {
11457         DisplayError(_("Can't back up any further"), 0);
11458         return FALSE;
11459     }
11460     return LoadPosition(lastLoadPositionFP, positionNumber,
11461                         lastLoadPositionTitle);
11462 }
11463
11464 /* Load the nth position from the given file */
11465 int
11466 LoadPositionFromFile(filename, n, title)
11467      char *filename;
11468      int n;
11469      char *title;
11470 {
11471     FILE *f;
11472     char buf[MSG_SIZ];
11473
11474     if (strcmp(filename, "-") == 0) {
11475         return LoadPosition(stdin, n, "stdin");
11476     } else {
11477         f = fopen(filename, "rb");
11478         if (f == NULL) {
11479             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11480             DisplayError(buf, errno);
11481             return FALSE;
11482         } else {
11483             return LoadPosition(f, n, title);
11484         }
11485     }
11486 }
11487
11488 /* Load the nth position from the given open file, and close it */
11489 int
11490 LoadPosition(f, positionNumber, title)
11491      FILE *f;
11492      int positionNumber;
11493      char *title;
11494 {
11495     char *p, line[MSG_SIZ];
11496     Board initial_position;
11497     int i, j, fenMode, pn;
11498
11499     if (gameMode == Training )
11500         SetTrainingModeOff();
11501
11502     if (gameMode != BeginningOfGame) {
11503         Reset(FALSE, TRUE);
11504     }
11505     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11506         fclose(lastLoadPositionFP);
11507     }
11508     if (positionNumber == 0) positionNumber = 1;
11509     lastLoadPositionFP = f;
11510     lastLoadPositionNumber = positionNumber;
11511     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11512     if (first.pr == NoProc) {
11513       StartChessProgram(&first);
11514       InitChessProgram(&first, FALSE);
11515     }
11516     pn = positionNumber;
11517     if (positionNumber < 0) {
11518         /* Negative position number means to seek to that byte offset */
11519         if (fseek(f, -positionNumber, 0) == -1) {
11520             DisplayError(_("Can't seek on position file"), 0);
11521             return FALSE;
11522         };
11523         pn = 1;
11524     } else {
11525         if (fseek(f, 0, 0) == -1) {
11526             if (f == lastLoadPositionFP ?
11527                 positionNumber == lastLoadPositionNumber + 1 :
11528                 positionNumber == 1) {
11529                 pn = 1;
11530             } else {
11531                 DisplayError(_("Can't seek on position file"), 0);
11532                 return FALSE;
11533             }
11534         }
11535     }
11536     /* See if this file is FEN or old-style xboard */
11537     if (fgets(line, MSG_SIZ, f) == NULL) {
11538         DisplayError(_("Position not found in file"), 0);
11539         return FALSE;
11540     }
11541     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11542     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11543
11544     if (pn >= 2) {
11545         if (fenMode || line[0] == '#') pn--;
11546         while (pn > 0) {
11547             /* skip positions before number pn */
11548             if (fgets(line, MSG_SIZ, f) == NULL) {
11549                 Reset(TRUE, TRUE);
11550                 DisplayError(_("Position not found in file"), 0);
11551                 return FALSE;
11552             }
11553             if (fenMode || line[0] == '#') pn--;
11554         }
11555     }
11556
11557     if (fenMode) {
11558         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11559             DisplayError(_("Bad FEN position in file"), 0);
11560             return FALSE;
11561         }
11562     } else {
11563         (void) fgets(line, MSG_SIZ, f);
11564         (void) fgets(line, MSG_SIZ, f);
11565
11566         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11567             (void) fgets(line, MSG_SIZ, f);
11568             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11569                 if (*p == ' ')
11570                   continue;
11571                 initial_position[i][j++] = CharToPiece(*p);
11572             }
11573         }
11574
11575         blackPlaysFirst = FALSE;
11576         if (!feof(f)) {
11577             (void) fgets(line, MSG_SIZ, f);
11578             if (strncmp(line, "black", strlen("black"))==0)
11579               blackPlaysFirst = TRUE;
11580         }
11581     }
11582     startedFromSetupPosition = TRUE;
11583
11584     SendToProgram("force\n", &first);
11585     CopyBoard(boards[0], initial_position);
11586     if (blackPlaysFirst) {
11587         currentMove = forwardMostMove = backwardMostMove = 1;
11588         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11589         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11590         CopyBoard(boards[1], initial_position);
11591         DisplayMessage("", _("Black to play"));
11592     } else {
11593         currentMove = forwardMostMove = backwardMostMove = 0;
11594         DisplayMessage("", _("White to play"));
11595     }
11596     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11597     SendBoard(&first, forwardMostMove);
11598     if (appData.debugMode) {
11599 int i, j;
11600   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11601   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11602         fprintf(debugFP, "Load Position\n");
11603     }
11604
11605     if (positionNumber > 1) {
11606       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11607         DisplayTitle(line);
11608     } else {
11609         DisplayTitle(title);
11610     }
11611     gameMode = EditGame;
11612     ModeHighlight();
11613     ResetClocks();
11614     timeRemaining[0][1] = whiteTimeRemaining;
11615     timeRemaining[1][1] = blackTimeRemaining;
11616     DrawPosition(FALSE, boards[currentMove]);
11617
11618     return TRUE;
11619 }
11620
11621
11622 void
11623 CopyPlayerNameIntoFileName(dest, src)
11624      char **dest, *src;
11625 {
11626     while (*src != NULLCHAR && *src != ',') {
11627         if (*src == ' ') {
11628             *(*dest)++ = '_';
11629             src++;
11630         } else {
11631             *(*dest)++ = *src++;
11632         }
11633     }
11634 }
11635
11636 char *DefaultFileName(ext)
11637      char *ext;
11638 {
11639     static char def[MSG_SIZ];
11640     char *p;
11641
11642     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11643         p = def;
11644         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11645         *p++ = '-';
11646         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11647         *p++ = '.';
11648         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11649     } else {
11650         def[0] = NULLCHAR;
11651     }
11652     return def;
11653 }
11654
11655 /* Save the current game to the given file */
11656 int
11657 SaveGameToFile(filename, append)
11658      char *filename;
11659      int append;
11660 {
11661     FILE *f;
11662     char buf[MSG_SIZ];
11663     int result;
11664
11665     if (strcmp(filename, "-") == 0) {
11666         return SaveGame(stdout, 0, NULL);
11667     } else {
11668         f = fopen(filename, append ? "a" : "w");
11669         if (f == NULL) {
11670             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11671             DisplayError(buf, errno);
11672             return FALSE;
11673         } else {
11674             safeStrCpy(buf, lastMsg, MSG_SIZ);
11675             DisplayMessage(_("Waiting for access to save file"), "");
11676             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11677             DisplayMessage(_("Saving game"), "");
11678             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11679             result = SaveGame(f, 0, NULL);
11680             DisplayMessage(buf, "");
11681             return result;
11682         }
11683     }
11684 }
11685
11686 char *
11687 SavePart(str)
11688      char *str;
11689 {
11690     static char buf[MSG_SIZ];
11691     char *p;
11692
11693     p = strchr(str, ' ');
11694     if (p == NULL) return str;
11695     strncpy(buf, str, p - str);
11696     buf[p - str] = NULLCHAR;
11697     return buf;
11698 }
11699
11700 #define PGN_MAX_LINE 75
11701
11702 #define PGN_SIDE_WHITE  0
11703 #define PGN_SIDE_BLACK  1
11704
11705 /* [AS] */
11706 static int FindFirstMoveOutOfBook( int side )
11707 {
11708     int result = -1;
11709
11710     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11711         int index = backwardMostMove;
11712         int has_book_hit = 0;
11713
11714         if( (index % 2) != side ) {
11715             index++;
11716         }
11717
11718         while( index < forwardMostMove ) {
11719             /* Check to see if engine is in book */
11720             int depth = pvInfoList[index].depth;
11721             int score = pvInfoList[index].score;
11722             int in_book = 0;
11723
11724             if( depth <= 2 ) {
11725                 in_book = 1;
11726             }
11727             else if( score == 0 && depth == 63 ) {
11728                 in_book = 1; /* Zappa */
11729             }
11730             else if( score == 2 && depth == 99 ) {
11731                 in_book = 1; /* Abrok */
11732             }
11733
11734             has_book_hit += in_book;
11735
11736             if( ! in_book ) {
11737                 result = index;
11738
11739                 break;
11740             }
11741
11742             index += 2;
11743         }
11744     }
11745
11746     return result;
11747 }
11748
11749 /* [AS] */
11750 void GetOutOfBookInfo( char * buf )
11751 {
11752     int oob[2];
11753     int i;
11754     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11755
11756     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11757     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11758
11759     *buf = '\0';
11760
11761     if( oob[0] >= 0 || oob[1] >= 0 ) {
11762         for( i=0; i<2; i++ ) {
11763             int idx = oob[i];
11764
11765             if( idx >= 0 ) {
11766                 if( i > 0 && oob[0] >= 0 ) {
11767                     strcat( buf, "   " );
11768                 }
11769
11770                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11771                 sprintf( buf+strlen(buf), "%s%.2f",
11772                     pvInfoList[idx].score >= 0 ? "+" : "",
11773                     pvInfoList[idx].score / 100.0 );
11774             }
11775         }
11776     }
11777 }
11778
11779 /* Save game in PGN style and close the file */
11780 int
11781 SaveGamePGN(f)
11782      FILE *f;
11783 {
11784     int i, offset, linelen, newblock;
11785     time_t tm;
11786 //    char *movetext;
11787     char numtext[32];
11788     int movelen, numlen, blank;
11789     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11790
11791     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11792
11793     tm = time((time_t *) NULL);
11794
11795     PrintPGNTags(f, &gameInfo);
11796
11797     if (backwardMostMove > 0 || startedFromSetupPosition) {
11798         char *fen = PositionToFEN(backwardMostMove, NULL);
11799         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11800         fprintf(f, "\n{--------------\n");
11801         PrintPosition(f, backwardMostMove);
11802         fprintf(f, "--------------}\n");
11803         free(fen);
11804     }
11805     else {
11806         /* [AS] Out of book annotation */
11807         if( appData.saveOutOfBookInfo ) {
11808             char buf[64];
11809
11810             GetOutOfBookInfo( buf );
11811
11812             if( buf[0] != '\0' ) {
11813                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11814             }
11815         }
11816
11817         fprintf(f, "\n");
11818     }
11819
11820     i = backwardMostMove;
11821     linelen = 0;
11822     newblock = TRUE;
11823
11824     while (i < forwardMostMove) {
11825         /* Print comments preceding this move */
11826         if (commentList[i] != NULL) {
11827             if (linelen > 0) fprintf(f, "\n");
11828             fprintf(f, "%s", commentList[i]);
11829             linelen = 0;
11830             newblock = TRUE;
11831         }
11832
11833         /* Format move number */
11834         if ((i % 2) == 0)
11835           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11836         else
11837           if (newblock)
11838             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11839           else
11840             numtext[0] = NULLCHAR;
11841
11842         numlen = strlen(numtext);
11843         newblock = FALSE;
11844
11845         /* Print move number */
11846         blank = linelen > 0 && numlen > 0;
11847         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11848             fprintf(f, "\n");
11849             linelen = 0;
11850             blank = 0;
11851         }
11852         if (blank) {
11853             fprintf(f, " ");
11854             linelen++;
11855         }
11856         fprintf(f, "%s", numtext);
11857         linelen += numlen;
11858
11859         /* Get move */
11860         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11861         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11862
11863         /* Print move */
11864         blank = linelen > 0 && movelen > 0;
11865         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11866             fprintf(f, "\n");
11867             linelen = 0;
11868             blank = 0;
11869         }
11870         if (blank) {
11871             fprintf(f, " ");
11872             linelen++;
11873         }
11874         fprintf(f, "%s", move_buffer);
11875         linelen += movelen;
11876
11877         /* [AS] Add PV info if present */
11878         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11879             /* [HGM] add time */
11880             char buf[MSG_SIZ]; int seconds;
11881
11882             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11883
11884             if( seconds <= 0)
11885               buf[0] = 0;
11886             else
11887               if( seconds < 30 )
11888                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11889               else
11890                 {
11891                   seconds = (seconds + 4)/10; // round to full seconds
11892                   if( seconds < 60 )
11893                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11894                   else
11895                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11896                 }
11897
11898             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11899                       pvInfoList[i].score >= 0 ? "+" : "",
11900                       pvInfoList[i].score / 100.0,
11901                       pvInfoList[i].depth,
11902                       buf );
11903
11904             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11905
11906             /* Print score/depth */
11907             blank = linelen > 0 && movelen > 0;
11908             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11909                 fprintf(f, "\n");
11910                 linelen = 0;
11911                 blank = 0;
11912             }
11913             if (blank) {
11914                 fprintf(f, " ");
11915                 linelen++;
11916             }
11917             fprintf(f, "%s", move_buffer);
11918             linelen += movelen;
11919         }
11920
11921         i++;
11922     }
11923
11924     /* Start a new line */
11925     if (linelen > 0) fprintf(f, "\n");
11926
11927     /* Print comments after last move */
11928     if (commentList[i] != NULL) {
11929         fprintf(f, "%s\n", commentList[i]);
11930     }
11931
11932     /* Print result */
11933     if (gameInfo.resultDetails != NULL &&
11934         gameInfo.resultDetails[0] != NULLCHAR) {
11935         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11936                 PGNResult(gameInfo.result));
11937     } else {
11938         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11939     }
11940
11941     fclose(f);
11942     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11943     return TRUE;
11944 }
11945
11946 /* Save game in old style and close the file */
11947 int
11948 SaveGameOldStyle(f)
11949      FILE *f;
11950 {
11951     int i, offset;
11952     time_t tm;
11953
11954     tm = time((time_t *) NULL);
11955
11956     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11957     PrintOpponents(f);
11958
11959     if (backwardMostMove > 0 || startedFromSetupPosition) {
11960         fprintf(f, "\n[--------------\n");
11961         PrintPosition(f, backwardMostMove);
11962         fprintf(f, "--------------]\n");
11963     } else {
11964         fprintf(f, "\n");
11965     }
11966
11967     i = backwardMostMove;
11968     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11969
11970     while (i < forwardMostMove) {
11971         if (commentList[i] != NULL) {
11972             fprintf(f, "[%s]\n", commentList[i]);
11973         }
11974
11975         if ((i % 2) == 1) {
11976             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11977             i++;
11978         } else {
11979             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11980             i++;
11981             if (commentList[i] != NULL) {
11982                 fprintf(f, "\n");
11983                 continue;
11984             }
11985             if (i >= forwardMostMove) {
11986                 fprintf(f, "\n");
11987                 break;
11988             }
11989             fprintf(f, "%s\n", parseList[i]);
11990             i++;
11991         }
11992     }
11993
11994     if (commentList[i] != NULL) {
11995         fprintf(f, "[%s]\n", commentList[i]);
11996     }
11997
11998     /* This isn't really the old style, but it's close enough */
11999     if (gameInfo.resultDetails != NULL &&
12000         gameInfo.resultDetails[0] != NULLCHAR) {
12001         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12002                 gameInfo.resultDetails);
12003     } else {
12004         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12005     }
12006
12007     fclose(f);
12008     return TRUE;
12009 }
12010
12011 /* Save the current game to open file f and close the file */
12012 int
12013 SaveGame(f, dummy, dummy2)
12014      FILE *f;
12015      int dummy;
12016      char *dummy2;
12017 {
12018     if (gameMode == EditPosition) EditPositionDone(TRUE);
12019     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12020     if (appData.oldSaveStyle)
12021       return SaveGameOldStyle(f);
12022     else
12023       return SaveGamePGN(f);
12024 }
12025
12026 /* Save the current position to the given file */
12027 int
12028 SavePositionToFile(filename)
12029      char *filename;
12030 {
12031     FILE *f;
12032     char buf[MSG_SIZ];
12033
12034     if (strcmp(filename, "-") == 0) {
12035         return SavePosition(stdout, 0, NULL);
12036     } else {
12037         f = fopen(filename, "a");
12038         if (f == NULL) {
12039             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12040             DisplayError(buf, errno);
12041             return FALSE;
12042         } else {
12043             safeStrCpy(buf, lastMsg, MSG_SIZ);
12044             DisplayMessage(_("Waiting for access to save file"), "");
12045             flock(fileno(f), LOCK_EX); // [HGM] lock
12046             DisplayMessage(_("Saving position"), "");
12047             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
12048             SavePosition(f, 0, NULL);
12049             DisplayMessage(buf, "");
12050             return TRUE;
12051         }
12052     }
12053 }
12054
12055 /* Save the current position to the given open file and close the file */
12056 int
12057 SavePosition(f, dummy, dummy2)
12058      FILE *f;
12059      int dummy;
12060      char *dummy2;
12061 {
12062     time_t tm;
12063     char *fen;
12064
12065     if (gameMode == EditPosition) EditPositionDone(TRUE);
12066     if (appData.oldSaveStyle) {
12067         tm = time((time_t *) NULL);
12068
12069         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12070         PrintOpponents(f);
12071         fprintf(f, "[--------------\n");
12072         PrintPosition(f, currentMove);
12073         fprintf(f, "--------------]\n");
12074     } else {
12075         fen = PositionToFEN(currentMove, NULL);
12076         fprintf(f, "%s\n", fen);
12077         free(fen);
12078     }
12079     fclose(f);
12080     return TRUE;
12081 }
12082
12083 void
12084 ReloadCmailMsgEvent(unregister)
12085      int unregister;
12086 {
12087 #if !WIN32
12088     static char *inFilename = NULL;
12089     static char *outFilename;
12090     int i;
12091     struct stat inbuf, outbuf;
12092     int status;
12093
12094     /* Any registered moves are unregistered if unregister is set, */
12095     /* i.e. invoked by the signal handler */
12096     if (unregister) {
12097         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12098             cmailMoveRegistered[i] = FALSE;
12099             if (cmailCommentList[i] != NULL) {
12100                 free(cmailCommentList[i]);
12101                 cmailCommentList[i] = NULL;
12102             }
12103         }
12104         nCmailMovesRegistered = 0;
12105     }
12106
12107     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12108         cmailResult[i] = CMAIL_NOT_RESULT;
12109     }
12110     nCmailResults = 0;
12111
12112     if (inFilename == NULL) {
12113         /* Because the filenames are static they only get malloced once  */
12114         /* and they never get freed                                      */
12115         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12116         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12117
12118         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12119         sprintf(outFilename, "%s.out", appData.cmailGameName);
12120     }
12121
12122     status = stat(outFilename, &outbuf);
12123     if (status < 0) {
12124         cmailMailedMove = FALSE;
12125     } else {
12126         status = stat(inFilename, &inbuf);
12127         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12128     }
12129
12130     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12131        counts the games, notes how each one terminated, etc.
12132
12133        It would be nice to remove this kludge and instead gather all
12134        the information while building the game list.  (And to keep it
12135        in the game list nodes instead of having a bunch of fixed-size
12136        parallel arrays.)  Note this will require getting each game's
12137        termination from the PGN tags, as the game list builder does
12138        not process the game moves.  --mann
12139        */
12140     cmailMsgLoaded = TRUE;
12141     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12142
12143     /* Load first game in the file or popup game menu */
12144     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12145
12146 #endif /* !WIN32 */
12147     return;
12148 }
12149
12150 int
12151 RegisterMove()
12152 {
12153     FILE *f;
12154     char string[MSG_SIZ];
12155
12156     if (   cmailMailedMove
12157         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12158         return TRUE;            /* Allow free viewing  */
12159     }
12160
12161     /* Unregister move to ensure that we don't leave RegisterMove        */
12162     /* with the move registered when the conditions for registering no   */
12163     /* longer hold                                                       */
12164     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12165         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12166         nCmailMovesRegistered --;
12167
12168         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12169           {
12170               free(cmailCommentList[lastLoadGameNumber - 1]);
12171               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12172           }
12173     }
12174
12175     if (cmailOldMove == -1) {
12176         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12177         return FALSE;
12178     }
12179
12180     if (currentMove > cmailOldMove + 1) {
12181         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12182         return FALSE;
12183     }
12184
12185     if (currentMove < cmailOldMove) {
12186         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12187         return FALSE;
12188     }
12189
12190     if (forwardMostMove > currentMove) {
12191         /* Silently truncate extra moves */
12192         TruncateGame();
12193     }
12194
12195     if (   (currentMove == cmailOldMove + 1)
12196         || (   (currentMove == cmailOldMove)
12197             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12198                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12199         if (gameInfo.result != GameUnfinished) {
12200             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12201         }
12202
12203         if (commentList[currentMove] != NULL) {
12204             cmailCommentList[lastLoadGameNumber - 1]
12205               = StrSave(commentList[currentMove]);
12206         }
12207         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12208
12209         if (appData.debugMode)
12210           fprintf(debugFP, "Saving %s for game %d\n",
12211                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12212
12213         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12214
12215         f = fopen(string, "w");
12216         if (appData.oldSaveStyle) {
12217             SaveGameOldStyle(f); /* also closes the file */
12218
12219             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12220             f = fopen(string, "w");
12221             SavePosition(f, 0, NULL); /* also closes the file */
12222         } else {
12223             fprintf(f, "{--------------\n");
12224             PrintPosition(f, currentMove);
12225             fprintf(f, "--------------}\n\n");
12226
12227             SaveGame(f, 0, NULL); /* also closes the file*/
12228         }
12229
12230         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12231         nCmailMovesRegistered ++;
12232     } else if (nCmailGames == 1) {
12233         DisplayError(_("You have not made a move yet"), 0);
12234         return FALSE;
12235     }
12236
12237     return TRUE;
12238 }
12239
12240 void
12241 MailMoveEvent()
12242 {
12243 #if !WIN32
12244     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12245     FILE *commandOutput;
12246     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12247     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12248     int nBuffers;
12249     int i;
12250     int archived;
12251     char *arcDir;
12252
12253     if (! cmailMsgLoaded) {
12254         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12255         return;
12256     }
12257
12258     if (nCmailGames == nCmailResults) {
12259         DisplayError(_("No unfinished games"), 0);
12260         return;
12261     }
12262
12263 #if CMAIL_PROHIBIT_REMAIL
12264     if (cmailMailedMove) {
12265       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);
12266         DisplayError(msg, 0);
12267         return;
12268     }
12269 #endif
12270
12271     if (! (cmailMailedMove || RegisterMove())) return;
12272
12273     if (   cmailMailedMove
12274         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12275       snprintf(string, MSG_SIZ, partCommandString,
12276                appData.debugMode ? " -v" : "", appData.cmailGameName);
12277         commandOutput = popen(string, "r");
12278
12279         if (commandOutput == NULL) {
12280             DisplayError(_("Failed to invoke cmail"), 0);
12281         } else {
12282             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12283                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12284             }
12285             if (nBuffers > 1) {
12286                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12287                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12288                 nBytes = MSG_SIZ - 1;
12289             } else {
12290                 (void) memcpy(msg, buffer, nBytes);
12291             }
12292             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12293
12294             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12295                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12296
12297                 archived = TRUE;
12298                 for (i = 0; i < nCmailGames; i ++) {
12299                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12300                         archived = FALSE;
12301                     }
12302                 }
12303                 if (   archived
12304                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12305                         != NULL)) {
12306                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12307                            arcDir,
12308                            appData.cmailGameName,
12309                            gameInfo.date);
12310                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12311                     cmailMsgLoaded = FALSE;
12312                 }
12313             }
12314
12315             DisplayInformation(msg);
12316             pclose(commandOutput);
12317         }
12318     } else {
12319         if ((*cmailMsg) != '\0') {
12320             DisplayInformation(cmailMsg);
12321         }
12322     }
12323
12324     return;
12325 #endif /* !WIN32 */
12326 }
12327
12328 char *
12329 CmailMsg()
12330 {
12331 #if WIN32
12332     return NULL;
12333 #else
12334     int  prependComma = 0;
12335     char number[5];
12336     char string[MSG_SIZ];       /* Space for game-list */
12337     int  i;
12338
12339     if (!cmailMsgLoaded) return "";
12340
12341     if (cmailMailedMove) {
12342       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12343     } else {
12344         /* Create a list of games left */
12345       snprintf(string, MSG_SIZ, "[");
12346         for (i = 0; i < nCmailGames; i ++) {
12347             if (! (   cmailMoveRegistered[i]
12348                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12349                 if (prependComma) {
12350                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12351                 } else {
12352                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12353                     prependComma = 1;
12354                 }
12355
12356                 strcat(string, number);
12357             }
12358         }
12359         strcat(string, "]");
12360
12361         if (nCmailMovesRegistered + nCmailResults == 0) {
12362             switch (nCmailGames) {
12363               case 1:
12364                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12365                 break;
12366
12367               case 2:
12368                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12369                 break;
12370
12371               default:
12372                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12373                          nCmailGames);
12374                 break;
12375             }
12376         } else {
12377             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12378               case 1:
12379                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12380                          string);
12381                 break;
12382
12383               case 0:
12384                 if (nCmailResults == nCmailGames) {
12385                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12386                 } else {
12387                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12388                 }
12389                 break;
12390
12391               default:
12392                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12393                          string);
12394             }
12395         }
12396     }
12397     return cmailMsg;
12398 #endif /* WIN32 */
12399 }
12400
12401 void
12402 ResetGameEvent()
12403 {
12404     if (gameMode == Training)
12405       SetTrainingModeOff();
12406
12407     Reset(TRUE, TRUE);
12408     cmailMsgLoaded = FALSE;
12409     if (appData.icsActive) {
12410       SendToICS(ics_prefix);
12411       SendToICS("refresh\n");
12412     }
12413 }
12414
12415 void
12416 ExitEvent(status)
12417      int status;
12418 {
12419     exiting++;
12420     if (exiting > 2) {
12421       /* Give up on clean exit */
12422       exit(status);
12423     }
12424     if (exiting > 1) {
12425       /* Keep trying for clean exit */
12426       return;
12427     }
12428
12429     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12430
12431     if (telnetISR != NULL) {
12432       RemoveInputSource(telnetISR);
12433     }
12434     if (icsPR != NoProc) {
12435       DestroyChildProcess(icsPR, TRUE);
12436     }
12437
12438     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12439     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12440
12441     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12442     /* make sure this other one finishes before killing it!                  */
12443     if(endingGame) { int count = 0;
12444         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12445         while(endingGame && count++ < 10) DoSleep(1);
12446         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12447     }
12448
12449     /* Kill off chess programs */
12450     if (first.pr != NoProc) {
12451         ExitAnalyzeMode();
12452
12453         DoSleep( appData.delayBeforeQuit );
12454         SendToProgram("quit\n", &first);
12455         DoSleep( appData.delayAfterQuit );
12456         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12457     }
12458     if (second.pr != NoProc) {
12459         DoSleep( appData.delayBeforeQuit );
12460         SendToProgram("quit\n", &second);
12461         DoSleep( appData.delayAfterQuit );
12462         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12463     }
12464     if (first.isr != NULL) {
12465         RemoveInputSource(first.isr);
12466     }
12467     if (second.isr != NULL) {
12468         RemoveInputSource(second.isr);
12469     }
12470
12471     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12472     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12473
12474     ShutDownFrontEnd();
12475     exit(status);
12476 }
12477
12478 void
12479 PauseEvent()
12480 {
12481     if (appData.debugMode)
12482         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12483     if (pausing) {
12484         pausing = FALSE;
12485         ModeHighlight();
12486         if (gameMode == MachinePlaysWhite ||
12487             gameMode == MachinePlaysBlack) {
12488             StartClocks();
12489         } else {
12490             DisplayBothClocks();
12491         }
12492         if (gameMode == PlayFromGameFile) {
12493             if (appData.timeDelay >= 0)
12494                 AutoPlayGameLoop();
12495         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12496             Reset(FALSE, TRUE);
12497             SendToICS(ics_prefix);
12498             SendToICS("refresh\n");
12499         } else if (currentMove < forwardMostMove) {
12500             ForwardInner(forwardMostMove);
12501         }
12502         pauseExamInvalid = FALSE;
12503     } else {
12504         switch (gameMode) {
12505           default:
12506             return;
12507           case IcsExamining:
12508             pauseExamForwardMostMove = forwardMostMove;
12509             pauseExamInvalid = FALSE;
12510             /* fall through */
12511           case IcsObserving:
12512           case IcsPlayingWhite:
12513           case IcsPlayingBlack:
12514             pausing = TRUE;
12515             ModeHighlight();
12516             return;
12517           case PlayFromGameFile:
12518             (void) StopLoadGameTimer();
12519             pausing = TRUE;
12520             ModeHighlight();
12521             break;
12522           case BeginningOfGame:
12523             if (appData.icsActive) return;
12524             /* else fall through */
12525           case MachinePlaysWhite:
12526           case MachinePlaysBlack:
12527           case TwoMachinesPlay:
12528             if (forwardMostMove == 0)
12529               return;           /* don't pause if no one has moved */
12530             if ((gameMode == MachinePlaysWhite &&
12531                  !WhiteOnMove(forwardMostMove)) ||
12532                 (gameMode == MachinePlaysBlack &&
12533                  WhiteOnMove(forwardMostMove))) {
12534                 StopClocks();
12535             }
12536             pausing = TRUE;
12537             ModeHighlight();
12538             break;
12539         }
12540     }
12541 }
12542
12543 void
12544 EditCommentEvent()
12545 {
12546     char title[MSG_SIZ];
12547
12548     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12549       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12550     } else {
12551       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12552                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12553                parseList[currentMove - 1]);
12554     }
12555
12556     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12557 }
12558
12559
12560 void
12561 EditTagsEvent()
12562 {
12563     char *tags = PGNTags(&gameInfo);
12564     bookUp = FALSE;
12565     EditTagsPopUp(tags, NULL);
12566     free(tags);
12567 }
12568
12569 void
12570 AnalyzeModeEvent()
12571 {
12572     if (appData.noChessProgram || gameMode == AnalyzeMode)
12573       return;
12574
12575     if (gameMode != AnalyzeFile) {
12576         if (!appData.icsEngineAnalyze) {
12577                EditGameEvent();
12578                if (gameMode != EditGame) return;
12579         }
12580         ResurrectChessProgram();
12581         SendToProgram("analyze\n", &first);
12582         first.analyzing = TRUE;
12583         /*first.maybeThinking = TRUE;*/
12584         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12585         EngineOutputPopUp();
12586     }
12587     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12588     pausing = FALSE;
12589     ModeHighlight();
12590     SetGameInfo();
12591
12592     StartAnalysisClock();
12593     GetTimeMark(&lastNodeCountTime);
12594     lastNodeCount = 0;
12595 }
12596
12597 void
12598 AnalyzeFileEvent()
12599 {
12600     if (appData.noChessProgram || gameMode == AnalyzeFile)
12601       return;
12602
12603     if (gameMode != AnalyzeMode) {
12604         EditGameEvent();
12605         if (gameMode != EditGame) return;
12606         ResurrectChessProgram();
12607         SendToProgram("analyze\n", &first);
12608         first.analyzing = TRUE;
12609         /*first.maybeThinking = TRUE;*/
12610         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12611         EngineOutputPopUp();
12612     }
12613     gameMode = AnalyzeFile;
12614     pausing = FALSE;
12615     ModeHighlight();
12616     SetGameInfo();
12617
12618     StartAnalysisClock();
12619     GetTimeMark(&lastNodeCountTime);
12620     lastNodeCount = 0;
12621 }
12622
12623 void
12624 MachineWhiteEvent()
12625 {
12626     char buf[MSG_SIZ];
12627     char *bookHit = NULL;
12628
12629     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12630       return;
12631
12632
12633     if (gameMode == PlayFromGameFile ||
12634         gameMode == TwoMachinesPlay  ||
12635         gameMode == Training         ||
12636         gameMode == AnalyzeMode      ||
12637         gameMode == EndOfGame)
12638         EditGameEvent();
12639
12640     if (gameMode == EditPosition)
12641         EditPositionDone(TRUE);
12642
12643     if (!WhiteOnMove(currentMove)) {
12644         DisplayError(_("It is not White's turn"), 0);
12645         return;
12646     }
12647
12648     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12649       ExitAnalyzeMode();
12650
12651     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12652         gameMode == AnalyzeFile)
12653         TruncateGame();
12654
12655     ResurrectChessProgram();    /* in case it isn't running */
12656     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12657         gameMode = MachinePlaysWhite;
12658         ResetClocks();
12659     } else
12660     gameMode = MachinePlaysWhite;
12661     pausing = FALSE;
12662     ModeHighlight();
12663     SetGameInfo();
12664     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12665     DisplayTitle(buf);
12666     if (first.sendName) {
12667       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12668       SendToProgram(buf, &first);
12669     }
12670     if (first.sendTime) {
12671       if (first.useColors) {
12672         SendToProgram("black\n", &first); /*gnu kludge*/
12673       }
12674       SendTimeRemaining(&first, TRUE);
12675     }
12676     if (first.useColors) {
12677       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12678     }
12679     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12680     SetMachineThinkingEnables();
12681     first.maybeThinking = TRUE;
12682     StartClocks();
12683     firstMove = FALSE;
12684
12685     if (appData.autoFlipView && !flipView) {
12686       flipView = !flipView;
12687       DrawPosition(FALSE, NULL);
12688       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12689     }
12690
12691     if(bookHit) { // [HGM] book: simulate book reply
12692         static char bookMove[MSG_SIZ]; // a bit generous?
12693
12694         programStats.nodes = programStats.depth = programStats.time =
12695         programStats.score = programStats.got_only_move = 0;
12696         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12697
12698         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12699         strcat(bookMove, bookHit);
12700         HandleMachineMove(bookMove, &first);
12701     }
12702 }
12703
12704 void
12705 MachineBlackEvent()
12706 {
12707   char buf[MSG_SIZ];
12708   char *bookHit = NULL;
12709
12710     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12711         return;
12712
12713
12714     if (gameMode == PlayFromGameFile ||
12715         gameMode == TwoMachinesPlay  ||
12716         gameMode == Training         ||
12717         gameMode == AnalyzeMode      ||
12718         gameMode == EndOfGame)
12719         EditGameEvent();
12720
12721     if (gameMode == EditPosition)
12722         EditPositionDone(TRUE);
12723
12724     if (WhiteOnMove(currentMove)) {
12725         DisplayError(_("It is not Black's turn"), 0);
12726         return;
12727     }
12728
12729     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12730       ExitAnalyzeMode();
12731
12732     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12733         gameMode == AnalyzeFile)
12734         TruncateGame();
12735
12736     ResurrectChessProgram();    /* in case it isn't running */
12737     gameMode = MachinePlaysBlack;
12738     pausing = FALSE;
12739     ModeHighlight();
12740     SetGameInfo();
12741     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12742     DisplayTitle(buf);
12743     if (first.sendName) {
12744       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12745       SendToProgram(buf, &first);
12746     }
12747     if (first.sendTime) {
12748       if (first.useColors) {
12749         SendToProgram("white\n", &first); /*gnu kludge*/
12750       }
12751       SendTimeRemaining(&first, FALSE);
12752     }
12753     if (first.useColors) {
12754       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12755     }
12756     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12757     SetMachineThinkingEnables();
12758     first.maybeThinking = TRUE;
12759     StartClocks();
12760
12761     if (appData.autoFlipView && flipView) {
12762       flipView = !flipView;
12763       DrawPosition(FALSE, NULL);
12764       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12765     }
12766     if(bookHit) { // [HGM] book: simulate book reply
12767         static char bookMove[MSG_SIZ]; // a bit generous?
12768
12769         programStats.nodes = programStats.depth = programStats.time =
12770         programStats.score = programStats.got_only_move = 0;
12771         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12772
12773         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12774         strcat(bookMove, bookHit);
12775         HandleMachineMove(bookMove, &first);
12776     }
12777 }
12778
12779
12780 void
12781 DisplayTwoMachinesTitle()
12782 {
12783     char buf[MSG_SIZ];
12784     if (appData.matchGames > 0) {
12785         if(appData.tourneyFile[0]) {
12786           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12787                    gameInfo.white, gameInfo.black,
12788                    nextGame+1, appData.matchGames+1,
12789                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12790         } else 
12791         if (first.twoMachinesColor[0] == 'w') {
12792           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12793                    gameInfo.white, gameInfo.black,
12794                    first.matchWins, second.matchWins,
12795                    matchGame - 1 - (first.matchWins + second.matchWins));
12796         } else {
12797           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12798                    gameInfo.white, gameInfo.black,
12799                    second.matchWins, first.matchWins,
12800                    matchGame - 1 - (first.matchWins + second.matchWins));
12801         }
12802     } else {
12803       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12804     }
12805     DisplayTitle(buf);
12806 }
12807
12808 void
12809 SettingsMenuIfReady()
12810 {
12811   if (second.lastPing != second.lastPong) {
12812     DisplayMessage("", _("Waiting for second chess program"));
12813     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12814     return;
12815   }
12816   ThawUI();
12817   DisplayMessage("", "");
12818   SettingsPopUp(&second);
12819 }
12820
12821 int
12822 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12823 {
12824     char buf[MSG_SIZ];
12825     if (cps->pr == NULL) {
12826         StartChessProgram(cps);
12827         if (cps->protocolVersion == 1) {
12828           retry();
12829         } else {
12830           /* kludge: allow timeout for initial "feature" command */
12831           FreezeUI();
12832           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12833           DisplayMessage("", buf);
12834           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12835         }
12836         return 1;
12837     }
12838     return 0;
12839 }
12840
12841 void
12842 TwoMachinesEvent P((void))
12843 {
12844     int i;
12845     char buf[MSG_SIZ];
12846     ChessProgramState *onmove;
12847     char *bookHit = NULL;
12848     static int stalling = 0;
12849     TimeMark now;
12850     long wait;
12851
12852     if (appData.noChessProgram) return;
12853
12854     switch (gameMode) {
12855       case TwoMachinesPlay:
12856         return;
12857       case MachinePlaysWhite:
12858       case MachinePlaysBlack:
12859         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12860             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12861             return;
12862         }
12863         /* fall through */
12864       case BeginningOfGame:
12865       case PlayFromGameFile:
12866       case EndOfGame:
12867         EditGameEvent();
12868         if (gameMode != EditGame) return;
12869         break;
12870       case EditPosition:
12871         EditPositionDone(TRUE);
12872         break;
12873       case AnalyzeMode:
12874       case AnalyzeFile:
12875         ExitAnalyzeMode();
12876         break;
12877       case EditGame:
12878       default:
12879         break;
12880     }
12881
12882 //    forwardMostMove = currentMove;
12883     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12884
12885     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12886
12887     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12888     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12889       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12890       return;
12891     }
12892     if(!stalling) {
12893       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12894       SendToProgram("force\n", &second);
12895       stalling = 1;
12896       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12897       return;
12898     }
12899     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12900     if(appData.matchPause>10000 || appData.matchPause<10)
12901                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12902     wait = SubtractTimeMarks(&now, &pauseStart);
12903     if(wait < appData.matchPause) {
12904         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12905         return;
12906     }
12907     stalling = 0;
12908     DisplayMessage("", "");
12909     if (startedFromSetupPosition) {
12910         SendBoard(&second, backwardMostMove);
12911     if (appData.debugMode) {
12912         fprintf(debugFP, "Two Machines\n");
12913     }
12914     }
12915     for (i = backwardMostMove; i < forwardMostMove; i++) {
12916         SendMoveToProgram(i, &second);
12917     }
12918
12919     gameMode = TwoMachinesPlay;
12920     pausing = FALSE;
12921     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12922     SetGameInfo();
12923     DisplayTwoMachinesTitle();
12924     firstMove = TRUE;
12925     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12926         onmove = &first;
12927     } else {
12928         onmove = &second;
12929     }
12930     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12931     SendToProgram(first.computerString, &first);
12932     if (first.sendName) {
12933       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12934       SendToProgram(buf, &first);
12935     }
12936     SendToProgram(second.computerString, &second);
12937     if (second.sendName) {
12938       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12939       SendToProgram(buf, &second);
12940     }
12941
12942     ResetClocks();
12943     if (!first.sendTime || !second.sendTime) {
12944         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12945         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12946     }
12947     if (onmove->sendTime) {
12948       if (onmove->useColors) {
12949         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12950       }
12951       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12952     }
12953     if (onmove->useColors) {
12954       SendToProgram(onmove->twoMachinesColor, onmove);
12955     }
12956     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12957 //    SendToProgram("go\n", onmove);
12958     onmove->maybeThinking = TRUE;
12959     SetMachineThinkingEnables();
12960
12961     StartClocks();
12962
12963     if(bookHit) { // [HGM] book: simulate book reply
12964         static char bookMove[MSG_SIZ]; // a bit generous?
12965
12966         programStats.nodes = programStats.depth = programStats.time =
12967         programStats.score = programStats.got_only_move = 0;
12968         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12969
12970         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12971         strcat(bookMove, bookHit);
12972         savedMessage = bookMove; // args for deferred call
12973         savedState = onmove;
12974         ScheduleDelayedEvent(DeferredBookMove, 1);
12975     }
12976 }
12977
12978 void
12979 TrainingEvent()
12980 {
12981     if (gameMode == Training) {
12982       SetTrainingModeOff();
12983       gameMode = PlayFromGameFile;
12984       DisplayMessage("", _("Training mode off"));
12985     } else {
12986       gameMode = Training;
12987       animateTraining = appData.animate;
12988
12989       /* make sure we are not already at the end of the game */
12990       if (currentMove < forwardMostMove) {
12991         SetTrainingModeOn();
12992         DisplayMessage("", _("Training mode on"));
12993       } else {
12994         gameMode = PlayFromGameFile;
12995         DisplayError(_("Already at end of game"), 0);
12996       }
12997     }
12998     ModeHighlight();
12999 }
13000
13001 void
13002 IcsClientEvent()
13003 {
13004     if (!appData.icsActive) return;
13005     switch (gameMode) {
13006       case IcsPlayingWhite:
13007       case IcsPlayingBlack:
13008       case IcsObserving:
13009       case IcsIdle:
13010       case BeginningOfGame:
13011       case IcsExamining:
13012         return;
13013
13014       case EditGame:
13015         break;
13016
13017       case EditPosition:
13018         EditPositionDone(TRUE);
13019         break;
13020
13021       case AnalyzeMode:
13022       case AnalyzeFile:
13023         ExitAnalyzeMode();
13024         break;
13025
13026       default:
13027         EditGameEvent();
13028         break;
13029     }
13030
13031     gameMode = IcsIdle;
13032     ModeHighlight();
13033     return;
13034 }
13035
13036
13037 void
13038 EditGameEvent()
13039 {
13040     int i;
13041
13042     switch (gameMode) {
13043       case Training:
13044         SetTrainingModeOff();
13045         break;
13046       case MachinePlaysWhite:
13047       case MachinePlaysBlack:
13048       case BeginningOfGame:
13049         SendToProgram("force\n", &first);
13050         SetUserThinkingEnables();
13051         break;
13052       case PlayFromGameFile:
13053         (void) StopLoadGameTimer();
13054         if (gameFileFP != NULL) {
13055             gameFileFP = NULL;
13056         }
13057         break;
13058       case EditPosition:
13059         EditPositionDone(TRUE);
13060         break;
13061       case AnalyzeMode:
13062       case AnalyzeFile:
13063         ExitAnalyzeMode();
13064         SendToProgram("force\n", &first);
13065         break;
13066       case TwoMachinesPlay:
13067         GameEnds(EndOfFile, NULL, GE_PLAYER);
13068         ResurrectChessProgram();
13069         SetUserThinkingEnables();
13070         break;
13071       case EndOfGame:
13072         ResurrectChessProgram();
13073         break;
13074       case IcsPlayingBlack:
13075       case IcsPlayingWhite:
13076         DisplayError(_("Warning: You are still playing a game"), 0);
13077         break;
13078       case IcsObserving:
13079         DisplayError(_("Warning: You are still observing a game"), 0);
13080         break;
13081       case IcsExamining:
13082         DisplayError(_("Warning: You are still examining a game"), 0);
13083         break;
13084       case IcsIdle:
13085         break;
13086       case EditGame:
13087       default:
13088         return;
13089     }
13090
13091     pausing = FALSE;
13092     StopClocks();
13093     first.offeredDraw = second.offeredDraw = 0;
13094
13095     if (gameMode == PlayFromGameFile) {
13096         whiteTimeRemaining = timeRemaining[0][currentMove];
13097         blackTimeRemaining = timeRemaining[1][currentMove];
13098         DisplayTitle("");
13099     }
13100
13101     if (gameMode == MachinePlaysWhite ||
13102         gameMode == MachinePlaysBlack ||
13103         gameMode == TwoMachinesPlay ||
13104         gameMode == EndOfGame) {
13105         i = forwardMostMove;
13106         while (i > currentMove) {
13107             SendToProgram("undo\n", &first);
13108             i--;
13109         }
13110         whiteTimeRemaining = timeRemaining[0][currentMove];
13111         blackTimeRemaining = timeRemaining[1][currentMove];
13112         DisplayBothClocks();
13113         if (whiteFlag || blackFlag) {
13114             whiteFlag = blackFlag = 0;
13115         }
13116         DisplayTitle("");
13117     }
13118
13119     gameMode = EditGame;
13120     ModeHighlight();
13121     SetGameInfo();
13122 }
13123
13124
13125 void
13126 EditPositionEvent()
13127 {
13128     if (gameMode == EditPosition) {
13129         EditGameEvent();
13130         return;
13131     }
13132
13133     EditGameEvent();
13134     if (gameMode != EditGame) return;
13135
13136     gameMode = EditPosition;
13137     ModeHighlight();
13138     SetGameInfo();
13139     if (currentMove > 0)
13140       CopyBoard(boards[0], boards[currentMove]);
13141
13142     blackPlaysFirst = !WhiteOnMove(currentMove);
13143     ResetClocks();
13144     currentMove = forwardMostMove = backwardMostMove = 0;
13145     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13146     DisplayMove(-1);
13147 }
13148
13149 void
13150 ExitAnalyzeMode()
13151 {
13152     /* [DM] icsEngineAnalyze - possible call from other functions */
13153     if (appData.icsEngineAnalyze) {
13154         appData.icsEngineAnalyze = FALSE;
13155
13156         DisplayMessage("",_("Close ICS engine analyze..."));
13157     }
13158     if (first.analysisSupport && first.analyzing) {
13159       SendToProgram("exit\n", &first);
13160       first.analyzing = FALSE;
13161     }
13162     thinkOutput[0] = NULLCHAR;
13163 }
13164
13165 void
13166 EditPositionDone(Boolean fakeRights)
13167 {
13168     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13169
13170     startedFromSetupPosition = TRUE;
13171     InitChessProgram(&first, FALSE);
13172     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13173       boards[0][EP_STATUS] = EP_NONE;
13174       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13175     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13176         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13177         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13178       } else boards[0][CASTLING][2] = NoRights;
13179     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13180         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13181         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13182       } else boards[0][CASTLING][5] = NoRights;
13183     }
13184     SendToProgram("force\n", &first);
13185     if (blackPlaysFirst) {
13186         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13187         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13188         currentMove = forwardMostMove = backwardMostMove = 1;
13189         CopyBoard(boards[1], boards[0]);
13190     } else {
13191         currentMove = forwardMostMove = backwardMostMove = 0;
13192     }
13193     SendBoard(&first, forwardMostMove);
13194     if (appData.debugMode) {
13195         fprintf(debugFP, "EditPosDone\n");
13196     }
13197     DisplayTitle("");
13198     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13199     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13200     gameMode = EditGame;
13201     ModeHighlight();
13202     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13203     ClearHighlights(); /* [AS] */
13204 }
13205
13206 /* Pause for `ms' milliseconds */
13207 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13208 void
13209 TimeDelay(ms)
13210      long ms;
13211 {
13212     TimeMark m1, m2;
13213
13214     GetTimeMark(&m1);
13215     do {
13216         GetTimeMark(&m2);
13217     } while (SubtractTimeMarks(&m2, &m1) < ms);
13218 }
13219
13220 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13221 void
13222 SendMultiLineToICS(buf)
13223      char *buf;
13224 {
13225     char temp[MSG_SIZ+1], *p;
13226     int len;
13227
13228     len = strlen(buf);
13229     if (len > MSG_SIZ)
13230       len = MSG_SIZ;
13231
13232     strncpy(temp, buf, len);
13233     temp[len] = 0;
13234
13235     p = temp;
13236     while (*p) {
13237         if (*p == '\n' || *p == '\r')
13238           *p = ' ';
13239         ++p;
13240     }
13241
13242     strcat(temp, "\n");
13243     SendToICS(temp);
13244     SendToPlayer(temp, strlen(temp));
13245 }
13246
13247 void
13248 SetWhiteToPlayEvent()
13249 {
13250     if (gameMode == EditPosition) {
13251         blackPlaysFirst = FALSE;
13252         DisplayBothClocks();    /* works because currentMove is 0 */
13253     } else if (gameMode == IcsExamining) {
13254         SendToICS(ics_prefix);
13255         SendToICS("tomove white\n");
13256     }
13257 }
13258
13259 void
13260 SetBlackToPlayEvent()
13261 {
13262     if (gameMode == EditPosition) {
13263         blackPlaysFirst = TRUE;
13264         currentMove = 1;        /* kludge */
13265         DisplayBothClocks();
13266         currentMove = 0;
13267     } else if (gameMode == IcsExamining) {
13268         SendToICS(ics_prefix);
13269         SendToICS("tomove black\n");
13270     }
13271 }
13272
13273 void
13274 EditPositionMenuEvent(selection, x, y)
13275      ChessSquare selection;
13276      int x, y;
13277 {
13278     char buf[MSG_SIZ];
13279     ChessSquare piece = boards[0][y][x];
13280
13281     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13282
13283     switch (selection) {
13284       case ClearBoard:
13285         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13286             SendToICS(ics_prefix);
13287             SendToICS("bsetup clear\n");
13288         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13289             SendToICS(ics_prefix);
13290             SendToICS("clearboard\n");
13291         } else {
13292             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13293                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13294                 for (y = 0; y < BOARD_HEIGHT; y++) {
13295                     if (gameMode == IcsExamining) {
13296                         if (boards[currentMove][y][x] != EmptySquare) {
13297                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13298                                     AAA + x, ONE + y);
13299                             SendToICS(buf);
13300                         }
13301                     } else {
13302                         boards[0][y][x] = p;
13303                     }
13304                 }
13305             }
13306         }
13307         if (gameMode == EditPosition) {
13308             DrawPosition(FALSE, boards[0]);
13309         }
13310         break;
13311
13312       case WhitePlay:
13313         SetWhiteToPlayEvent();
13314         break;
13315
13316       case BlackPlay:
13317         SetBlackToPlayEvent();
13318         break;
13319
13320       case EmptySquare:
13321         if (gameMode == IcsExamining) {
13322             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13323             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13324             SendToICS(buf);
13325         } else {
13326             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13327                 if(x == BOARD_LEFT-2) {
13328                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13329                     boards[0][y][1] = 0;
13330                 } else
13331                 if(x == BOARD_RGHT+1) {
13332                     if(y >= gameInfo.holdingsSize) break;
13333                     boards[0][y][BOARD_WIDTH-2] = 0;
13334                 } else break;
13335             }
13336             boards[0][y][x] = EmptySquare;
13337             DrawPosition(FALSE, boards[0]);
13338         }
13339         break;
13340
13341       case PromotePiece:
13342         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13343            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13344             selection = (ChessSquare) (PROMOTED piece);
13345         } else if(piece == EmptySquare) selection = WhiteSilver;
13346         else selection = (ChessSquare)((int)piece - 1);
13347         goto defaultlabel;
13348
13349       case DemotePiece:
13350         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13351            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13352             selection = (ChessSquare) (DEMOTED piece);
13353         } else if(piece == EmptySquare) selection = BlackSilver;
13354         else selection = (ChessSquare)((int)piece + 1);
13355         goto defaultlabel;
13356
13357       case WhiteQueen:
13358       case BlackQueen:
13359         if(gameInfo.variant == VariantShatranj ||
13360            gameInfo.variant == VariantXiangqi  ||
13361            gameInfo.variant == VariantCourier  ||
13362            gameInfo.variant == VariantMakruk     )
13363             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13364         goto defaultlabel;
13365
13366       case WhiteKing:
13367       case BlackKing:
13368         if(gameInfo.variant == VariantXiangqi)
13369             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13370         if(gameInfo.variant == VariantKnightmate)
13371             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13372       default:
13373         defaultlabel:
13374         if (gameMode == IcsExamining) {
13375             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13376             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13377                      PieceToChar(selection), AAA + x, ONE + y);
13378             SendToICS(buf);
13379         } else {
13380             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13381                 int n;
13382                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13383                     n = PieceToNumber(selection - BlackPawn);
13384                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13385                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13386                     boards[0][BOARD_HEIGHT-1-n][1]++;
13387                 } else
13388                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13389                     n = PieceToNumber(selection);
13390                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13391                     boards[0][n][BOARD_WIDTH-1] = selection;
13392                     boards[0][n][BOARD_WIDTH-2]++;
13393                 }
13394             } else
13395             boards[0][y][x] = selection;
13396             DrawPosition(TRUE, boards[0]);
13397         }
13398         break;
13399     }
13400 }
13401
13402
13403 void
13404 DropMenuEvent(selection, x, y)
13405      ChessSquare selection;
13406      int x, y;
13407 {
13408     ChessMove moveType;
13409
13410     switch (gameMode) {
13411       case IcsPlayingWhite:
13412       case MachinePlaysBlack:
13413         if (!WhiteOnMove(currentMove)) {
13414             DisplayMoveError(_("It is Black's turn"));
13415             return;
13416         }
13417         moveType = WhiteDrop;
13418         break;
13419       case IcsPlayingBlack:
13420       case MachinePlaysWhite:
13421         if (WhiteOnMove(currentMove)) {
13422             DisplayMoveError(_("It is White's turn"));
13423             return;
13424         }
13425         moveType = BlackDrop;
13426         break;
13427       case EditGame:
13428         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13429         break;
13430       default:
13431         return;
13432     }
13433
13434     if (moveType == BlackDrop && selection < BlackPawn) {
13435       selection = (ChessSquare) ((int) selection
13436                                  + (int) BlackPawn - (int) WhitePawn);
13437     }
13438     if (boards[currentMove][y][x] != EmptySquare) {
13439         DisplayMoveError(_("That square is occupied"));
13440         return;
13441     }
13442
13443     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13444 }
13445
13446 void
13447 AcceptEvent()
13448 {
13449     /* Accept a pending offer of any kind from opponent */
13450
13451     if (appData.icsActive) {
13452         SendToICS(ics_prefix);
13453         SendToICS("accept\n");
13454     } else if (cmailMsgLoaded) {
13455         if (currentMove == cmailOldMove &&
13456             commentList[cmailOldMove] != NULL &&
13457             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13458                    "Black offers a draw" : "White offers a draw")) {
13459             TruncateGame();
13460             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13461             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13462         } else {
13463             DisplayError(_("There is no pending offer on this move"), 0);
13464             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13465         }
13466     } else {
13467         /* Not used for offers from chess program */
13468     }
13469 }
13470
13471 void
13472 DeclineEvent()
13473 {
13474     /* Decline a pending offer of any kind from opponent */
13475
13476     if (appData.icsActive) {
13477         SendToICS(ics_prefix);
13478         SendToICS("decline\n");
13479     } else if (cmailMsgLoaded) {
13480         if (currentMove == cmailOldMove &&
13481             commentList[cmailOldMove] != NULL &&
13482             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13483                    "Black offers a draw" : "White offers a draw")) {
13484 #ifdef NOTDEF
13485             AppendComment(cmailOldMove, "Draw declined", TRUE);
13486             DisplayComment(cmailOldMove - 1, "Draw declined");
13487 #endif /*NOTDEF*/
13488         } else {
13489             DisplayError(_("There is no pending offer on this move"), 0);
13490         }
13491     } else {
13492         /* Not used for offers from chess program */
13493     }
13494 }
13495
13496 void
13497 RematchEvent()
13498 {
13499     /* Issue ICS rematch command */
13500     if (appData.icsActive) {
13501         SendToICS(ics_prefix);
13502         SendToICS("rematch\n");
13503     }
13504 }
13505
13506 void
13507 CallFlagEvent()
13508 {
13509     /* Call your opponent's flag (claim a win on time) */
13510     if (appData.icsActive) {
13511         SendToICS(ics_prefix);
13512         SendToICS("flag\n");
13513     } else {
13514         switch (gameMode) {
13515           default:
13516             return;
13517           case MachinePlaysWhite:
13518             if (whiteFlag) {
13519                 if (blackFlag)
13520                   GameEnds(GameIsDrawn, "Both players ran out of time",
13521                            GE_PLAYER);
13522                 else
13523                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13524             } else {
13525                 DisplayError(_("Your opponent is not out of time"), 0);
13526             }
13527             break;
13528           case MachinePlaysBlack:
13529             if (blackFlag) {
13530                 if (whiteFlag)
13531                   GameEnds(GameIsDrawn, "Both players ran out of time",
13532                            GE_PLAYER);
13533                 else
13534                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13535             } else {
13536                 DisplayError(_("Your opponent is not out of time"), 0);
13537             }
13538             break;
13539         }
13540     }
13541 }
13542
13543 void
13544 ClockClick(int which)
13545 {       // [HGM] code moved to back-end from winboard.c
13546         if(which) { // black clock
13547           if (gameMode == EditPosition || gameMode == IcsExamining) {
13548             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13549             SetBlackToPlayEvent();
13550           } else if (gameMode == EditGame || shiftKey) {
13551             AdjustClock(which, -1);
13552           } else if (gameMode == IcsPlayingWhite ||
13553                      gameMode == MachinePlaysBlack) {
13554             CallFlagEvent();
13555           }
13556         } else { // white clock
13557           if (gameMode == EditPosition || gameMode == IcsExamining) {
13558             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13559             SetWhiteToPlayEvent();
13560           } else if (gameMode == EditGame || shiftKey) {
13561             AdjustClock(which, -1);
13562           } else if (gameMode == IcsPlayingBlack ||
13563                    gameMode == MachinePlaysWhite) {
13564             CallFlagEvent();
13565           }
13566         }
13567 }
13568
13569 void
13570 DrawEvent()
13571 {
13572     /* Offer draw or accept pending draw offer from opponent */
13573
13574     if (appData.icsActive) {
13575         /* Note: tournament rules require draw offers to be
13576            made after you make your move but before you punch
13577            your clock.  Currently ICS doesn't let you do that;
13578            instead, you immediately punch your clock after making
13579            a move, but you can offer a draw at any time. */
13580
13581         SendToICS(ics_prefix);
13582         SendToICS("draw\n");
13583         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13584     } else if (cmailMsgLoaded) {
13585         if (currentMove == cmailOldMove &&
13586             commentList[cmailOldMove] != NULL &&
13587             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13588                    "Black offers a draw" : "White offers a draw")) {
13589             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13590             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13591         } else if (currentMove == cmailOldMove + 1) {
13592             char *offer = WhiteOnMove(cmailOldMove) ?
13593               "White offers a draw" : "Black offers a draw";
13594             AppendComment(currentMove, offer, TRUE);
13595             DisplayComment(currentMove - 1, offer);
13596             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13597         } else {
13598             DisplayError(_("You must make your move before offering a draw"), 0);
13599             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13600         }
13601     } else if (first.offeredDraw) {
13602         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13603     } else {
13604         if (first.sendDrawOffers) {
13605             SendToProgram("draw\n", &first);
13606             userOfferedDraw = TRUE;
13607         }
13608     }
13609 }
13610
13611 void
13612 AdjournEvent()
13613 {
13614     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13615
13616     if (appData.icsActive) {
13617         SendToICS(ics_prefix);
13618         SendToICS("adjourn\n");
13619     } else {
13620         /* Currently GNU Chess doesn't offer or accept Adjourns */
13621     }
13622 }
13623
13624
13625 void
13626 AbortEvent()
13627 {
13628     /* Offer Abort or accept pending Abort offer from opponent */
13629
13630     if (appData.icsActive) {
13631         SendToICS(ics_prefix);
13632         SendToICS("abort\n");
13633     } else {
13634         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13635     }
13636 }
13637
13638 void
13639 ResignEvent()
13640 {
13641     /* Resign.  You can do this even if it's not your turn. */
13642
13643     if (appData.icsActive) {
13644         SendToICS(ics_prefix);
13645         SendToICS("resign\n");
13646     } else {
13647         switch (gameMode) {
13648           case MachinePlaysWhite:
13649             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13650             break;
13651           case MachinePlaysBlack:
13652             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13653             break;
13654           case EditGame:
13655             if (cmailMsgLoaded) {
13656                 TruncateGame();
13657                 if (WhiteOnMove(cmailOldMove)) {
13658                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13659                 } else {
13660                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13661                 }
13662                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13663             }
13664             break;
13665           default:
13666             break;
13667         }
13668     }
13669 }
13670
13671
13672 void
13673 StopObservingEvent()
13674 {
13675     /* Stop observing current games */
13676     SendToICS(ics_prefix);
13677     SendToICS("unobserve\n");
13678 }
13679
13680 void
13681 StopExaminingEvent()
13682 {
13683     /* Stop observing current game */
13684     SendToICS(ics_prefix);
13685     SendToICS("unexamine\n");
13686 }
13687
13688 void
13689 ForwardInner(target)
13690      int target;
13691 {
13692     int limit;
13693
13694     if (appData.debugMode)
13695         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13696                 target, currentMove, forwardMostMove);
13697
13698     if (gameMode == EditPosition)
13699       return;
13700
13701     if (gameMode == PlayFromGameFile && !pausing)
13702       PauseEvent();
13703
13704     if (gameMode == IcsExamining && pausing)
13705       limit = pauseExamForwardMostMove;
13706     else
13707       limit = forwardMostMove;
13708
13709     if (target > limit) target = limit;
13710
13711     if (target > 0 && moveList[target - 1][0]) {
13712         int fromX, fromY, toX, toY;
13713         toX = moveList[target - 1][2] - AAA;
13714         toY = moveList[target - 1][3] - ONE;
13715         if (moveList[target - 1][1] == '@') {
13716             if (appData.highlightLastMove) {
13717                 SetHighlights(-1, -1, toX, toY);
13718             }
13719         } else {
13720             fromX = moveList[target - 1][0] - AAA;
13721             fromY = moveList[target - 1][1] - ONE;
13722             if (target == currentMove + 1) {
13723                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13724             }
13725             if (appData.highlightLastMove) {
13726                 SetHighlights(fromX, fromY, toX, toY);
13727             }
13728         }
13729     }
13730     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13731         gameMode == Training || gameMode == PlayFromGameFile ||
13732         gameMode == AnalyzeFile) {
13733         while (currentMove < target) {
13734             SendMoveToProgram(currentMove++, &first);
13735         }
13736     } else {
13737         currentMove = target;
13738     }
13739
13740     if (gameMode == EditGame || gameMode == EndOfGame) {
13741         whiteTimeRemaining = timeRemaining[0][currentMove];
13742         blackTimeRemaining = timeRemaining[1][currentMove];
13743     }
13744     DisplayBothClocks();
13745     DisplayMove(currentMove - 1);
13746     DrawPosition(FALSE, boards[currentMove]);
13747     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13748     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13749         DisplayComment(currentMove - 1, commentList[currentMove]);
13750     }
13751     DisplayBook(currentMove);
13752 }
13753
13754
13755 void
13756 ForwardEvent()
13757 {
13758     if (gameMode == IcsExamining && !pausing) {
13759         SendToICS(ics_prefix);
13760         SendToICS("forward\n");
13761     } else {
13762         ForwardInner(currentMove + 1);
13763     }
13764 }
13765
13766 void
13767 ToEndEvent()
13768 {
13769     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13770         /* to optimze, we temporarily turn off analysis mode while we feed
13771          * the remaining moves to the engine. Otherwise we get analysis output
13772          * after each move.
13773          */
13774         if (first.analysisSupport) {
13775           SendToProgram("exit\nforce\n", &first);
13776           first.analyzing = FALSE;
13777         }
13778     }
13779
13780     if (gameMode == IcsExamining && !pausing) {
13781         SendToICS(ics_prefix);
13782         SendToICS("forward 999999\n");
13783     } else {
13784         ForwardInner(forwardMostMove);
13785     }
13786
13787     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13788         /* we have fed all the moves, so reactivate analysis mode */
13789         SendToProgram("analyze\n", &first);
13790         first.analyzing = TRUE;
13791         /*first.maybeThinking = TRUE;*/
13792         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13793     }
13794 }
13795
13796 void
13797 BackwardInner(target)
13798      int target;
13799 {
13800     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13801
13802     if (appData.debugMode)
13803         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13804                 target, currentMove, forwardMostMove);
13805
13806     if (gameMode == EditPosition) return;
13807     if (currentMove <= backwardMostMove) {
13808         ClearHighlights();
13809         DrawPosition(full_redraw, boards[currentMove]);
13810         return;
13811     }
13812     if (gameMode == PlayFromGameFile && !pausing)
13813       PauseEvent();
13814
13815     if (moveList[target][0]) {
13816         int fromX, fromY, toX, toY;
13817         toX = moveList[target][2] - AAA;
13818         toY = moveList[target][3] - ONE;
13819         if (moveList[target][1] == '@') {
13820             if (appData.highlightLastMove) {
13821                 SetHighlights(-1, -1, toX, toY);
13822             }
13823         } else {
13824             fromX = moveList[target][0] - AAA;
13825             fromY = moveList[target][1] - ONE;
13826             if (target == currentMove - 1) {
13827                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13828             }
13829             if (appData.highlightLastMove) {
13830                 SetHighlights(fromX, fromY, toX, toY);
13831             }
13832         }
13833     }
13834     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13835         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13836         while (currentMove > target) {
13837             SendToProgram("undo\n", &first);
13838             currentMove--;
13839         }
13840     } else {
13841         currentMove = target;
13842     }
13843
13844     if (gameMode == EditGame || gameMode == EndOfGame) {
13845         whiteTimeRemaining = timeRemaining[0][currentMove];
13846         blackTimeRemaining = timeRemaining[1][currentMove];
13847     }
13848     DisplayBothClocks();
13849     DisplayMove(currentMove - 1);
13850     DrawPosition(full_redraw, boards[currentMove]);
13851     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13852     // [HGM] PV info: routine tests if comment empty
13853     DisplayComment(currentMove - 1, commentList[currentMove]);
13854     DisplayBook(currentMove);
13855 }
13856
13857 void
13858 BackwardEvent()
13859 {
13860     if (gameMode == IcsExamining && !pausing) {
13861         SendToICS(ics_prefix);
13862         SendToICS("backward\n");
13863     } else {
13864         BackwardInner(currentMove - 1);
13865     }
13866 }
13867
13868 void
13869 ToStartEvent()
13870 {
13871     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13872         /* to optimize, we temporarily turn off analysis mode while we undo
13873          * all the moves. Otherwise we get analysis output after each undo.
13874          */
13875         if (first.analysisSupport) {
13876           SendToProgram("exit\nforce\n", &first);
13877           first.analyzing = FALSE;
13878         }
13879     }
13880
13881     if (gameMode == IcsExamining && !pausing) {
13882         SendToICS(ics_prefix);
13883         SendToICS("backward 999999\n");
13884     } else {
13885         BackwardInner(backwardMostMove);
13886     }
13887
13888     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13889         /* we have fed all the moves, so reactivate analysis mode */
13890         SendToProgram("analyze\n", &first);
13891         first.analyzing = TRUE;
13892         /*first.maybeThinking = TRUE;*/
13893         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13894     }
13895 }
13896
13897 void
13898 ToNrEvent(int to)
13899 {
13900   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13901   if (to >= forwardMostMove) to = forwardMostMove;
13902   if (to <= backwardMostMove) to = backwardMostMove;
13903   if (to < currentMove) {
13904     BackwardInner(to);
13905   } else {
13906     ForwardInner(to);
13907   }
13908 }
13909
13910 void
13911 RevertEvent(Boolean annotate)
13912 {
13913     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13914         return;
13915     }
13916     if (gameMode != IcsExamining) {
13917         DisplayError(_("You are not examining a game"), 0);
13918         return;
13919     }
13920     if (pausing) {
13921         DisplayError(_("You can't revert while pausing"), 0);
13922         return;
13923     }
13924     SendToICS(ics_prefix);
13925     SendToICS("revert\n");
13926 }
13927
13928 void
13929 RetractMoveEvent()
13930 {
13931     switch (gameMode) {
13932       case MachinePlaysWhite:
13933       case MachinePlaysBlack:
13934         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13935             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13936             return;
13937         }
13938         if (forwardMostMove < 2) return;
13939         currentMove = forwardMostMove = forwardMostMove - 2;
13940         whiteTimeRemaining = timeRemaining[0][currentMove];
13941         blackTimeRemaining = timeRemaining[1][currentMove];
13942         DisplayBothClocks();
13943         DisplayMove(currentMove - 1);
13944         ClearHighlights();/*!! could figure this out*/
13945         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13946         SendToProgram("remove\n", &first);
13947         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13948         break;
13949
13950       case BeginningOfGame:
13951       default:
13952         break;
13953
13954       case IcsPlayingWhite:
13955       case IcsPlayingBlack:
13956         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13957             SendToICS(ics_prefix);
13958             SendToICS("takeback 2\n");
13959         } else {
13960             SendToICS(ics_prefix);
13961             SendToICS("takeback 1\n");
13962         }
13963         break;
13964     }
13965 }
13966
13967 void
13968 MoveNowEvent()
13969 {
13970     ChessProgramState *cps;
13971
13972     switch (gameMode) {
13973       case MachinePlaysWhite:
13974         if (!WhiteOnMove(forwardMostMove)) {
13975             DisplayError(_("It is your turn"), 0);
13976             return;
13977         }
13978         cps = &first;
13979         break;
13980       case MachinePlaysBlack:
13981         if (WhiteOnMove(forwardMostMove)) {
13982             DisplayError(_("It is your turn"), 0);
13983             return;
13984         }
13985         cps = &first;
13986         break;
13987       case TwoMachinesPlay:
13988         if (WhiteOnMove(forwardMostMove) ==
13989             (first.twoMachinesColor[0] == 'w')) {
13990             cps = &first;
13991         } else {
13992             cps = &second;
13993         }
13994         break;
13995       case BeginningOfGame:
13996       default:
13997         return;
13998     }
13999     SendToProgram("?\n", cps);
14000 }
14001
14002 void
14003 TruncateGameEvent()
14004 {
14005     EditGameEvent();
14006     if (gameMode != EditGame) return;
14007     TruncateGame();
14008 }
14009
14010 void
14011 TruncateGame()
14012 {
14013     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14014     if (forwardMostMove > currentMove) {
14015         if (gameInfo.resultDetails != NULL) {
14016             free(gameInfo.resultDetails);
14017             gameInfo.resultDetails = NULL;
14018             gameInfo.result = GameUnfinished;
14019         }
14020         forwardMostMove = currentMove;
14021         HistorySet(parseList, backwardMostMove, forwardMostMove,
14022                    currentMove-1);
14023     }
14024 }
14025
14026 void
14027 HintEvent()
14028 {
14029     if (appData.noChessProgram) return;
14030     switch (gameMode) {
14031       case MachinePlaysWhite:
14032         if (WhiteOnMove(forwardMostMove)) {
14033             DisplayError(_("Wait until your turn"), 0);
14034             return;
14035         }
14036         break;
14037       case BeginningOfGame:
14038       case MachinePlaysBlack:
14039         if (!WhiteOnMove(forwardMostMove)) {
14040             DisplayError(_("Wait until your turn"), 0);
14041             return;
14042         }
14043         break;
14044       default:
14045         DisplayError(_("No hint available"), 0);
14046         return;
14047     }
14048     SendToProgram("hint\n", &first);
14049     hintRequested = TRUE;
14050 }
14051
14052 void
14053 BookEvent()
14054 {
14055     if (appData.noChessProgram) return;
14056     switch (gameMode) {
14057       case MachinePlaysWhite:
14058         if (WhiteOnMove(forwardMostMove)) {
14059             DisplayError(_("Wait until your turn"), 0);
14060             return;
14061         }
14062         break;
14063       case BeginningOfGame:
14064       case MachinePlaysBlack:
14065         if (!WhiteOnMove(forwardMostMove)) {
14066             DisplayError(_("Wait until your turn"), 0);
14067             return;
14068         }
14069         break;
14070       case EditPosition:
14071         EditPositionDone(TRUE);
14072         break;
14073       case TwoMachinesPlay:
14074         return;
14075       default:
14076         break;
14077     }
14078     SendToProgram("bk\n", &first);
14079     bookOutput[0] = NULLCHAR;
14080     bookRequested = TRUE;
14081 }
14082
14083 void
14084 AboutGameEvent()
14085 {
14086     char *tags = PGNTags(&gameInfo);
14087     TagsPopUp(tags, CmailMsg());
14088     free(tags);
14089 }
14090
14091 /* end button procedures */
14092
14093 void
14094 PrintPosition(fp, move)
14095      FILE *fp;
14096      int move;
14097 {
14098     int i, j;
14099
14100     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14101         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14102             char c = PieceToChar(boards[move][i][j]);
14103             fputc(c == 'x' ? '.' : c, fp);
14104             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14105         }
14106     }
14107     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14108       fprintf(fp, "white to play\n");
14109     else
14110       fprintf(fp, "black to play\n");
14111 }
14112
14113 void
14114 PrintOpponents(fp)
14115      FILE *fp;
14116 {
14117     if (gameInfo.white != NULL) {
14118         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14119     } else {
14120         fprintf(fp, "\n");
14121     }
14122 }
14123
14124 /* Find last component of program's own name, using some heuristics */
14125 void
14126 TidyProgramName(prog, host, buf)
14127      char *prog, *host, buf[MSG_SIZ];
14128 {
14129     char *p, *q;
14130     int local = (strcmp(host, "localhost") == 0);
14131     while (!local && (p = strchr(prog, ';')) != NULL) {
14132         p++;
14133         while (*p == ' ') p++;
14134         prog = p;
14135     }
14136     if (*prog == '"' || *prog == '\'') {
14137         q = strchr(prog + 1, *prog);
14138     } else {
14139         q = strchr(prog, ' ');
14140     }
14141     if (q == NULL) q = prog + strlen(prog);
14142     p = q;
14143     while (p >= prog && *p != '/' && *p != '\\') p--;
14144     p++;
14145     if(p == prog && *p == '"') p++;
14146     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14147     memcpy(buf, p, q - p);
14148     buf[q - p] = NULLCHAR;
14149     if (!local) {
14150         strcat(buf, "@");
14151         strcat(buf, host);
14152     }
14153 }
14154
14155 char *
14156 TimeControlTagValue()
14157 {
14158     char buf[MSG_SIZ];
14159     if (!appData.clockMode) {
14160       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14161     } else if (movesPerSession > 0) {
14162       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14163     } else if (timeIncrement == 0) {
14164       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14165     } else {
14166       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14167     }
14168     return StrSave(buf);
14169 }
14170
14171 void
14172 SetGameInfo()
14173 {
14174     /* This routine is used only for certain modes */
14175     VariantClass v = gameInfo.variant;
14176     ChessMove r = GameUnfinished;
14177     char *p = NULL;
14178
14179     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14180         r = gameInfo.result;
14181         p = gameInfo.resultDetails;
14182         gameInfo.resultDetails = NULL;
14183     }
14184     ClearGameInfo(&gameInfo);
14185     gameInfo.variant = v;
14186
14187     switch (gameMode) {
14188       case MachinePlaysWhite:
14189         gameInfo.event = StrSave( appData.pgnEventHeader );
14190         gameInfo.site = StrSave(HostName());
14191         gameInfo.date = PGNDate();
14192         gameInfo.round = StrSave("-");
14193         gameInfo.white = StrSave(first.tidy);
14194         gameInfo.black = StrSave(UserName());
14195         gameInfo.timeControl = TimeControlTagValue();
14196         break;
14197
14198       case MachinePlaysBlack:
14199         gameInfo.event = StrSave( appData.pgnEventHeader );
14200         gameInfo.site = StrSave(HostName());
14201         gameInfo.date = PGNDate();
14202         gameInfo.round = StrSave("-");
14203         gameInfo.white = StrSave(UserName());
14204         gameInfo.black = StrSave(first.tidy);
14205         gameInfo.timeControl = TimeControlTagValue();
14206         break;
14207
14208       case TwoMachinesPlay:
14209         gameInfo.event = StrSave( appData.pgnEventHeader );
14210         gameInfo.site = StrSave(HostName());
14211         gameInfo.date = PGNDate();
14212         if (roundNr > 0) {
14213             char buf[MSG_SIZ];
14214             snprintf(buf, MSG_SIZ, "%d", roundNr);
14215             gameInfo.round = StrSave(buf);
14216         } else {
14217             gameInfo.round = StrSave("-");
14218         }
14219         if (first.twoMachinesColor[0] == 'w') {
14220             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14221             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14222         } else {
14223             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14224             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14225         }
14226         gameInfo.timeControl = TimeControlTagValue();
14227         break;
14228
14229       case EditGame:
14230         gameInfo.event = StrSave("Edited game");
14231         gameInfo.site = StrSave(HostName());
14232         gameInfo.date = PGNDate();
14233         gameInfo.round = StrSave("-");
14234         gameInfo.white = StrSave("-");
14235         gameInfo.black = StrSave("-");
14236         gameInfo.result = r;
14237         gameInfo.resultDetails = p;
14238         break;
14239
14240       case EditPosition:
14241         gameInfo.event = StrSave("Edited position");
14242         gameInfo.site = StrSave(HostName());
14243         gameInfo.date = PGNDate();
14244         gameInfo.round = StrSave("-");
14245         gameInfo.white = StrSave("-");
14246         gameInfo.black = StrSave("-");
14247         break;
14248
14249       case IcsPlayingWhite:
14250       case IcsPlayingBlack:
14251       case IcsObserving:
14252       case IcsExamining:
14253         break;
14254
14255       case PlayFromGameFile:
14256         gameInfo.event = StrSave("Game from non-PGN file");
14257         gameInfo.site = StrSave(HostName());
14258         gameInfo.date = PGNDate();
14259         gameInfo.round = StrSave("-");
14260         gameInfo.white = StrSave("?");
14261         gameInfo.black = StrSave("?");
14262         break;
14263
14264       default:
14265         break;
14266     }
14267 }
14268
14269 void
14270 ReplaceComment(index, text)
14271      int index;
14272      char *text;
14273 {
14274     int len;
14275     char *p;
14276     float score;
14277
14278     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14279        pvInfoList[index-1].depth == len &&
14280        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14281        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14282     while (*text == '\n') text++;
14283     len = strlen(text);
14284     while (len > 0 && text[len - 1] == '\n') len--;
14285
14286     if (commentList[index] != NULL)
14287       free(commentList[index]);
14288
14289     if (len == 0) {
14290         commentList[index] = NULL;
14291         return;
14292     }
14293   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14294       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14295       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14296     commentList[index] = (char *) malloc(len + 2);
14297     strncpy(commentList[index], text, len);
14298     commentList[index][len] = '\n';
14299     commentList[index][len + 1] = NULLCHAR;
14300   } else {
14301     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14302     char *p;
14303     commentList[index] = (char *) malloc(len + 7);
14304     safeStrCpy(commentList[index], "{\n", 3);
14305     safeStrCpy(commentList[index]+2, text, len+1);
14306     commentList[index][len+2] = NULLCHAR;
14307     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14308     strcat(commentList[index], "\n}\n");
14309   }
14310 }
14311
14312 void
14313 CrushCRs(text)
14314      char *text;
14315 {
14316   char *p = text;
14317   char *q = text;
14318   char ch;
14319
14320   do {
14321     ch = *p++;
14322     if (ch == '\r') continue;
14323     *q++ = ch;
14324   } while (ch != '\0');
14325 }
14326
14327 void
14328 AppendComment(index, text, addBraces)
14329      int index;
14330      char *text;
14331      Boolean addBraces; // [HGM] braces: tells if we should add {}
14332 {
14333     int oldlen, len;
14334     char *old;
14335
14336 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14337     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14338
14339     CrushCRs(text);
14340     while (*text == '\n') text++;
14341     len = strlen(text);
14342     while (len > 0 && text[len - 1] == '\n') len--;
14343
14344     if (len == 0) return;
14345
14346     if (commentList[index] != NULL) {
14347         old = commentList[index];
14348         oldlen = strlen(old);
14349         while(commentList[index][oldlen-1] ==  '\n')
14350           commentList[index][--oldlen] = NULLCHAR;
14351         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14352         safeStrCpy(commentList[index], old, oldlen + len + 6);
14353         free(old);
14354         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14355         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14356           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14357           while (*text == '\n') { text++; len--; }
14358           commentList[index][--oldlen] = NULLCHAR;
14359       }
14360         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14361         else          strcat(commentList[index], "\n");
14362         strcat(commentList[index], text);
14363         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14364         else          strcat(commentList[index], "\n");
14365     } else {
14366         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14367         if(addBraces)
14368           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14369         else commentList[index][0] = NULLCHAR;
14370         strcat(commentList[index], text);
14371         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14372         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14373     }
14374 }
14375
14376 static char * FindStr( char * text, char * sub_text )
14377 {
14378     char * result = strstr( text, sub_text );
14379
14380     if( result != NULL ) {
14381         result += strlen( sub_text );
14382     }
14383
14384     return result;
14385 }
14386
14387 /* [AS] Try to extract PV info from PGN comment */
14388 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14389 char *GetInfoFromComment( int index, char * text )
14390 {
14391     char * sep = text, *p;
14392
14393     if( text != NULL && index > 0 ) {
14394         int score = 0;
14395         int depth = 0;
14396         int time = -1, sec = 0, deci;
14397         char * s_eval = FindStr( text, "[%eval " );
14398         char * s_emt = FindStr( text, "[%emt " );
14399
14400         if( s_eval != NULL || s_emt != NULL ) {
14401             /* New style */
14402             char delim;
14403
14404             if( s_eval != NULL ) {
14405                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14406                     return text;
14407                 }
14408
14409                 if( delim != ']' ) {
14410                     return text;
14411                 }
14412             }
14413
14414             if( s_emt != NULL ) {
14415             }
14416                 return text;
14417         }
14418         else {
14419             /* We expect something like: [+|-]nnn.nn/dd */
14420             int score_lo = 0;
14421
14422             if(*text != '{') return text; // [HGM] braces: must be normal comment
14423
14424             sep = strchr( text, '/' );
14425             if( sep == NULL || sep < (text+4) ) {
14426                 return text;
14427             }
14428
14429             p = text;
14430             if(p[1] == '(') { // comment starts with PV
14431                p = strchr(p, ')'); // locate end of PV
14432                if(p == NULL || sep < p+5) return text;
14433                // at this point we have something like "{(.*) +0.23/6 ..."
14434                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14435                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14436                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14437             }
14438             time = -1; sec = -1; deci = -1;
14439             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14440                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14441                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14442                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14443                 return text;
14444             }
14445
14446             if( score_lo < 0 || score_lo >= 100 ) {
14447                 return text;
14448             }
14449
14450             if(sec >= 0) time = 600*time + 10*sec; else
14451             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14452
14453             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14454
14455             /* [HGM] PV time: now locate end of PV info */
14456             while( *++sep >= '0' && *sep <= '9'); // strip depth
14457             if(time >= 0)
14458             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14459             if(sec >= 0)
14460             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14461             if(deci >= 0)
14462             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14463             while(*sep == ' ') sep++;
14464         }
14465
14466         if( depth <= 0 ) {
14467             return text;
14468         }
14469
14470         if( time < 0 ) {
14471             time = -1;
14472         }
14473
14474         pvInfoList[index-1].depth = depth;
14475         pvInfoList[index-1].score = score;
14476         pvInfoList[index-1].time  = 10*time; // centi-sec
14477         if(*sep == '}') *sep = 0; else *--sep = '{';
14478         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14479     }
14480     return sep;
14481 }
14482
14483 void
14484 SendToProgram(message, cps)
14485      char *message;
14486      ChessProgramState *cps;
14487 {
14488     int count, outCount, error;
14489     char buf[MSG_SIZ];
14490
14491     if (cps->pr == NULL) return;
14492     Attention(cps);
14493
14494     if (appData.debugMode) {
14495         TimeMark now;
14496         GetTimeMark(&now);
14497         fprintf(debugFP, "%ld >%-6s: %s",
14498                 SubtractTimeMarks(&now, &programStartTime),
14499                 cps->which, message);
14500     }
14501
14502     count = strlen(message);
14503     outCount = OutputToProcess(cps->pr, message, count, &error);
14504     if (outCount < count && !exiting
14505                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14506       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14507       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14508         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14509             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14510                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14511                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14512                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14513             } else {
14514                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14515                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14516                 gameInfo.result = res;
14517             }
14518             gameInfo.resultDetails = StrSave(buf);
14519         }
14520         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14521         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14522     }
14523 }
14524
14525 void
14526 ReceiveFromProgram(isr, closure, message, count, error)
14527      InputSourceRef isr;
14528      VOIDSTAR closure;
14529      char *message;
14530      int count;
14531      int error;
14532 {
14533     char *end_str;
14534     char buf[MSG_SIZ];
14535     ChessProgramState *cps = (ChessProgramState *)closure;
14536
14537     if (isr != cps->isr) return; /* Killed intentionally */
14538     if (count <= 0) {
14539         if (count == 0) {
14540             RemoveInputSource(cps->isr);
14541             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14542             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14543                     _(cps->which), cps->program);
14544         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14545                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14546                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14547                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14548                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14549                 } else {
14550                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14551                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14552                     gameInfo.result = res;
14553                 }
14554                 gameInfo.resultDetails = StrSave(buf);
14555             }
14556             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14557             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14558         } else {
14559             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14560                     _(cps->which), cps->program);
14561             RemoveInputSource(cps->isr);
14562
14563             /* [AS] Program is misbehaving badly... kill it */
14564             if( count == -2 ) {
14565                 DestroyChildProcess( cps->pr, 9 );
14566                 cps->pr = NoProc;
14567             }
14568
14569             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14570         }
14571         return;
14572     }
14573
14574     if ((end_str = strchr(message, '\r')) != NULL)
14575       *end_str = NULLCHAR;
14576     if ((end_str = strchr(message, '\n')) != NULL)
14577       *end_str = NULLCHAR;
14578
14579     if (appData.debugMode) {
14580         TimeMark now; int print = 1;
14581         char *quote = ""; char c; int i;
14582
14583         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14584                 char start = message[0];
14585                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14586                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14587                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14588                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14589                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14590                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14591                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14592                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14593                    sscanf(message, "hint: %c", &c)!=1 && 
14594                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14595                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14596                     print = (appData.engineComments >= 2);
14597                 }
14598                 message[0] = start; // restore original message
14599         }
14600         if(print) {
14601                 GetTimeMark(&now);
14602                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14603                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14604                         quote,
14605                         message);
14606         }
14607     }
14608
14609     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14610     if (appData.icsEngineAnalyze) {
14611         if (strstr(message, "whisper") != NULL ||
14612              strstr(message, "kibitz") != NULL ||
14613             strstr(message, "tellics") != NULL) return;
14614     }
14615
14616     HandleMachineMove(message, cps);
14617 }
14618
14619
14620 void
14621 SendTimeControl(cps, mps, tc, inc, sd, st)
14622      ChessProgramState *cps;
14623      int mps, inc, sd, st;
14624      long tc;
14625 {
14626     char buf[MSG_SIZ];
14627     int seconds;
14628
14629     if( timeControl_2 > 0 ) {
14630         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14631             tc = timeControl_2;
14632         }
14633     }
14634     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14635     inc /= cps->timeOdds;
14636     st  /= cps->timeOdds;
14637
14638     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14639
14640     if (st > 0) {
14641       /* Set exact time per move, normally using st command */
14642       if (cps->stKludge) {
14643         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14644         seconds = st % 60;
14645         if (seconds == 0) {
14646           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14647         } else {
14648           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14649         }
14650       } else {
14651         snprintf(buf, MSG_SIZ, "st %d\n", st);
14652       }
14653     } else {
14654       /* Set conventional or incremental time control, using level command */
14655       if (seconds == 0) {
14656         /* Note old gnuchess bug -- minutes:seconds used to not work.
14657            Fixed in later versions, but still avoid :seconds
14658            when seconds is 0. */
14659         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14660       } else {
14661         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14662                  seconds, inc/1000.);
14663       }
14664     }
14665     SendToProgram(buf, cps);
14666
14667     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14668     /* Orthogonally, limit search to given depth */
14669     if (sd > 0) {
14670       if (cps->sdKludge) {
14671         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14672       } else {
14673         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14674       }
14675       SendToProgram(buf, cps);
14676     }
14677
14678     if(cps->nps >= 0) { /* [HGM] nps */
14679         if(cps->supportsNPS == FALSE)
14680           cps->nps = -1; // don't use if engine explicitly says not supported!
14681         else {
14682           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14683           SendToProgram(buf, cps);
14684         }
14685     }
14686 }
14687
14688 ChessProgramState *WhitePlayer()
14689 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14690 {
14691     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14692        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14693         return &second;
14694     return &first;
14695 }
14696
14697 void
14698 SendTimeRemaining(cps, machineWhite)
14699      ChessProgramState *cps;
14700      int /*boolean*/ machineWhite;
14701 {
14702     char message[MSG_SIZ];
14703     long time, otime;
14704
14705     /* Note: this routine must be called when the clocks are stopped
14706        or when they have *just* been set or switched; otherwise
14707        it will be off by the time since the current tick started.
14708     */
14709     if (machineWhite) {
14710         time = whiteTimeRemaining / 10;
14711         otime = blackTimeRemaining / 10;
14712     } else {
14713         time = blackTimeRemaining / 10;
14714         otime = whiteTimeRemaining / 10;
14715     }
14716     /* [HGM] translate opponent's time by time-odds factor */
14717     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14718     if (appData.debugMode) {
14719         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14720     }
14721
14722     if (time <= 0) time = 1;
14723     if (otime <= 0) otime = 1;
14724
14725     snprintf(message, MSG_SIZ, "time %ld\n", time);
14726     SendToProgram(message, cps);
14727
14728     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14729     SendToProgram(message, cps);
14730 }
14731
14732 int
14733 BoolFeature(p, name, loc, cps)
14734      char **p;
14735      char *name;
14736      int *loc;
14737      ChessProgramState *cps;
14738 {
14739   char buf[MSG_SIZ];
14740   int len = strlen(name);
14741   int val;
14742
14743   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14744     (*p) += len + 1;
14745     sscanf(*p, "%d", &val);
14746     *loc = (val != 0);
14747     while (**p && **p != ' ')
14748       (*p)++;
14749     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14750     SendToProgram(buf, cps);
14751     return TRUE;
14752   }
14753   return FALSE;
14754 }
14755
14756 int
14757 IntFeature(p, name, loc, cps)
14758      char **p;
14759      char *name;
14760      int *loc;
14761      ChessProgramState *cps;
14762 {
14763   char buf[MSG_SIZ];
14764   int len = strlen(name);
14765   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14766     (*p) += len + 1;
14767     sscanf(*p, "%d", loc);
14768     while (**p && **p != ' ') (*p)++;
14769     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14770     SendToProgram(buf, cps);
14771     return TRUE;
14772   }
14773   return FALSE;
14774 }
14775
14776 int
14777 StringFeature(p, name, loc, cps)
14778      char **p;
14779      char *name;
14780      char loc[];
14781      ChessProgramState *cps;
14782 {
14783   char buf[MSG_SIZ];
14784   int len = strlen(name);
14785   if (strncmp((*p), name, len) == 0
14786       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14787     (*p) += len + 2;
14788     sscanf(*p, "%[^\"]", loc);
14789     while (**p && **p != '\"') (*p)++;
14790     if (**p == '\"') (*p)++;
14791     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14792     SendToProgram(buf, cps);
14793     return TRUE;
14794   }
14795   return FALSE;
14796 }
14797
14798 int
14799 ParseOption(Option *opt, ChessProgramState *cps)
14800 // [HGM] options: process the string that defines an engine option, and determine
14801 // name, type, default value, and allowed value range
14802 {
14803         char *p, *q, buf[MSG_SIZ];
14804         int n, min = (-1)<<31, max = 1<<31, def;
14805
14806         if(p = strstr(opt->name, " -spin ")) {
14807             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14808             if(max < min) max = min; // enforce consistency
14809             if(def < min) def = min;
14810             if(def > max) def = max;
14811             opt->value = def;
14812             opt->min = min;
14813             opt->max = max;
14814             opt->type = Spin;
14815         } else if((p = strstr(opt->name, " -slider "))) {
14816             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14817             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14818             if(max < min) max = min; // enforce consistency
14819             if(def < min) def = min;
14820             if(def > max) def = max;
14821             opt->value = def;
14822             opt->min = min;
14823             opt->max = max;
14824             opt->type = Spin; // Slider;
14825         } else if((p = strstr(opt->name, " -string "))) {
14826             opt->textValue = p+9;
14827             opt->type = TextBox;
14828         } else if((p = strstr(opt->name, " -file "))) {
14829             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14830             opt->textValue = p+7;
14831             opt->type = FileName; // FileName;
14832         } else if((p = strstr(opt->name, " -path "))) {
14833             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14834             opt->textValue = p+7;
14835             opt->type = PathName; // PathName;
14836         } else if(p = strstr(opt->name, " -check ")) {
14837             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14838             opt->value = (def != 0);
14839             opt->type = CheckBox;
14840         } else if(p = strstr(opt->name, " -combo ")) {
14841             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14842             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14843             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14844             opt->value = n = 0;
14845             while(q = StrStr(q, " /// ")) {
14846                 n++; *q = 0;    // count choices, and null-terminate each of them
14847                 q += 5;
14848                 if(*q == '*') { // remember default, which is marked with * prefix
14849                     q++;
14850                     opt->value = n;
14851                 }
14852                 cps->comboList[cps->comboCnt++] = q;
14853             }
14854             cps->comboList[cps->comboCnt++] = NULL;
14855             opt->max = n + 1;
14856             opt->type = ComboBox;
14857         } else if(p = strstr(opt->name, " -button")) {
14858             opt->type = Button;
14859         } else if(p = strstr(opt->name, " -save")) {
14860             opt->type = SaveButton;
14861         } else return FALSE;
14862         *p = 0; // terminate option name
14863         // now look if the command-line options define a setting for this engine option.
14864         if(cps->optionSettings && cps->optionSettings[0])
14865             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14866         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14867           snprintf(buf, MSG_SIZ, "option %s", p);
14868                 if(p = strstr(buf, ",")) *p = 0;
14869                 if(q = strchr(buf, '=')) switch(opt->type) {
14870                     case ComboBox:
14871                         for(n=0; n<opt->max; n++)
14872                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14873                         break;
14874                     case TextBox:
14875                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14876                         break;
14877                     case Spin:
14878                     case CheckBox:
14879                         opt->value = atoi(q+1);
14880                     default:
14881                         break;
14882                 }
14883                 strcat(buf, "\n");
14884                 SendToProgram(buf, cps);
14885         }
14886         return TRUE;
14887 }
14888
14889 void
14890 FeatureDone(cps, val)
14891      ChessProgramState* cps;
14892      int val;
14893 {
14894   DelayedEventCallback cb = GetDelayedEvent();
14895   if ((cb == InitBackEnd3 && cps == &first) ||
14896       (cb == SettingsMenuIfReady && cps == &second) ||
14897       (cb == LoadEngine) ||
14898       (cb == TwoMachinesEventIfReady)) {
14899     CancelDelayedEvent();
14900     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14901   }
14902   cps->initDone = val;
14903 }
14904
14905 /* Parse feature command from engine */
14906 void
14907 ParseFeatures(args, cps)
14908      char* args;
14909      ChessProgramState *cps;
14910 {
14911   char *p = args;
14912   char *q;
14913   int val;
14914   char buf[MSG_SIZ];
14915
14916   for (;;) {
14917     while (*p == ' ') p++;
14918     if (*p == NULLCHAR) return;
14919
14920     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14921     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14922     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14923     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14924     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14925     if (BoolFeature(&p, "reuse", &val, cps)) {
14926       /* Engine can disable reuse, but can't enable it if user said no */
14927       if (!val) cps->reuse = FALSE;
14928       continue;
14929     }
14930     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14931     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14932       if (gameMode == TwoMachinesPlay) {
14933         DisplayTwoMachinesTitle();
14934       } else {
14935         DisplayTitle("");
14936       }
14937       continue;
14938     }
14939     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14940     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14941     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14942     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14943     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14944     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14945     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14946     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14947     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14948     if (IntFeature(&p, "done", &val, cps)) {
14949       FeatureDone(cps, val);
14950       continue;
14951     }
14952     /* Added by Tord: */
14953     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14954     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14955     /* End of additions by Tord */
14956
14957     /* [HGM] added features: */
14958     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14959     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14960     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14961     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14962     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14963     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14964     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14965         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14966           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14967             SendToProgram(buf, cps);
14968             continue;
14969         }
14970         if(cps->nrOptions >= MAX_OPTIONS) {
14971             cps->nrOptions--;
14972             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14973             DisplayError(buf, 0);
14974         }
14975         continue;
14976     }
14977     /* End of additions by HGM */
14978
14979     /* unknown feature: complain and skip */
14980     q = p;
14981     while (*q && *q != '=') q++;
14982     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14983     SendToProgram(buf, cps);
14984     p = q;
14985     if (*p == '=') {
14986       p++;
14987       if (*p == '\"') {
14988         p++;
14989         while (*p && *p != '\"') p++;
14990         if (*p == '\"') p++;
14991       } else {
14992         while (*p && *p != ' ') p++;
14993       }
14994     }
14995   }
14996
14997 }
14998
14999 void
15000 PeriodicUpdatesEvent(newState)
15001      int newState;
15002 {
15003     if (newState == appData.periodicUpdates)
15004       return;
15005
15006     appData.periodicUpdates=newState;
15007
15008     /* Display type changes, so update it now */
15009 //    DisplayAnalysis();
15010
15011     /* Get the ball rolling again... */
15012     if (newState) {
15013         AnalysisPeriodicEvent(1);
15014         StartAnalysisClock();
15015     }
15016 }
15017
15018 void
15019 PonderNextMoveEvent(newState)
15020      int newState;
15021 {
15022     if (newState == appData.ponderNextMove) return;
15023     if (gameMode == EditPosition) EditPositionDone(TRUE);
15024     if (newState) {
15025         SendToProgram("hard\n", &first);
15026         if (gameMode == TwoMachinesPlay) {
15027             SendToProgram("hard\n", &second);
15028         }
15029     } else {
15030         SendToProgram("easy\n", &first);
15031         thinkOutput[0] = NULLCHAR;
15032         if (gameMode == TwoMachinesPlay) {
15033             SendToProgram("easy\n", &second);
15034         }
15035     }
15036     appData.ponderNextMove = newState;
15037 }
15038
15039 void
15040 NewSettingEvent(option, feature, command, value)
15041      char *command;
15042      int option, value, *feature;
15043 {
15044     char buf[MSG_SIZ];
15045
15046     if (gameMode == EditPosition) EditPositionDone(TRUE);
15047     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15048     if(feature == NULL || *feature) SendToProgram(buf, &first);
15049     if (gameMode == TwoMachinesPlay) {
15050         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15051     }
15052 }
15053
15054 void
15055 ShowThinkingEvent()
15056 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15057 {
15058     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15059     int newState = appData.showThinking
15060         // [HGM] thinking: other features now need thinking output as well
15061         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15062
15063     if (oldState == newState) return;
15064     oldState = newState;
15065     if (gameMode == EditPosition) EditPositionDone(TRUE);
15066     if (oldState) {
15067         SendToProgram("post\n", &first);
15068         if (gameMode == TwoMachinesPlay) {
15069             SendToProgram("post\n", &second);
15070         }
15071     } else {
15072         SendToProgram("nopost\n", &first);
15073         thinkOutput[0] = NULLCHAR;
15074         if (gameMode == TwoMachinesPlay) {
15075             SendToProgram("nopost\n", &second);
15076         }
15077     }
15078 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15079 }
15080
15081 void
15082 AskQuestionEvent(title, question, replyPrefix, which)
15083      char *title; char *question; char *replyPrefix; char *which;
15084 {
15085   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15086   if (pr == NoProc) return;
15087   AskQuestion(title, question, replyPrefix, pr);
15088 }
15089
15090 void
15091 TypeInEvent(char firstChar)
15092 {
15093     if ((gameMode == BeginningOfGame && !appData.icsActive) || 
15094         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15095         gameMode == AnalyzeMode || gameMode == EditGame || 
15096         gameMode == EditPosition || gameMode == IcsExamining ||
15097         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15098         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15099                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15100                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||
15101         gameMode == Training) PopUpMoveDialog(firstChar);
15102 }
15103
15104 void
15105 TypeInDoneEvent(char *move)
15106 {
15107         Board board;
15108         int n, fromX, fromY, toX, toY;
15109         char promoChar;
15110         ChessMove moveType;
15111
15112         // [HGM] FENedit
15113         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15114                 EditPositionPasteFEN(move);
15115                 return;
15116         }
15117         // [HGM] movenum: allow move number to be typed in any mode
15118         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15119           ToNrEvent(2*n-1);
15120           return;
15121         }
15122
15123       if (gameMode != EditGame && currentMove != forwardMostMove && 
15124         gameMode != Training) {
15125         DisplayMoveError(_("Displayed move is not current"));
15126       } else {
15127         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15128           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15129         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15130         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, 
15131           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15132           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     
15133         } else {
15134           DisplayMoveError(_("Could not parse move"));
15135         }
15136       }
15137 }
15138
15139 void
15140 DisplayMove(moveNumber)
15141      int moveNumber;
15142 {
15143     char message[MSG_SIZ];
15144     char res[MSG_SIZ];
15145     char cpThinkOutput[MSG_SIZ];
15146
15147     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15148
15149     if (moveNumber == forwardMostMove - 1 ||
15150         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15151
15152         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15153
15154         if (strchr(cpThinkOutput, '\n')) {
15155             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15156         }
15157     } else {
15158         *cpThinkOutput = NULLCHAR;
15159     }
15160
15161     /* [AS] Hide thinking from human user */
15162     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15163         *cpThinkOutput = NULLCHAR;
15164         if( thinkOutput[0] != NULLCHAR ) {
15165             int i;
15166
15167             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15168                 cpThinkOutput[i] = '.';
15169             }
15170             cpThinkOutput[i] = NULLCHAR;
15171             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15172         }
15173     }
15174
15175     if (moveNumber == forwardMostMove - 1 &&
15176         gameInfo.resultDetails != NULL) {
15177         if (gameInfo.resultDetails[0] == NULLCHAR) {
15178           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15179         } else {
15180           snprintf(res, MSG_SIZ, " {%s} %s",
15181                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15182         }
15183     } else {
15184         res[0] = NULLCHAR;
15185     }
15186
15187     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15188         DisplayMessage(res, cpThinkOutput);
15189     } else {
15190       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15191                 WhiteOnMove(moveNumber) ? " " : ".. ",
15192                 parseList[moveNumber], res);
15193         DisplayMessage(message, cpThinkOutput);
15194     }
15195 }
15196
15197 void
15198 DisplayComment(moveNumber, text)
15199      int moveNumber;
15200      char *text;
15201 {
15202     char title[MSG_SIZ];
15203     char buf[8000]; // comment can be long!
15204     int score, depth;
15205
15206     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15207       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15208     } else {
15209       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15210               WhiteOnMove(moveNumber) ? " " : ".. ",
15211               parseList[moveNumber]);
15212     }
15213     // [HGM] PV info: display PV info together with (or as) comment
15214     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15215       if(text == NULL) text = "";
15216       score = pvInfoList[moveNumber].score;
15217       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15218               depth, (pvInfoList[moveNumber].time+50)/100, text);
15219       text = buf;
15220     }
15221     if (text != NULL && (appData.autoDisplayComment || commentUp))
15222         CommentPopUp(title, text);
15223 }
15224
15225 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15226  * might be busy thinking or pondering.  It can be omitted if your
15227  * gnuchess is configured to stop thinking immediately on any user
15228  * input.  However, that gnuchess feature depends on the FIONREAD
15229  * ioctl, which does not work properly on some flavors of Unix.
15230  */
15231 void
15232 Attention(cps)
15233      ChessProgramState *cps;
15234 {
15235 #if ATTENTION
15236     if (!cps->useSigint) return;
15237     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15238     switch (gameMode) {
15239       case MachinePlaysWhite:
15240       case MachinePlaysBlack:
15241       case TwoMachinesPlay:
15242       case IcsPlayingWhite:
15243       case IcsPlayingBlack:
15244       case AnalyzeMode:
15245       case AnalyzeFile:
15246         /* Skip if we know it isn't thinking */
15247         if (!cps->maybeThinking) return;
15248         if (appData.debugMode)
15249           fprintf(debugFP, "Interrupting %s\n", cps->which);
15250         InterruptChildProcess(cps->pr);
15251         cps->maybeThinking = FALSE;
15252         break;
15253       default:
15254         break;
15255     }
15256 #endif /*ATTENTION*/
15257 }
15258
15259 int
15260 CheckFlags()
15261 {
15262     if (whiteTimeRemaining <= 0) {
15263         if (!whiteFlag) {
15264             whiteFlag = TRUE;
15265             if (appData.icsActive) {
15266                 if (appData.autoCallFlag &&
15267                     gameMode == IcsPlayingBlack && !blackFlag) {
15268                   SendToICS(ics_prefix);
15269                   SendToICS("flag\n");
15270                 }
15271             } else {
15272                 if (blackFlag) {
15273                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15274                 } else {
15275                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15276                     if (appData.autoCallFlag) {
15277                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15278                         return TRUE;
15279                     }
15280                 }
15281             }
15282         }
15283     }
15284     if (blackTimeRemaining <= 0) {
15285         if (!blackFlag) {
15286             blackFlag = TRUE;
15287             if (appData.icsActive) {
15288                 if (appData.autoCallFlag &&
15289                     gameMode == IcsPlayingWhite && !whiteFlag) {
15290                   SendToICS(ics_prefix);
15291                   SendToICS("flag\n");
15292                 }
15293             } else {
15294                 if (whiteFlag) {
15295                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15296                 } else {
15297                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15298                     if (appData.autoCallFlag) {
15299                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15300                         return TRUE;
15301                     }
15302                 }
15303             }
15304         }
15305     }
15306     return FALSE;
15307 }
15308
15309 void
15310 CheckTimeControl()
15311 {
15312     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15313         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15314
15315     /*
15316      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15317      */
15318     if ( !WhiteOnMove(forwardMostMove) ) {
15319         /* White made time control */
15320         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15321         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15322         /* [HGM] time odds: correct new time quota for time odds! */
15323                                             / WhitePlayer()->timeOdds;
15324         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15325     } else {
15326         lastBlack -= blackTimeRemaining;
15327         /* Black made time control */
15328         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15329                                             / WhitePlayer()->other->timeOdds;
15330         lastWhite = whiteTimeRemaining;
15331     }
15332 }
15333
15334 void
15335 DisplayBothClocks()
15336 {
15337     int wom = gameMode == EditPosition ?
15338       !blackPlaysFirst : WhiteOnMove(currentMove);
15339     DisplayWhiteClock(whiteTimeRemaining, wom);
15340     DisplayBlackClock(blackTimeRemaining, !wom);
15341 }
15342
15343
15344 /* Timekeeping seems to be a portability nightmare.  I think everyone
15345    has ftime(), but I'm really not sure, so I'm including some ifdefs
15346    to use other calls if you don't.  Clocks will be less accurate if
15347    you have neither ftime nor gettimeofday.
15348 */
15349
15350 /* VS 2008 requires the #include outside of the function */
15351 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15352 #include <sys/timeb.h>
15353 #endif
15354
15355 /* Get the current time as a TimeMark */
15356 void
15357 GetTimeMark(tm)
15358      TimeMark *tm;
15359 {
15360 #if HAVE_GETTIMEOFDAY
15361
15362     struct timeval timeVal;
15363     struct timezone timeZone;
15364
15365     gettimeofday(&timeVal, &timeZone);
15366     tm->sec = (long) timeVal.tv_sec;
15367     tm->ms = (int) (timeVal.tv_usec / 1000L);
15368
15369 #else /*!HAVE_GETTIMEOFDAY*/
15370 #if HAVE_FTIME
15371
15372 // include <sys/timeb.h> / moved to just above start of function
15373     struct timeb timeB;
15374
15375     ftime(&timeB);
15376     tm->sec = (long) timeB.time;
15377     tm->ms = (int) timeB.millitm;
15378
15379 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15380     tm->sec = (long) time(NULL);
15381     tm->ms = 0;
15382 #endif
15383 #endif
15384 }
15385
15386 /* Return the difference in milliseconds between two
15387    time marks.  We assume the difference will fit in a long!
15388 */
15389 long
15390 SubtractTimeMarks(tm2, tm1)
15391      TimeMark *tm2, *tm1;
15392 {
15393     return 1000L*(tm2->sec - tm1->sec) +
15394            (long) (tm2->ms - tm1->ms);
15395 }
15396
15397
15398 /*
15399  * Code to manage the game clocks.
15400  *
15401  * In tournament play, black starts the clock and then white makes a move.
15402  * We give the human user a slight advantage if he is playing white---the
15403  * clocks don't run until he makes his first move, so it takes zero time.
15404  * Also, we don't account for network lag, so we could get out of sync
15405  * with GNU Chess's clock -- but then, referees are always right.
15406  */
15407
15408 static TimeMark tickStartTM;
15409 static long intendedTickLength;
15410
15411 long
15412 NextTickLength(timeRemaining)
15413      long timeRemaining;
15414 {
15415     long nominalTickLength, nextTickLength;
15416
15417     if (timeRemaining > 0L && timeRemaining <= 10000L)
15418       nominalTickLength = 100L;
15419     else
15420       nominalTickLength = 1000L;
15421     nextTickLength = timeRemaining % nominalTickLength;
15422     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15423
15424     return nextTickLength;
15425 }
15426
15427 /* Adjust clock one minute up or down */
15428 void
15429 AdjustClock(Boolean which, int dir)
15430 {
15431     if(which) blackTimeRemaining += 60000*dir;
15432     else      whiteTimeRemaining += 60000*dir;
15433     DisplayBothClocks();
15434 }
15435
15436 /* Stop clocks and reset to a fresh time control */
15437 void
15438 ResetClocks()
15439 {
15440     (void) StopClockTimer();
15441     if (appData.icsActive) {
15442         whiteTimeRemaining = blackTimeRemaining = 0;
15443     } else if (searchTime) {
15444         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15445         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15446     } else { /* [HGM] correct new time quote for time odds */
15447         whiteTC = blackTC = fullTimeControlString;
15448         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15449         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15450     }
15451     if (whiteFlag || blackFlag) {
15452         DisplayTitle("");
15453         whiteFlag = blackFlag = FALSE;
15454     }
15455     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15456     DisplayBothClocks();
15457 }
15458
15459 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15460
15461 /* Decrement running clock by amount of time that has passed */
15462 void
15463 DecrementClocks()
15464 {
15465     long timeRemaining;
15466     long lastTickLength, fudge;
15467     TimeMark now;
15468
15469     if (!appData.clockMode) return;
15470     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15471
15472     GetTimeMark(&now);
15473
15474     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15475
15476     /* Fudge if we woke up a little too soon */
15477     fudge = intendedTickLength - lastTickLength;
15478     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15479
15480     if (WhiteOnMove(forwardMostMove)) {
15481         if(whiteNPS >= 0) lastTickLength = 0;
15482         timeRemaining = whiteTimeRemaining -= lastTickLength;
15483         if(timeRemaining < 0 && !appData.icsActive) {
15484             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15485             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15486                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15487                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15488             }
15489         }
15490         DisplayWhiteClock(whiteTimeRemaining - fudge,
15491                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15492     } else {
15493         if(blackNPS >= 0) lastTickLength = 0;
15494         timeRemaining = blackTimeRemaining -= lastTickLength;
15495         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15496             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15497             if(suddenDeath) {
15498                 blackStartMove = forwardMostMove;
15499                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15500             }
15501         }
15502         DisplayBlackClock(blackTimeRemaining - fudge,
15503                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15504     }
15505     if (CheckFlags()) return;
15506
15507     tickStartTM = now;
15508     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15509     StartClockTimer(intendedTickLength);
15510
15511     /* if the time remaining has fallen below the alarm threshold, sound the
15512      * alarm. if the alarm has sounded and (due to a takeback or time control
15513      * with increment) the time remaining has increased to a level above the
15514      * threshold, reset the alarm so it can sound again.
15515      */
15516
15517     if (appData.icsActive && appData.icsAlarm) {
15518
15519         /* make sure we are dealing with the user's clock */
15520         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15521                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15522            )) return;
15523
15524         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15525             alarmSounded = FALSE;
15526         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15527             PlayAlarmSound();
15528             alarmSounded = TRUE;
15529         }
15530     }
15531 }
15532
15533
15534 /* A player has just moved, so stop the previously running
15535    clock and (if in clock mode) start the other one.
15536    We redisplay both clocks in case we're in ICS mode, because
15537    ICS gives us an update to both clocks after every move.
15538    Note that this routine is called *after* forwardMostMove
15539    is updated, so the last fractional tick must be subtracted
15540    from the color that is *not* on move now.
15541 */
15542 void
15543 SwitchClocks(int newMoveNr)
15544 {
15545     long lastTickLength;
15546     TimeMark now;
15547     int flagged = FALSE;
15548
15549     GetTimeMark(&now);
15550
15551     if (StopClockTimer() && appData.clockMode) {
15552         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15553         if (!WhiteOnMove(forwardMostMove)) {
15554             if(blackNPS >= 0) lastTickLength = 0;
15555             blackTimeRemaining -= lastTickLength;
15556            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15557 //         if(pvInfoList[forwardMostMove].time == -1)
15558                  pvInfoList[forwardMostMove].time =               // use GUI time
15559                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15560         } else {
15561            if(whiteNPS >= 0) lastTickLength = 0;
15562            whiteTimeRemaining -= lastTickLength;
15563            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15564 //         if(pvInfoList[forwardMostMove].time == -1)
15565                  pvInfoList[forwardMostMove].time =
15566                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15567         }
15568         flagged = CheckFlags();
15569     }
15570     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15571     CheckTimeControl();
15572
15573     if (flagged || !appData.clockMode) return;
15574
15575     switch (gameMode) {
15576       case MachinePlaysBlack:
15577       case MachinePlaysWhite:
15578       case BeginningOfGame:
15579         if (pausing) return;
15580         break;
15581
15582       case EditGame:
15583       case PlayFromGameFile:
15584       case IcsExamining:
15585         return;
15586
15587       default:
15588         break;
15589     }
15590
15591     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15592         if(WhiteOnMove(forwardMostMove))
15593              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15594         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15595     }
15596
15597     tickStartTM = now;
15598     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15599       whiteTimeRemaining : blackTimeRemaining);
15600     StartClockTimer(intendedTickLength);
15601 }
15602
15603
15604 /* Stop both clocks */
15605 void
15606 StopClocks()
15607 {
15608     long lastTickLength;
15609     TimeMark now;
15610
15611     if (!StopClockTimer()) return;
15612     if (!appData.clockMode) return;
15613
15614     GetTimeMark(&now);
15615
15616     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15617     if (WhiteOnMove(forwardMostMove)) {
15618         if(whiteNPS >= 0) lastTickLength = 0;
15619         whiteTimeRemaining -= lastTickLength;
15620         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15621     } else {
15622         if(blackNPS >= 0) lastTickLength = 0;
15623         blackTimeRemaining -= lastTickLength;
15624         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15625     }
15626     CheckFlags();
15627 }
15628
15629 /* Start clock of player on move.  Time may have been reset, so
15630    if clock is already running, stop and restart it. */
15631 void
15632 StartClocks()
15633 {
15634     (void) StopClockTimer(); /* in case it was running already */
15635     DisplayBothClocks();
15636     if (CheckFlags()) return;
15637
15638     if (!appData.clockMode) return;
15639     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15640
15641     GetTimeMark(&tickStartTM);
15642     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15643       whiteTimeRemaining : blackTimeRemaining);
15644
15645    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15646     whiteNPS = blackNPS = -1;
15647     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15648        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15649         whiteNPS = first.nps;
15650     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15651        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15652         blackNPS = first.nps;
15653     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15654         whiteNPS = second.nps;
15655     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15656         blackNPS = second.nps;
15657     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15658
15659     StartClockTimer(intendedTickLength);
15660 }
15661
15662 char *
15663 TimeString(ms)
15664      long ms;
15665 {
15666     long second, minute, hour, day;
15667     char *sign = "";
15668     static char buf[32];
15669
15670     if (ms > 0 && ms <= 9900) {
15671       /* convert milliseconds to tenths, rounding up */
15672       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15673
15674       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15675       return buf;
15676     }
15677
15678     /* convert milliseconds to seconds, rounding up */
15679     /* use floating point to avoid strangeness of integer division
15680        with negative dividends on many machines */
15681     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15682
15683     if (second < 0) {
15684         sign = "-";
15685         second = -second;
15686     }
15687
15688     day = second / (60 * 60 * 24);
15689     second = second % (60 * 60 * 24);
15690     hour = second / (60 * 60);
15691     second = second % (60 * 60);
15692     minute = second / 60;
15693     second = second % 60;
15694
15695     if (day > 0)
15696       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15697               sign, day, hour, minute, second);
15698     else if (hour > 0)
15699       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15700     else
15701       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15702
15703     return buf;
15704 }
15705
15706
15707 /*
15708  * This is necessary because some C libraries aren't ANSI C compliant yet.
15709  */
15710 char *
15711 StrStr(string, match)
15712      char *string, *match;
15713 {
15714     int i, length;
15715
15716     length = strlen(match);
15717
15718     for (i = strlen(string) - length; i >= 0; i--, string++)
15719       if (!strncmp(match, string, length))
15720         return string;
15721
15722     return NULL;
15723 }
15724
15725 char *
15726 StrCaseStr(string, match)
15727      char *string, *match;
15728 {
15729     int i, j, length;
15730
15731     length = strlen(match);
15732
15733     for (i = strlen(string) - length; i >= 0; i--, string++) {
15734         for (j = 0; j < length; j++) {
15735             if (ToLower(match[j]) != ToLower(string[j]))
15736               break;
15737         }
15738         if (j == length) return string;
15739     }
15740
15741     return NULL;
15742 }
15743
15744 #ifndef _amigados
15745 int
15746 StrCaseCmp(s1, s2)
15747      char *s1, *s2;
15748 {
15749     char c1, c2;
15750
15751     for (;;) {
15752         c1 = ToLower(*s1++);
15753         c2 = ToLower(*s2++);
15754         if (c1 > c2) return 1;
15755         if (c1 < c2) return -1;
15756         if (c1 == NULLCHAR) return 0;
15757     }
15758 }
15759
15760
15761 int
15762 ToLower(c)
15763      int c;
15764 {
15765     return isupper(c) ? tolower(c) : c;
15766 }
15767
15768
15769 int
15770 ToUpper(c)
15771      int c;
15772 {
15773     return islower(c) ? toupper(c) : c;
15774 }
15775 #endif /* !_amigados    */
15776
15777 char *
15778 StrSave(s)
15779      char *s;
15780 {
15781   char *ret;
15782
15783   if ((ret = (char *) malloc(strlen(s) + 1)))
15784     {
15785       safeStrCpy(ret, s, strlen(s)+1);
15786     }
15787   return ret;
15788 }
15789
15790 char *
15791 StrSavePtr(s, savePtr)
15792      char *s, **savePtr;
15793 {
15794     if (*savePtr) {
15795         free(*savePtr);
15796     }
15797     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15798       safeStrCpy(*savePtr, s, strlen(s)+1);
15799     }
15800     return(*savePtr);
15801 }
15802
15803 char *
15804 PGNDate()
15805 {
15806     time_t clock;
15807     struct tm *tm;
15808     char buf[MSG_SIZ];
15809
15810     clock = time((time_t *)NULL);
15811     tm = localtime(&clock);
15812     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15813             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15814     return StrSave(buf);
15815 }
15816
15817
15818 char *
15819 PositionToFEN(move, overrideCastling)
15820      int move;
15821      char *overrideCastling;
15822 {
15823     int i, j, fromX, fromY, toX, toY;
15824     int whiteToPlay;
15825     char buf[MSG_SIZ];
15826     char *p, *q;
15827     int emptycount;
15828     ChessSquare piece;
15829
15830     whiteToPlay = (gameMode == EditPosition) ?
15831       !blackPlaysFirst : (move % 2 == 0);
15832     p = buf;
15833
15834     /* Piece placement data */
15835     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15836         if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
15837         emptycount = 0;
15838         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15839             if (boards[move][i][j] == EmptySquare) {
15840                 emptycount++;
15841             } else { ChessSquare piece = boards[move][i][j];
15842                 if (emptycount > 0) {
15843                     if(emptycount<10) /* [HGM] can be >= 10 */
15844                         *p++ = '0' + emptycount;
15845                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15846                     emptycount = 0;
15847                 }
15848                 if(PieceToChar(piece) == '+') {
15849                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15850                     *p++ = '+';
15851                     piece = (ChessSquare)(DEMOTED piece);
15852                 }
15853                 *p++ = PieceToChar(piece);
15854                 if(p[-1] == '~') {
15855                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15856                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15857                     *p++ = '~';
15858                 }
15859             }
15860         }
15861         if (emptycount > 0) {
15862             if(emptycount<10) /* [HGM] can be >= 10 */
15863                 *p++ = '0' + emptycount;
15864             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15865             emptycount = 0;
15866         }
15867         *p++ = '/';
15868     }
15869     *(p - 1) = ' ';
15870
15871     /* [HGM] print Crazyhouse or Shogi holdings */
15872     if( gameInfo.holdingsWidth ) {
15873         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15874         q = p;
15875         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15876             piece = boards[move][i][BOARD_WIDTH-1];
15877             if( piece != EmptySquare )
15878               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15879                   *p++ = PieceToChar(piece);
15880         }
15881         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15882             piece = boards[move][BOARD_HEIGHT-i-1][0];
15883             if( piece != EmptySquare )
15884               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15885                   *p++ = PieceToChar(piece);
15886         }
15887
15888         if( q == p ) *p++ = '-';
15889         *p++ = ']';
15890         *p++ = ' ';
15891     }
15892
15893     /* Active color */
15894     *p++ = whiteToPlay ? 'w' : 'b';
15895     *p++ = ' ';
15896
15897   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15898     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15899   } else {
15900   if(nrCastlingRights) {
15901      q = p;
15902      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15903        /* [HGM] write directly from rights */
15904            if(boards[move][CASTLING][2] != NoRights &&
15905               boards[move][CASTLING][0] != NoRights   )
15906                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15907            if(boards[move][CASTLING][2] != NoRights &&
15908               boards[move][CASTLING][1] != NoRights   )
15909                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15910            if(boards[move][CASTLING][5] != NoRights &&
15911               boards[move][CASTLING][3] != NoRights   )
15912                 *p++ = boards[move][CASTLING][3] + AAA;
15913            if(boards[move][CASTLING][5] != NoRights &&
15914               boards[move][CASTLING][4] != NoRights   )
15915                 *p++ = boards[move][CASTLING][4] + AAA;
15916      } else {
15917
15918         /* [HGM] write true castling rights */
15919         if( nrCastlingRights == 6 ) {
15920             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15921                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15922             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15923                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15924             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15925                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15926             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15927                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15928         }
15929      }
15930      if (q == p) *p++ = '-'; /* No castling rights */
15931      *p++ = ' ';
15932   }
15933
15934   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15935      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15936     /* En passant target square */
15937     if (move > backwardMostMove) {
15938         fromX = moveList[move - 1][0] - AAA;
15939         fromY = moveList[move - 1][1] - ONE;
15940         toX = moveList[move - 1][2] - AAA;
15941         toY = moveList[move - 1][3] - ONE;
15942         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15943             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15944             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15945             fromX == toX) {
15946             /* 2-square pawn move just happened */
15947             *p++ = toX + AAA;
15948             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15949         } else {
15950             *p++ = '-';
15951         }
15952     } else if(move == backwardMostMove) {
15953         // [HGM] perhaps we should always do it like this, and forget the above?
15954         if((signed char)boards[move][EP_STATUS] >= 0) {
15955             *p++ = boards[move][EP_STATUS] + AAA;
15956             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15957         } else {
15958             *p++ = '-';
15959         }
15960     } else {
15961         *p++ = '-';
15962     }
15963     *p++ = ' ';
15964   }
15965   }
15966
15967     /* [HGM] find reversible plies */
15968     {   int i = 0, j=move;
15969
15970         if (appData.debugMode) { int k;
15971             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15972             for(k=backwardMostMove; k<=forwardMostMove; k++)
15973                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15974
15975         }
15976
15977         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15978         if( j == backwardMostMove ) i += initialRulePlies;
15979         sprintf(p, "%d ", i);
15980         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15981     }
15982     /* Fullmove number */
15983     sprintf(p, "%d", (move / 2) + 1);
15984
15985     return StrSave(buf);
15986 }
15987
15988 Boolean
15989 ParseFEN(board, blackPlaysFirst, fen)
15990     Board board;
15991      int *blackPlaysFirst;
15992      char *fen;
15993 {
15994     int i, j;
15995     char *p, c;
15996     int emptycount;
15997     ChessSquare piece;
15998
15999     p = fen;
16000
16001     /* [HGM] by default clear Crazyhouse holdings, if present */
16002     if(gameInfo.holdingsWidth) {
16003        for(i=0; i<BOARD_HEIGHT; i++) {
16004            board[i][0]             = EmptySquare; /* black holdings */
16005            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16006            board[i][1]             = (ChessSquare) 0; /* black counts */
16007            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16008        }
16009     }
16010
16011     /* Piece placement data */
16012     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16013         j = 0;
16014         for (;;) {
16015             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16016                 if (*p == '/') p++;
16017                 emptycount = gameInfo.boardWidth - j;
16018                 while (emptycount--)
16019                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16020                 break;
16021 #if(BOARD_FILES >= 10)
16022             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16023                 p++; emptycount=10;
16024                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16025                 while (emptycount--)
16026                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16027 #endif
16028             } else if (isdigit(*p)) {
16029                 emptycount = *p++ - '0';
16030                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16031                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16032                 while (emptycount--)
16033                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16034             } else if (*p == '+' || isalpha(*p)) {
16035                 if (j >= gameInfo.boardWidth) return FALSE;
16036                 if(*p=='+') {
16037                     piece = CharToPiece(*++p);
16038                     if(piece == EmptySquare) return FALSE; /* unknown piece */
16039                     piece = (ChessSquare) (PROMOTED piece ); p++;
16040                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16041                 } else piece = CharToPiece(*p++);
16042
16043                 if(piece==EmptySquare) return FALSE; /* unknown piece */
16044                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16045                     piece = (ChessSquare) (PROMOTED piece);
16046                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16047                     p++;
16048                 }
16049                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16050             } else {
16051                 return FALSE;
16052             }
16053         }
16054     }
16055     while (*p == '/' || *p == ' ') p++;
16056
16057     /* [HGM] look for Crazyhouse holdings here */
16058     while(*p==' ') p++;
16059     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16060         if(*p == '[') p++;
16061         if(*p == '-' ) p++; /* empty holdings */ else {
16062             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16063             /* if we would allow FEN reading to set board size, we would   */
16064             /* have to add holdings and shift the board read so far here   */
16065             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16066                 p++;
16067                 if((int) piece >= (int) BlackPawn ) {
16068                     i = (int)piece - (int)BlackPawn;
16069                     i = PieceToNumber((ChessSquare)i);
16070                     if( i >= gameInfo.holdingsSize ) return FALSE;
16071                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16072                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16073                 } else {
16074                     i = (int)piece - (int)WhitePawn;
16075                     i = PieceToNumber((ChessSquare)i);
16076                     if( i >= gameInfo.holdingsSize ) return FALSE;
16077                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16078                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16079                 }
16080             }
16081         }
16082         if(*p == ']') p++;
16083     }
16084
16085     while(*p == ' ') p++;
16086
16087     /* Active color */
16088     c = *p++;
16089     if(appData.colorNickNames) {
16090       if( c == appData.colorNickNames[0] ) c = 'w'; else
16091       if( c == appData.colorNickNames[1] ) c = 'b';
16092     }
16093     switch (c) {
16094       case 'w':
16095         *blackPlaysFirst = FALSE;
16096         break;
16097       case 'b':
16098         *blackPlaysFirst = TRUE;
16099         break;
16100       default:
16101         return FALSE;
16102     }
16103
16104     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16105     /* return the extra info in global variiables             */
16106
16107     /* set defaults in case FEN is incomplete */
16108     board[EP_STATUS] = EP_UNKNOWN;
16109     for(i=0; i<nrCastlingRights; i++ ) {
16110         board[CASTLING][i] =
16111             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16112     }   /* assume possible unless obviously impossible */
16113     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16114     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16115     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16116                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16117     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16118     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16119     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16120                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16121     FENrulePlies = 0;
16122
16123     while(*p==' ') p++;
16124     if(nrCastlingRights) {
16125       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16126           /* castling indicator present, so default becomes no castlings */
16127           for(i=0; i<nrCastlingRights; i++ ) {
16128                  board[CASTLING][i] = NoRights;
16129           }
16130       }
16131       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16132              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16133              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16134              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16135         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16136
16137         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16138             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16139             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16140         }
16141         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16142             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16143         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16144                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16145         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16146                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16147         switch(c) {
16148           case'K':
16149               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16150               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16151               board[CASTLING][2] = whiteKingFile;
16152               break;
16153           case'Q':
16154               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16155               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16156               board[CASTLING][2] = whiteKingFile;
16157               break;
16158           case'k':
16159               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16160               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16161               board[CASTLING][5] = blackKingFile;
16162               break;
16163           case'q':
16164               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16165               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16166               board[CASTLING][5] = blackKingFile;
16167           case '-':
16168               break;
16169           default: /* FRC castlings */
16170               if(c >= 'a') { /* black rights */
16171                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16172                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16173                   if(i == BOARD_RGHT) break;
16174                   board[CASTLING][5] = i;
16175                   c -= AAA;
16176                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16177                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16178                   if(c > i)
16179                       board[CASTLING][3] = c;
16180                   else
16181                       board[CASTLING][4] = c;
16182               } else { /* white rights */
16183                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16184                     if(board[0][i] == WhiteKing) break;
16185                   if(i == BOARD_RGHT) break;
16186                   board[CASTLING][2] = i;
16187                   c -= AAA - 'a' + 'A';
16188                   if(board[0][c] >= WhiteKing) break;
16189                   if(c > i)
16190                       board[CASTLING][0] = c;
16191                   else
16192                       board[CASTLING][1] = c;
16193               }
16194         }
16195       }
16196       for(i=0; i<nrCastlingRights; i++)
16197         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16198     if (appData.debugMode) {
16199         fprintf(debugFP, "FEN castling rights:");
16200         for(i=0; i<nrCastlingRights; i++)
16201         fprintf(debugFP, " %d", board[CASTLING][i]);
16202         fprintf(debugFP, "\n");
16203     }
16204
16205       while(*p==' ') p++;
16206     }
16207
16208     /* read e.p. field in games that know e.p. capture */
16209     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16210        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16211       if(*p=='-') {
16212         p++; board[EP_STATUS] = EP_NONE;
16213       } else {
16214          char c = *p++ - AAA;
16215
16216          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16217          if(*p >= '0' && *p <='9') p++;
16218          board[EP_STATUS] = c;
16219       }
16220     }
16221
16222
16223     if(sscanf(p, "%d", &i) == 1) {
16224         FENrulePlies = i; /* 50-move ply counter */
16225         /* (The move number is still ignored)    */
16226     }
16227
16228     return TRUE;
16229 }
16230
16231 void
16232 EditPositionPasteFEN(char *fen)
16233 {
16234   if (fen != NULL) {
16235     Board initial_position;
16236
16237     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16238       DisplayError(_("Bad FEN position in clipboard"), 0);
16239       return ;
16240     } else {
16241       int savedBlackPlaysFirst = blackPlaysFirst;
16242       EditPositionEvent();
16243       blackPlaysFirst = savedBlackPlaysFirst;
16244       CopyBoard(boards[0], initial_position);
16245       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16246       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16247       DisplayBothClocks();
16248       DrawPosition(FALSE, boards[currentMove]);
16249     }
16250   }
16251 }
16252
16253 static char cseq[12] = "\\   ";
16254
16255 Boolean set_cont_sequence(char *new_seq)
16256 {
16257     int len;
16258     Boolean ret;
16259
16260     // handle bad attempts to set the sequence
16261         if (!new_seq)
16262                 return 0; // acceptable error - no debug
16263
16264     len = strlen(new_seq);
16265     ret = (len > 0) && (len < sizeof(cseq));
16266     if (ret)
16267       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16268     else if (appData.debugMode)
16269       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16270     return ret;
16271 }
16272
16273 /*
16274     reformat a source message so words don't cross the width boundary.  internal
16275     newlines are not removed.  returns the wrapped size (no null character unless
16276     included in source message).  If dest is NULL, only calculate the size required
16277     for the dest buffer.  lp argument indicats line position upon entry, and it's
16278     passed back upon exit.
16279 */
16280 int wrap(char *dest, char *src, int count, int width, int *lp)
16281 {
16282     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16283
16284     cseq_len = strlen(cseq);
16285     old_line = line = *lp;
16286     ansi = len = clen = 0;
16287
16288     for (i=0; i < count; i++)
16289     {
16290         if (src[i] == '\033')
16291             ansi = 1;
16292
16293         // if we hit the width, back up
16294         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16295         {
16296             // store i & len in case the word is too long
16297             old_i = i, old_len = len;
16298
16299             // find the end of the last word
16300             while (i && src[i] != ' ' && src[i] != '\n')
16301             {
16302                 i--;
16303                 len--;
16304             }
16305
16306             // word too long?  restore i & len before splitting it
16307             if ((old_i-i+clen) >= width)
16308             {
16309                 i = old_i;
16310                 len = old_len;
16311             }
16312
16313             // extra space?
16314             if (i && src[i-1] == ' ')
16315                 len--;
16316
16317             if (src[i] != ' ' && src[i] != '\n')
16318             {
16319                 i--;
16320                 if (len)
16321                     len--;
16322             }
16323
16324             // now append the newline and continuation sequence
16325             if (dest)
16326                 dest[len] = '\n';
16327             len++;
16328             if (dest)
16329                 strncpy(dest+len, cseq, cseq_len);
16330             len += cseq_len;
16331             line = cseq_len;
16332             clen = cseq_len;
16333             continue;
16334         }
16335
16336         if (dest)
16337             dest[len] = src[i];
16338         len++;
16339         if (!ansi)
16340             line++;
16341         if (src[i] == '\n')
16342             line = 0;
16343         if (src[i] == 'm')
16344             ansi = 0;
16345     }
16346     if (dest && appData.debugMode)
16347     {
16348         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16349             count, width, line, len, *lp);
16350         show_bytes(debugFP, src, count);
16351         fprintf(debugFP, "\ndest: ");
16352         show_bytes(debugFP, dest, len);
16353         fprintf(debugFP, "\n");
16354     }
16355     *lp = dest ? line : old_line;
16356
16357     return len;
16358 }
16359
16360 // [HGM] vari: routines for shelving variations
16361
16362 void
16363 PushInner(int firstMove, int lastMove)
16364 {
16365         int i, j, nrMoves = lastMove - firstMove;
16366
16367         // push current tail of game on stack
16368         savedResult[storedGames] = gameInfo.result;
16369         savedDetails[storedGames] = gameInfo.resultDetails;
16370         gameInfo.resultDetails = NULL;
16371         savedFirst[storedGames] = firstMove;
16372         savedLast [storedGames] = lastMove;
16373         savedFramePtr[storedGames] = framePtr;
16374         framePtr -= nrMoves; // reserve space for the boards
16375         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16376             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16377             for(j=0; j<MOVE_LEN; j++)
16378                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16379             for(j=0; j<2*MOVE_LEN; j++)
16380                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16381             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16382             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16383             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16384             pvInfoList[firstMove+i-1].depth = 0;
16385             commentList[framePtr+i] = commentList[firstMove+i];
16386             commentList[firstMove+i] = NULL;
16387         }
16388
16389         storedGames++;
16390         forwardMostMove = firstMove; // truncate game so we can start variation
16391 }
16392
16393 void
16394 PushTail(int firstMove, int lastMove)
16395 {
16396         if(appData.icsActive) { // only in local mode
16397                 forwardMostMove = currentMove; // mimic old ICS behavior
16398                 return;
16399         }
16400         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16401
16402         PushInner(firstMove, lastMove);
16403         if(storedGames == 1) GreyRevert(FALSE);
16404 }
16405
16406 void
16407 PopInner(Boolean annotate)
16408 {
16409         int i, j, nrMoves;
16410         char buf[8000], moveBuf[20];
16411
16412         storedGames--;
16413         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16414         nrMoves = savedLast[storedGames] - currentMove;
16415         if(annotate) {
16416                 int cnt = 10;
16417                 if(!WhiteOnMove(currentMove))
16418                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16419                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16420                 for(i=currentMove; i<forwardMostMove; i++) {
16421                         if(WhiteOnMove(i))
16422                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16423                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16424                         strcat(buf, moveBuf);
16425                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16426                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16427                 }
16428                 strcat(buf, ")");
16429         }
16430         for(i=1; i<=nrMoves; i++) { // copy last variation back
16431             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16432             for(j=0; j<MOVE_LEN; j++)
16433                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16434             for(j=0; j<2*MOVE_LEN; j++)
16435                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16436             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16437             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16438             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16439             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16440             commentList[currentMove+i] = commentList[framePtr+i];
16441             commentList[framePtr+i] = NULL;
16442         }
16443         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16444         framePtr = savedFramePtr[storedGames];
16445         gameInfo.result = savedResult[storedGames];
16446         if(gameInfo.resultDetails != NULL) {
16447             free(gameInfo.resultDetails);
16448       }
16449         gameInfo.resultDetails = savedDetails[storedGames];
16450         forwardMostMove = currentMove + nrMoves;
16451 }
16452
16453 Boolean
16454 PopTail(Boolean annotate)
16455 {
16456         if(appData.icsActive) return FALSE; // only in local mode
16457         if(!storedGames) return FALSE; // sanity
16458         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16459
16460         PopInner(annotate);
16461
16462         if(storedGames == 0) GreyRevert(TRUE);
16463         return TRUE;
16464 }
16465
16466 void
16467 CleanupTail()
16468 {       // remove all shelved variations
16469         int i;
16470         for(i=0; i<storedGames; i++) {
16471             if(savedDetails[i])
16472                 free(savedDetails[i]);
16473             savedDetails[i] = NULL;
16474         }
16475         for(i=framePtr; i<MAX_MOVES; i++) {
16476                 if(commentList[i]) free(commentList[i]);
16477                 commentList[i] = NULL;
16478         }
16479         framePtr = MAX_MOVES-1;
16480         storedGames = 0;
16481 }
16482
16483 void
16484 LoadVariation(int index, char *text)
16485 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16486         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16487         int level = 0, move;
16488
16489         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16490         // first find outermost bracketing variation
16491         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16492             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16493                 if(*p == '{') wait = '}'; else
16494                 if(*p == '[') wait = ']'; else
16495                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16496                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16497             }
16498             if(*p == wait) wait = NULLCHAR; // closing ]} found
16499             p++;
16500         }
16501         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16502         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16503         end[1] = NULLCHAR; // clip off comment beyond variation
16504         ToNrEvent(currentMove-1);
16505         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16506         // kludge: use ParsePV() to append variation to game
16507         move = currentMove;
16508         ParsePV(start, TRUE, TRUE);
16509         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16510         ClearPremoveHighlights();
16511         CommentPopDown();
16512         ToNrEvent(currentMove+1);
16513 }
16514