Improve quoting of engine name on install
[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
238 #ifdef WIN32
239        extern void ConsoleCreate();
240 #endif
241
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
245
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
253 Boolean abortMatch;
254
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 int endPV = -1;
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
262 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
266 Boolean partnerUp;
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
278 int chattingPartner;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
284
285 /* States for ics_getting_history */
286 #define H_FALSE 0
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
292
293 /* whosays values for GameEnds */
294 #define GE_ICS 0
295 #define GE_ENGINE 1
296 #define GE_PLAYER 2
297 #define GE_FILE 3
298 #define GE_XBOARD 4
299 #define GE_ENGINE1 5
300 #define GE_ENGINE2 6
301
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
304
305 /* Different types of move when calling RegisterMove */
306 #define CMAIL_MOVE   0
307 #define CMAIL_RESIGN 1
308 #define CMAIL_DRAW   2
309 #define CMAIL_ACCEPT 3
310
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
315
316 /* Telnet protocol constants */
317 #define TN_WILL 0373
318 #define TN_WONT 0374
319 #define TN_DO   0375
320 #define TN_DONT 0376
321 #define TN_IAC  0377
322 #define TN_ECHO 0001
323 #define TN_SGA  0003
324 #define TN_PORT 23
325
326 char*
327 safeStrCpy( char *dst, const char *src, size_t count )
328 { // [HGM] made safe
329   int i;
330   assert( dst != NULL );
331   assert( src != NULL );
332   assert( count > 0 );
333
334   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335   if(  i == count && dst[count-1] != NULLCHAR)
336     {
337       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338       if(appData.debugMode)
339       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
340     }
341
342   return dst;
343 }
344
345 /* Some compiler can't cast u64 to double
346  * This function do the job for us:
347
348  * We use the highest bit for cast, this only
349  * works if the highest bit is not
350  * in use (This should not happen)
351  *
352  * We used this for all compiler
353  */
354 double
355 u64ToDouble(u64 value)
356 {
357   double r;
358   u64 tmp = value & u64Const(0x7fffffffffffffff);
359   r = (double)(s64)tmp;
360   if (value & u64Const(0x8000000000000000))
361        r +=  9.2233720368547758080e18; /* 2^63 */
362  return r;
363 }
364
365 /* Fake up flags for now, as we aren't keeping track of castling
366    availability yet. [HGM] Change of logic: the flag now only
367    indicates the type of castlings allowed by the rule of the game.
368    The actual rights themselves are maintained in the array
369    castlingRights, as part of the game history, and are not probed
370    by this function.
371  */
372 int
373 PosFlags(index)
374 {
375   int flags = F_ALL_CASTLE_OK;
376   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377   switch (gameInfo.variant) {
378   case VariantSuicide:
379     flags &= ~F_ALL_CASTLE_OK;
380   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381     flags |= F_IGNORE_CHECK;
382   case VariantLosers:
383     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
384     break;
385   case VariantAtomic:
386     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387     break;
388   case VariantKriegspiel:
389     flags |= F_KRIEGSPIEL_CAPTURE;
390     break;
391   case VariantCapaRandom:
392   case VariantFischeRandom:
393     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394   case VariantNoCastle:
395   case VariantShatranj:
396   case VariantCourier:
397   case VariantMakruk:
398     flags &= ~F_ALL_CASTLE_OK;
399     break;
400   default:
401     break;
402   }
403   return flags;
404 }
405
406 FILE *gameFileFP, *debugFP;
407
408 /*
409     [AS] Note: sometimes, the sscanf() function is used to parse the input
410     into a fixed-size buffer. Because of this, we must be prepared to
411     receive strings as long as the size of the input buffer, which is currently
412     set to 4K for Windows and 8K for the rest.
413     So, we must either allocate sufficiently large buffers here, or
414     reduce the size of the input buffer in the input reading part.
415 */
416
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
420
421 ChessProgramState first, second, pairing;
422
423 /* premove variables */
424 int premoveToX = 0;
425 int premoveToY = 0;
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
429 int gotPremove = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
432
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
435
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
463
464 int have_sent_ICS_logon = 0;
465 int movesPerSession;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
476
477 /* animateTraining preserves the state of appData.animate
478  * when Training mode is activated. This allows the
479  * response to be animated when appData.animate == TRUE and
480  * appData.animateDragging == TRUE.
481  */
482 Boolean animateTraining;
483
484 GameInfo gameInfo;
485
486 AppData appData;
487
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char  initialRights[BOARD_FILES];
492 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int   initialRulePlies, FENrulePlies;
494 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int loadFlag = 0;
496 int shuffleOpenings;
497 int mute; // mute all sounds
498
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int storedGames = 0;
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
508
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
514
515 ChessSquare  FIDEArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519         BlackKing, BlackBishop, BlackKnight, BlackRook }
520 };
521
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526         BlackKing, BlackKing, BlackKnight, BlackRook }
527 };
528
529 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
530     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532     { BlackRook, BlackMan, BlackBishop, BlackQueen,
533         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 };
535
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 };
542
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 };
549
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 };
556
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560     { BlackRook, BlackKnight, BlackMan, BlackFerz,
561         BlackKing, BlackMan, BlackKnight, BlackRook }
562 };
563
564
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 };
572
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 };
579
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 };
586
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 };
593
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 };
600
601 #ifdef GOTHIC
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 };
608 #else // !GOTHIC
609 #define GothicArray CapablancaArray
610 #endif // !GOTHIC
611
612 #ifdef FALCON
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
618 };
619 #else // !FALCON
620 #define FalconArray CapablancaArray
621 #endif // !FALCON
622
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
629
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 };
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
640
641
642 Board initialPosition;
643
644
645 /* Convert str to a rating. Checks for special cases of "----",
646
647    "++++", etc. Also strips ()'s */
648 int
649 string_to_rating(str)
650   char *str;
651 {
652   while(*str && !isdigit(*str)) ++str;
653   if (!*str)
654     return 0;   /* One of the special "no rating" cases */
655   else
656     return atoi(str);
657 }
658
659 void
660 ClearProgramStats()
661 {
662     /* Init programStats */
663     programStats.movelist[0] = 0;
664     programStats.depth = 0;
665     programStats.nr_moves = 0;
666     programStats.moves_left = 0;
667     programStats.nodes = 0;
668     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
669     programStats.score = 0;
670     programStats.got_only_move = 0;
671     programStats.got_fail = 0;
672     programStats.line_is_book = 0;
673 }
674
675 void
676 CommonEngineInit()
677 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678     if (appData.firstPlaysBlack) {
679         first.twoMachinesColor = "black\n";
680         second.twoMachinesColor = "white\n";
681     } else {
682         first.twoMachinesColor = "white\n";
683         second.twoMachinesColor = "black\n";
684     }
685
686     first.other = &second;
687     second.other = &first;
688
689     { float norm = 1;
690         if(appData.timeOddsMode) {
691             norm = appData.timeOdds[0];
692             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693         }
694         first.timeOdds  = appData.timeOdds[0]/norm;
695         second.timeOdds = appData.timeOdds[1]/norm;
696     }
697
698     if(programVersion) free(programVersion);
699     if (appData.noChessProgram) {
700         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701         sprintf(programVersion, "%s", PACKAGE_STRING);
702     } else {
703       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706     }
707 }
708
709 void
710 UnloadEngine(ChessProgramState *cps)
711 {
712         /* Kill off first chess program */
713         if (cps->isr != NULL)
714           RemoveInputSource(cps->isr);
715         cps->isr = NULL;
716
717         if (cps->pr != NoProc) {
718             ExitAnalyzeMode();
719             DoSleep( appData.delayBeforeQuit );
720             SendToProgram("quit\n", cps);
721             DoSleep( appData.delayAfterQuit );
722             DestroyChildProcess(cps->pr, cps->useSigterm);
723         }
724         cps->pr = NoProc;
725         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 }
727
728 void
729 ClearOptions(ChessProgramState *cps)
730 {
731     int i;
732     cps->nrOptions = cps->comboCnt = 0;
733     for(i=0; i<MAX_OPTIONS; i++) {
734         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735         cps->option[i].textValue = 0;
736     }
737 }
738
739 char *engineNames[] = {
740 "first",
741 "second"
742 };
743
744 void
745 InitEngine(ChessProgramState *cps, int n)
746 {   // [HGM] all engine initialiation put in a function that does one engine
747
748     ClearOptions(cps);
749
750     cps->which = engineNames[n];
751     cps->maybeThinking = FALSE;
752     cps->pr = NoProc;
753     cps->isr = NULL;
754     cps->sendTime = 2;
755     cps->sendDrawOffers = 1;
756
757     cps->program = appData.chessProgram[n];
758     cps->host = appData.host[n];
759     cps->dir = appData.directory[n];
760     cps->initString = appData.engInitString[n];
761     cps->computerString = appData.computerString[n];
762     cps->useSigint  = TRUE;
763     cps->useSigterm = TRUE;
764     cps->reuse = appData.reuse[n];
765     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
766     cps->useSetboard = FALSE;
767     cps->useSAN = FALSE;
768     cps->usePing = FALSE;
769     cps->lastPing = 0;
770     cps->lastPong = 0;
771     cps->usePlayother = FALSE;
772     cps->useColors = TRUE;
773     cps->useUsermove = FALSE;
774     cps->sendICS = FALSE;
775     cps->sendName = appData.icsActive;
776     cps->sdKludge = FALSE;
777     cps->stKludge = FALSE;
778     TidyProgramName(cps->program, cps->host, cps->tidy);
779     cps->matchWins = 0;
780     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781     cps->analysisSupport = 2; /* detect */
782     cps->analyzing = FALSE;
783     cps->initDone = FALSE;
784
785     /* New features added by Tord: */
786     cps->useFEN960 = FALSE;
787     cps->useOOCastle = TRUE;
788     /* End of new features added by Tord. */
789     cps->fenOverride  = appData.fenOverride[n];
790
791     /* [HGM] time odds: set factor for each machine */
792     cps->timeOdds  = appData.timeOdds[n];
793
794     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795     cps->accumulateTC = appData.accumulateTC[n];
796     cps->maxNrOfSessions = 1;
797
798     /* [HGM] debug */
799     cps->debug = FALSE;
800
801     cps->supportsNPS = UNKNOWN;
802     cps->memSize = FALSE;
803     cps->maxCores = FALSE;
804     cps->egtFormats[0] = NULLCHAR;
805
806     /* [HGM] options */
807     cps->optionSettings  = appData.engOptions[n];
808
809     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810     cps->isUCI = appData.isUCI[n]; /* [AS] */
811     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
812
813     if (appData.protocolVersion[n] > PROTOVER
814         || appData.protocolVersion[n] < 1)
815       {
816         char buf[MSG_SIZ];
817         int len;
818
819         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820                        appData.protocolVersion[n]);
821         if( (len > MSG_SIZ) && appData.debugMode )
822           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
823
824         DisplayFatalError(buf, 0, 2);
825       }
826     else
827       {
828         cps->protocolVersion = appData.protocolVersion[n];
829       }
830
831     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
832 }
833
834 ChessProgramState *savCps;
835
836 void
837 LoadEngine()
838 {
839     int i;
840     if(WaitForEngine(savCps, LoadEngine)) return;
841     CommonEngineInit(); // recalculate time odds
842     if(gameInfo.variant != StringToVariant(appData.variant)) {
843         // we changed variant when loading the engine; this forces us to reset
844         Reset(TRUE, savCps != &first);
845         EditGameEvent(); // for consistency with other path, as Reset changes mode
846     }
847     InitChessProgram(savCps, FALSE);
848     SendToProgram("force\n", savCps);
849     DisplayMessage("", "");
850     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852     ThawUI();
853     SetGNUMode();
854 }
855
856 void
857 ReplaceEngine(ChessProgramState *cps, int n)
858 {
859     EditGameEvent();
860     UnloadEngine(cps);
861     appData.noChessProgram = FALSE;
862     appData.clockMode = TRUE;
863     InitEngine(cps, n);
864     if(n) return; // only startup first engine immediately; second can wait
865     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
866     LoadEngine();
867 }
868
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
871
872 static char resetOptions[] = 
873         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
875
876 void
877 Load(ChessProgramState *cps, int i)
878 {
879     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880     if(engineLine[0]) { // an engine was selected from the combo box
881         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884         ParseArgsFromString(buf);
885         SwapEngines(i);
886         ReplaceEngine(cps, i);
887         return;
888     }
889     p = engineName;
890     while(q = strchr(p, SLASH)) p = q+1;
891     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892     if(engineDir[0] != NULLCHAR)
893         appData.directory[i] = engineDir;
894     else if(p != engineName) { // derive directory from engine path, when not given
895         p[-1] = 0;
896         appData.directory[i] = strdup(engineName);
897         p[-1] = SLASH;
898     } else appData.directory[i] = ".";
899     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900     if(params[0]) {
901         snprintf(command, MSG_SIZ, "%s %s", p, params);
902         p = command;
903     }
904     appData.chessProgram[i] = strdup(p);
905     appData.isUCI[i] = isUCI;
906     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907     appData.hasOwnBookUCI[i] = hasBook;
908     if(!nickName[0]) useNick = FALSE;
909     if(useNick) ASSIGN(appData.pgnName[i], nickName);
910     if(addToList) {
911         int len;
912         char quote;
913         q = firstChessProgramNames;
914         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917                         quote, p, quote, appData.directory[i], 
918                         useNick ? " -fn \"" : "",
919                         useNick ? nickName : "",
920                         useNick ? "\"" : "",
921                         v1 ? " -firstProtocolVersion 1" : "",
922                         hasBook ? "" : " -fNoOwnBookUCI",
923                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924                         storeVariant ? " -variant " : "",
925                         storeVariant ? VariantName(gameInfo.variant) : "");
926         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
928         if(q)   free(q);
929     }
930     ReplaceEngine(cps, i);
931 }
932
933 void
934 InitTimeControls()
935 {
936     int matched, min, sec;
937     /*
938      * Parse timeControl resource
939      */
940     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941                           appData.movesPerSession)) {
942         char buf[MSG_SIZ];
943         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944         DisplayFatalError(buf, 0, 2);
945     }
946
947     /*
948      * Parse searchTime resource
949      */
950     if (*appData.searchTime != NULLCHAR) {
951         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952         if (matched == 1) {
953             searchTime = min * 60;
954         } else if (matched == 2) {
955             searchTime = min * 60 + sec;
956         } else {
957             char buf[MSG_SIZ];
958             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959             DisplayFatalError(buf, 0, 2);
960         }
961     }
962 }
963
964 void
965 InitBackEnd1()
966 {
967
968     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970
971     GetTimeMark(&programStartTime);
972     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
974
975     ClearProgramStats();
976     programStats.ok_to_send = 1;
977     programStats.seen_stat = 0;
978
979     /*
980      * Initialize game list
981      */
982     ListNew(&gameList);
983
984
985     /*
986      * Internet chess server status
987      */
988     if (appData.icsActive) {
989         appData.matchMode = FALSE;
990         appData.matchGames = 0;
991 #if ZIPPY
992         appData.noChessProgram = !appData.zippyPlay;
993 #else
994         appData.zippyPlay = FALSE;
995         appData.zippyTalk = FALSE;
996         appData.noChessProgram = TRUE;
997 #endif
998         if (*appData.icsHelper != NULLCHAR) {
999             appData.useTelnet = TRUE;
1000             appData.telnetProgram = appData.icsHelper;
1001         }
1002     } else {
1003         appData.zippyTalk = appData.zippyPlay = FALSE;
1004     }
1005
1006     /* [AS] Initialize pv info list [HGM] and game state */
1007     {
1008         int i, j;
1009
1010         for( i=0; i<=framePtr; i++ ) {
1011             pvInfoList[i].depth = -1;
1012             boards[i][EP_STATUS] = EP_NONE;
1013             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1014         }
1015     }
1016
1017     InitTimeControls();
1018
1019     /* [AS] Adjudication threshold */
1020     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1021
1022     InitEngine(&first, 0);
1023     InitEngine(&second, 1);
1024     CommonEngineInit();
1025
1026     pairing.which = "pairing"; // pairing engine
1027     pairing.pr = NoProc;
1028     pairing.isr = NULL;
1029     pairing.program = appData.pairingEngine;
1030     pairing.host = "localhost";
1031     pairing.dir = ".";
1032
1033     if (appData.icsActive) {
1034         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1035     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036         appData.clockMode = FALSE;
1037         first.sendTime = second.sendTime = 0;
1038     }
1039
1040 #if ZIPPY
1041     /* Override some settings from environment variables, for backward
1042        compatibility.  Unfortunately it's not feasible to have the env
1043        vars just set defaults, at least in xboard.  Ugh.
1044     */
1045     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046       ZippyInit();
1047     }
1048 #endif
1049
1050     if (!appData.icsActive) {
1051       char buf[MSG_SIZ];
1052       int len;
1053
1054       /* Check for variants that are supported only in ICS mode,
1055          or not at all.  Some that are accepted here nevertheless
1056          have bugs; see comments below.
1057       */
1058       VariantClass variant = StringToVariant(appData.variant);
1059       switch (variant) {
1060       case VariantBughouse:     /* need four players and two boards */
1061       case VariantKriegspiel:   /* need to hide pieces and move details */
1062         /* case VariantFischeRandom: (Fabien: moved below) */
1063         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064         if( (len > MSG_SIZ) && appData.debugMode )
1065           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1066
1067         DisplayFatalError(buf, 0, 2);
1068         return;
1069
1070       case VariantUnknown:
1071       case VariantLoadable:
1072       case Variant29:
1073       case Variant30:
1074       case Variant31:
1075       case Variant32:
1076       case Variant33:
1077       case Variant34:
1078       case Variant35:
1079       case Variant36:
1080       default:
1081         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082         if( (len > MSG_SIZ) && appData.debugMode )
1083           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1084
1085         DisplayFatalError(buf, 0, 2);
1086         return;
1087
1088       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1089       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1090       case VariantGothic:     /* [HGM] should work */
1091       case VariantCapablanca: /* [HGM] should work */
1092       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1093       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1094       case VariantKnightmate: /* [HGM] should work */
1095       case VariantCylinder:   /* [HGM] untested */
1096       case VariantFalcon:     /* [HGM] untested */
1097       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098                                  offboard interposition not understood */
1099       case VariantNormal:     /* definitely works! */
1100       case VariantWildCastle: /* pieces not automatically shuffled */
1101       case VariantNoCastle:   /* pieces not automatically shuffled */
1102       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103       case VariantLosers:     /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantSuicide:    /* should work except for win condition,
1106                                  and doesn't know captures are mandatory */
1107       case VariantGiveaway:   /* should work except for win condition,
1108                                  and doesn't know captures are mandatory */
1109       case VariantTwoKings:   /* should work */
1110       case VariantAtomic:     /* should work except for win condition */
1111       case Variant3Check:     /* should work except for win condition */
1112       case VariantShatranj:   /* should work except for all win conditions */
1113       case VariantMakruk:     /* should work except for daw countdown */
1114       case VariantBerolina:   /* might work if TestLegality is off */
1115       case VariantCapaRandom: /* should work */
1116       case VariantJanus:      /* should work */
1117       case VariantSuper:      /* experimental */
1118       case VariantGreat:      /* experimental, requires legality testing to be off */
1119       case VariantSChess:     /* S-Chess, should work */
1120       case VariantSpartan:    /* should work */
1121         break;
1122       }
1123     }
1124
1125 }
1126
1127 int NextIntegerFromString( char ** str, long * value )
1128 {
1129     int result = -1;
1130     char * s = *str;
1131
1132     while( *s == ' ' || *s == '\t' ) {
1133         s++;
1134     }
1135
1136     *value = 0;
1137
1138     if( *s >= '0' && *s <= '9' ) {
1139         while( *s >= '0' && *s <= '9' ) {
1140             *value = *value * 10 + (*s - '0');
1141             s++;
1142         }
1143
1144         result = 0;
1145     }
1146
1147     *str = s;
1148
1149     return result;
1150 }
1151
1152 int NextTimeControlFromString( char ** str, long * value )
1153 {
1154     long temp;
1155     int result = NextIntegerFromString( str, &temp );
1156
1157     if( result == 0 ) {
1158         *value = temp * 60; /* Minutes */
1159         if( **str == ':' ) {
1160             (*str)++;
1161             result = NextIntegerFromString( str, &temp );
1162             *value += temp; /* Seconds */
1163         }
1164     }
1165
1166     return result;
1167 }
1168
1169 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1170 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1171     int result = -1, type = 0; long temp, temp2;
1172
1173     if(**str != ':') return -1; // old params remain in force!
1174     (*str)++;
1175     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1176     if( NextIntegerFromString( str, &temp ) ) return -1;
1177     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1178
1179     if(**str != '/') {
1180         /* time only: incremental or sudden-death time control */
1181         if(**str == '+') { /* increment follows; read it */
1182             (*str)++;
1183             if(**str == '!') type = *(*str)++; // Bronstein TC
1184             if(result = NextIntegerFromString( str, &temp2)) return -1;
1185             *inc = temp2 * 1000;
1186             if(**str == '.') { // read fraction of increment
1187                 char *start = ++(*str);
1188                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1189                 temp2 *= 1000;
1190                 while(start++ < *str) temp2 /= 10;
1191                 *inc += temp2;
1192             }
1193         } else *inc = 0;
1194         *moves = 0; *tc = temp * 1000; *incType = type;
1195         return 0;
1196     }
1197
1198     (*str)++; /* classical time control */
1199     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1200
1201     if(result == 0) {
1202         *moves = temp;
1203         *tc    = temp2 * 1000;
1204         *inc   = 0;
1205         *incType = type;
1206     }
1207     return result;
1208 }
1209
1210 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1211 {   /* [HGM] get time to add from the multi-session time-control string */
1212     int incType, moves=1; /* kludge to force reading of first session */
1213     long time, increment;
1214     char *s = tcString;
1215
1216     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1217     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1218     do {
1219         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1220         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1221         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1222         if(movenr == -1) return time;    /* last move before new session     */
1223         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1224         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1225         if(!moves) return increment;     /* current session is incremental   */
1226         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1227     } while(movenr >= -1);               /* try again for next session       */
1228
1229     return 0; // no new time quota on this move
1230 }
1231
1232 int
1233 ParseTimeControl(tc, ti, mps)
1234      char *tc;
1235      float ti;
1236      int mps;
1237 {
1238   long tc1;
1239   long tc2;
1240   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1241   int min, sec=0;
1242
1243   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1244   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1245       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1246   if(ti > 0) {
1247
1248     if(mps)
1249       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1250     else 
1251       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1252   } else {
1253     if(mps)
1254       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1255     else 
1256       snprintf(buf, MSG_SIZ, ":%s", mytc);
1257   }
1258   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1259   
1260   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1261     return FALSE;
1262   }
1263
1264   if( *tc == '/' ) {
1265     /* Parse second time control */
1266     tc++;
1267
1268     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1269       return FALSE;
1270     }
1271
1272     if( tc2 == 0 ) {
1273       return FALSE;
1274     }
1275
1276     timeControl_2 = tc2 * 1000;
1277   }
1278   else {
1279     timeControl_2 = 0;
1280   }
1281
1282   if( tc1 == 0 ) {
1283     return FALSE;
1284   }
1285
1286   timeControl = tc1 * 1000;
1287
1288   if (ti >= 0) {
1289     timeIncrement = ti * 1000;  /* convert to ms */
1290     movesPerSession = 0;
1291   } else {
1292     timeIncrement = 0;
1293     movesPerSession = mps;
1294   }
1295   return TRUE;
1296 }
1297
1298 void
1299 InitBackEnd2()
1300 {
1301     if (appData.debugMode) {
1302         fprintf(debugFP, "%s\n", programVersion);
1303     }
1304
1305     set_cont_sequence(appData.wrapContSeq);
1306     if (appData.matchGames > 0) {
1307         appData.matchMode = TRUE;
1308     } else if (appData.matchMode) {
1309         appData.matchGames = 1;
1310     }
1311     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1312         appData.matchGames = appData.sameColorGames;
1313     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1314         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1315         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1316     }
1317     Reset(TRUE, FALSE);
1318     if (appData.noChessProgram || first.protocolVersion == 1) {
1319       InitBackEnd3();
1320     } else {
1321       /* kludge: allow timeout for initial "feature" commands */
1322       FreezeUI();
1323       DisplayMessage("", _("Starting chess program"));
1324       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1325     }
1326 }
1327
1328 int
1329 CalculateIndex(int index, int gameNr)
1330 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1331     int res;
1332     if(index > 0) return index; // fixed nmber
1333     if(index == 0) return 1;
1334     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1335     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1336     return res;
1337 }
1338
1339 int
1340 LoadGameOrPosition(int gameNr)
1341 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1342     if (*appData.loadGameFile != NULLCHAR) {
1343         if (!LoadGameFromFile(appData.loadGameFile,
1344                 CalculateIndex(appData.loadGameIndex, gameNr),
1345                               appData.loadGameFile, FALSE)) {
1346             DisplayFatalError(_("Bad game file"), 0, 1);
1347             return 0;
1348         }
1349     } else if (*appData.loadPositionFile != NULLCHAR) {
1350         if (!LoadPositionFromFile(appData.loadPositionFile,
1351                 CalculateIndex(appData.loadPositionIndex, gameNr),
1352                                   appData.loadPositionFile)) {
1353             DisplayFatalError(_("Bad position file"), 0, 1);
1354             return 0;
1355         }
1356     }
1357     return 1;
1358 }
1359
1360 void
1361 ReserveGame(int gameNr, char resChar)
1362 {
1363     FILE *tf = fopen(appData.tourneyFile, "r+");
1364     char *p, *q, c, buf[MSG_SIZ];
1365     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1366     safeStrCpy(buf, lastMsg, MSG_SIZ);
1367     DisplayMessage(_("Pick new game"), "");
1368     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1369     ParseArgsFromFile(tf);
1370     p = q = appData.results;
1371     if(appData.debugMode) {
1372       char *r = appData.participants;
1373       fprintf(debugFP, "results = '%s'\n", p);
1374       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1375       fprintf(debugFP, "\n");
1376     }
1377     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1378     nextGame = q - p;
1379     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1380     safeStrCpy(q, p, strlen(p) + 2);
1381     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1382     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1383     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1384         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1385         q[nextGame] = '*';
1386     }
1387     fseek(tf, -(strlen(p)+4), SEEK_END);
1388     c = fgetc(tf);
1389     if(c != '"') // depending on DOS or Unix line endings we can be one off
1390          fseek(tf, -(strlen(p)+2), SEEK_END);
1391     else fseek(tf, -(strlen(p)+3), SEEK_END);
1392     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1393     DisplayMessage(buf, "");
1394     free(p); appData.results = q;
1395     if(nextGame <= appData.matchGames && resChar != ' ' &&
1396        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1397         UnloadEngine(&first);  // next game belongs to other pairing;
1398         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1399     }
1400 }
1401
1402 void
1403 MatchEvent(int mode)
1404 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1405         int dummy;
1406         if(matchMode) { // already in match mode: switch it off
1407             abortMatch = TRUE;
1408             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1409             ModeHighlight(); // kludgey way to remove checkmark...
1410             return;
1411         }
1412 //      if(gameMode != BeginningOfGame) {
1413 //          DisplayError(_("You can only start a match from the initial position."), 0);
1414 //          return;
1415 //      }
1416         abortMatch = FALSE;
1417         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1418         /* Set up machine vs. machine match */
1419         nextGame = 0;
1420         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1421         if(appData.tourneyFile[0]) {
1422             ReserveGame(-1, 0);
1423             if(nextGame > appData.matchGames) {
1424                 char buf[MSG_SIZ];
1425                 if(strchr(appData.results, '*') == NULL) {
1426                     FILE *f;
1427                     appData.tourneyCycles++;
1428                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1429                         fclose(f);
1430                         NextTourneyGame(-1, &dummy);
1431                         ReserveGame(-1, 0);
1432                         if(nextGame <= appData.matchGames) {
1433                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1434                             matchMode = mode;
1435                             ScheduleDelayedEvent(NextMatchGame, 10000);
1436                             return;
1437                         }
1438                     }
1439                 }
1440                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1441                 DisplayError(buf, 0);
1442                 appData.tourneyFile[0] = 0;
1443                 return;
1444             }
1445         } else
1446         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1447             DisplayFatalError(_("Can't have a match with no chess programs"),
1448                               0, 2);
1449             return;
1450         }
1451         matchMode = mode;
1452         matchGame = roundNr = 1;
1453         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1454         NextMatchGame();
1455 }
1456
1457 void
1458 InitBackEnd3 P((void))
1459 {
1460     GameMode initialMode;
1461     char buf[MSG_SIZ];
1462     int err, len;
1463
1464     InitChessProgram(&first, startedFromSetupPosition);
1465
1466     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1467         free(programVersion);
1468         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1469         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1470     }
1471
1472     if (appData.icsActive) {
1473 #ifdef WIN32
1474         /* [DM] Make a console window if needed [HGM] merged ifs */
1475         ConsoleCreate();
1476 #endif
1477         err = establish();
1478         if (err != 0)
1479           {
1480             if (*appData.icsCommPort != NULLCHAR)
1481               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1482                              appData.icsCommPort);
1483             else
1484               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1485                         appData.icsHost, appData.icsPort);
1486
1487             if( (len > MSG_SIZ) && appData.debugMode )
1488               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1489
1490             DisplayFatalError(buf, err, 1);
1491             return;
1492         }
1493         SetICSMode();
1494         telnetISR =
1495           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1496         fromUserISR =
1497           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1498         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1499             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1500     } else if (appData.noChessProgram) {
1501         SetNCPMode();
1502     } else {
1503         SetGNUMode();
1504     }
1505
1506     if (*appData.cmailGameName != NULLCHAR) {
1507         SetCmailMode();
1508         OpenLoopback(&cmailPR);
1509         cmailISR =
1510           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1511     }
1512
1513     ThawUI();
1514     DisplayMessage("", "");
1515     if (StrCaseCmp(appData.initialMode, "") == 0) {
1516       initialMode = BeginningOfGame;
1517       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1518         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1519         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1520         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1521         ModeHighlight();
1522       }
1523     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1524       initialMode = TwoMachinesPlay;
1525     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1526       initialMode = AnalyzeFile;
1527     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1528       initialMode = AnalyzeMode;
1529     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1530       initialMode = MachinePlaysWhite;
1531     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1532       initialMode = MachinePlaysBlack;
1533     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1534       initialMode = EditGame;
1535     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1536       initialMode = EditPosition;
1537     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1538       initialMode = Training;
1539     } else {
1540       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1541       if( (len > MSG_SIZ) && appData.debugMode )
1542         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1543
1544       DisplayFatalError(buf, 0, 2);
1545       return;
1546     }
1547
1548     if (appData.matchMode) {
1549         if(appData.tourneyFile[0]) { // start tourney from command line
1550             FILE *f;
1551             if(f = fopen(appData.tourneyFile, "r")) {
1552                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1553                 fclose(f);
1554             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1555         }
1556         MatchEvent(TRUE);
1557     } else if (*appData.cmailGameName != NULLCHAR) {
1558         /* Set up cmail mode */
1559         ReloadCmailMsgEvent(TRUE);
1560     } else {
1561         /* Set up other modes */
1562         if (initialMode == AnalyzeFile) {
1563           if (*appData.loadGameFile == NULLCHAR) {
1564             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1565             return;
1566           }
1567         }
1568         if (*appData.loadGameFile != NULLCHAR) {
1569             (void) LoadGameFromFile(appData.loadGameFile,
1570                                     appData.loadGameIndex,
1571                                     appData.loadGameFile, TRUE);
1572         } else if (*appData.loadPositionFile != NULLCHAR) {
1573             (void) LoadPositionFromFile(appData.loadPositionFile,
1574                                         appData.loadPositionIndex,
1575                                         appData.loadPositionFile);
1576             /* [HGM] try to make self-starting even after FEN load */
1577             /* to allow automatic setup of fairy variants with wtm */
1578             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1579                 gameMode = BeginningOfGame;
1580                 setboardSpoiledMachineBlack = 1;
1581             }
1582             /* [HGM] loadPos: make that every new game uses the setup */
1583             /* from file as long as we do not switch variant          */
1584             if(!blackPlaysFirst) {
1585                 startedFromPositionFile = TRUE;
1586                 CopyBoard(filePosition, boards[0]);
1587             }
1588         }
1589         if (initialMode == AnalyzeMode) {
1590           if (appData.noChessProgram) {
1591             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1592             return;
1593           }
1594           if (appData.icsActive) {
1595             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1596             return;
1597           }
1598           AnalyzeModeEvent();
1599         } else if (initialMode == AnalyzeFile) {
1600           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1601           ShowThinkingEvent();
1602           AnalyzeFileEvent();
1603           AnalysisPeriodicEvent(1);
1604         } else if (initialMode == MachinePlaysWhite) {
1605           if (appData.noChessProgram) {
1606             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1607                               0, 2);
1608             return;
1609           }
1610           if (appData.icsActive) {
1611             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1612                               0, 2);
1613             return;
1614           }
1615           MachineWhiteEvent();
1616         } else if (initialMode == MachinePlaysBlack) {
1617           if (appData.noChessProgram) {
1618             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1619                               0, 2);
1620             return;
1621           }
1622           if (appData.icsActive) {
1623             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1624                               0, 2);
1625             return;
1626           }
1627           MachineBlackEvent();
1628         } else if (initialMode == TwoMachinesPlay) {
1629           if (appData.noChessProgram) {
1630             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1631                               0, 2);
1632             return;
1633           }
1634           if (appData.icsActive) {
1635             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1636                               0, 2);
1637             return;
1638           }
1639           TwoMachinesEvent();
1640         } else if (initialMode == EditGame) {
1641           EditGameEvent();
1642         } else if (initialMode == EditPosition) {
1643           EditPositionEvent();
1644         } else if (initialMode == Training) {
1645           if (*appData.loadGameFile == NULLCHAR) {
1646             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1647             return;
1648           }
1649           TrainingEvent();
1650         }
1651     }
1652 }
1653
1654 /*
1655  * Establish will establish a contact to a remote host.port.
1656  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1657  *  used to talk to the host.
1658  * Returns 0 if okay, error code if not.
1659  */
1660 int
1661 establish()
1662 {
1663     char buf[MSG_SIZ];
1664
1665     if (*appData.icsCommPort != NULLCHAR) {
1666         /* Talk to the host through a serial comm port */
1667         return OpenCommPort(appData.icsCommPort, &icsPR);
1668
1669     } else if (*appData.gateway != NULLCHAR) {
1670         if (*appData.remoteShell == NULLCHAR) {
1671             /* Use the rcmd protocol to run telnet program on a gateway host */
1672             snprintf(buf, sizeof(buf), "%s %s %s",
1673                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1674             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1675
1676         } else {
1677             /* Use the rsh program to run telnet program on a gateway host */
1678             if (*appData.remoteUser == NULLCHAR) {
1679                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1680                         appData.gateway, appData.telnetProgram,
1681                         appData.icsHost, appData.icsPort);
1682             } else {
1683                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1684                         appData.remoteShell, appData.gateway,
1685                         appData.remoteUser, appData.telnetProgram,
1686                         appData.icsHost, appData.icsPort);
1687             }
1688             return StartChildProcess(buf, "", &icsPR);
1689
1690         }
1691     } else if (appData.useTelnet) {
1692         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1693
1694     } else {
1695         /* TCP socket interface differs somewhat between
1696            Unix and NT; handle details in the front end.
1697            */
1698         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1699     }
1700 }
1701
1702 void EscapeExpand(char *p, char *q)
1703 {       // [HGM] initstring: routine to shape up string arguments
1704         while(*p++ = *q++) if(p[-1] == '\\')
1705             switch(*q++) {
1706                 case 'n': p[-1] = '\n'; break;
1707                 case 'r': p[-1] = '\r'; break;
1708                 case 't': p[-1] = '\t'; break;
1709                 case '\\': p[-1] = '\\'; break;
1710                 case 0: *p = 0; return;
1711                 default: p[-1] = q[-1]; break;
1712             }
1713 }
1714
1715 void
1716 show_bytes(fp, buf, count)
1717      FILE *fp;
1718      char *buf;
1719      int count;
1720 {
1721     while (count--) {
1722         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1723             fprintf(fp, "\\%03o", *buf & 0xff);
1724         } else {
1725             putc(*buf, fp);
1726         }
1727         buf++;
1728     }
1729     fflush(fp);
1730 }
1731
1732 /* Returns an errno value */
1733 int
1734 OutputMaybeTelnet(pr, message, count, outError)
1735      ProcRef pr;
1736      char *message;
1737      int count;
1738      int *outError;
1739 {
1740     char buf[8192], *p, *q, *buflim;
1741     int left, newcount, outcount;
1742
1743     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1744         *appData.gateway != NULLCHAR) {
1745         if (appData.debugMode) {
1746             fprintf(debugFP, ">ICS: ");
1747             show_bytes(debugFP, message, count);
1748             fprintf(debugFP, "\n");
1749         }
1750         return OutputToProcess(pr, message, count, outError);
1751     }
1752
1753     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1754     p = message;
1755     q = buf;
1756     left = count;
1757     newcount = 0;
1758     while (left) {
1759         if (q >= buflim) {
1760             if (appData.debugMode) {
1761                 fprintf(debugFP, ">ICS: ");
1762                 show_bytes(debugFP, buf, newcount);
1763                 fprintf(debugFP, "\n");
1764             }
1765             outcount = OutputToProcess(pr, buf, newcount, outError);
1766             if (outcount < newcount) return -1; /* to be sure */
1767             q = buf;
1768             newcount = 0;
1769         }
1770         if (*p == '\n') {
1771             *q++ = '\r';
1772             newcount++;
1773         } else if (((unsigned char) *p) == TN_IAC) {
1774             *q++ = (char) TN_IAC;
1775             newcount ++;
1776         }
1777         *q++ = *p++;
1778         newcount++;
1779         left--;
1780     }
1781     if (appData.debugMode) {
1782         fprintf(debugFP, ">ICS: ");
1783         show_bytes(debugFP, buf, newcount);
1784         fprintf(debugFP, "\n");
1785     }
1786     outcount = OutputToProcess(pr, buf, newcount, outError);
1787     if (outcount < newcount) return -1; /* to be sure */
1788     return count;
1789 }
1790
1791 void
1792 read_from_player(isr, closure, message, count, error)
1793      InputSourceRef isr;
1794      VOIDSTAR closure;
1795      char *message;
1796      int count;
1797      int error;
1798 {
1799     int outError, outCount;
1800     static int gotEof = 0;
1801
1802     /* Pass data read from player on to ICS */
1803     if (count > 0) {
1804         gotEof = 0;
1805         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1806         if (outCount < count) {
1807             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1808         }
1809     } else if (count < 0) {
1810         RemoveInputSource(isr);
1811         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1812     } else if (gotEof++ > 0) {
1813         RemoveInputSource(isr);
1814         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1815     }
1816 }
1817
1818 void
1819 KeepAlive()
1820 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1821     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1822     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1823     SendToICS("date\n");
1824     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1825 }
1826
1827 /* added routine for printf style output to ics */
1828 void ics_printf(char *format, ...)
1829 {
1830     char buffer[MSG_SIZ];
1831     va_list args;
1832
1833     va_start(args, format);
1834     vsnprintf(buffer, sizeof(buffer), format, args);
1835     buffer[sizeof(buffer)-1] = '\0';
1836     SendToICS(buffer);
1837     va_end(args);
1838 }
1839
1840 void
1841 SendToICS(s)
1842      char *s;
1843 {
1844     int count, outCount, outError;
1845
1846     if (icsPR == NULL) return;
1847
1848     count = strlen(s);
1849     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1850     if (outCount < count) {
1851         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1852     }
1853 }
1854
1855 /* This is used for sending logon scripts to the ICS. Sending
1856    without a delay causes problems when using timestamp on ICC
1857    (at least on my machine). */
1858 void
1859 SendToICSDelayed(s,msdelay)
1860      char *s;
1861      long msdelay;
1862 {
1863     int count, outCount, outError;
1864
1865     if (icsPR == NULL) return;
1866
1867     count = strlen(s);
1868     if (appData.debugMode) {
1869         fprintf(debugFP, ">ICS: ");
1870         show_bytes(debugFP, s, count);
1871         fprintf(debugFP, "\n");
1872     }
1873     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1874                                       msdelay);
1875     if (outCount < count) {
1876         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1877     }
1878 }
1879
1880
1881 /* Remove all highlighting escape sequences in s
1882    Also deletes any suffix starting with '('
1883    */
1884 char *
1885 StripHighlightAndTitle(s)
1886      char *s;
1887 {
1888     static char retbuf[MSG_SIZ];
1889     char *p = retbuf;
1890
1891     while (*s != NULLCHAR) {
1892         while (*s == '\033') {
1893             while (*s != NULLCHAR && !isalpha(*s)) s++;
1894             if (*s != NULLCHAR) s++;
1895         }
1896         while (*s != NULLCHAR && *s != '\033') {
1897             if (*s == '(' || *s == '[') {
1898                 *p = NULLCHAR;
1899                 return retbuf;
1900             }
1901             *p++ = *s++;
1902         }
1903     }
1904     *p = NULLCHAR;
1905     return retbuf;
1906 }
1907
1908 /* Remove all highlighting escape sequences in s */
1909 char *
1910 StripHighlight(s)
1911      char *s;
1912 {
1913     static char retbuf[MSG_SIZ];
1914     char *p = retbuf;
1915
1916     while (*s != NULLCHAR) {
1917         while (*s == '\033') {
1918             while (*s != NULLCHAR && !isalpha(*s)) s++;
1919             if (*s != NULLCHAR) s++;
1920         }
1921         while (*s != NULLCHAR && *s != '\033') {
1922             *p++ = *s++;
1923         }
1924     }
1925     *p = NULLCHAR;
1926     return retbuf;
1927 }
1928
1929 char *variantNames[] = VARIANT_NAMES;
1930 char *
1931 VariantName(v)
1932      VariantClass v;
1933 {
1934     return variantNames[v];
1935 }
1936
1937
1938 /* Identify a variant from the strings the chess servers use or the
1939    PGN Variant tag names we use. */
1940 VariantClass
1941 StringToVariant(e)
1942      char *e;
1943 {
1944     char *p;
1945     int wnum = -1;
1946     VariantClass v = VariantNormal;
1947     int i, found = FALSE;
1948     char buf[MSG_SIZ];
1949     int len;
1950
1951     if (!e) return v;
1952
1953     /* [HGM] skip over optional board-size prefixes */
1954     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1955         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1956         while( *e++ != '_');
1957     }
1958
1959     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1960         v = VariantNormal;
1961         found = TRUE;
1962     } else
1963     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1964       if (StrCaseStr(e, variantNames[i])) {
1965         v = (VariantClass) i;
1966         found = TRUE;
1967         break;
1968       }
1969     }
1970
1971     if (!found) {
1972       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1973           || StrCaseStr(e, "wild/fr")
1974           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1975         v = VariantFischeRandom;
1976       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1977                  (i = 1, p = StrCaseStr(e, "w"))) {
1978         p += i;
1979         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1980         if (isdigit(*p)) {
1981           wnum = atoi(p);
1982         } else {
1983           wnum = -1;
1984         }
1985         switch (wnum) {
1986         case 0: /* FICS only, actually */
1987         case 1:
1988           /* Castling legal even if K starts on d-file */
1989           v = VariantWildCastle;
1990           break;
1991         case 2:
1992         case 3:
1993         case 4:
1994           /* Castling illegal even if K & R happen to start in
1995              normal positions. */
1996           v = VariantNoCastle;
1997           break;
1998         case 5:
1999         case 7:
2000         case 8:
2001         case 10:
2002         case 11:
2003         case 12:
2004         case 13:
2005         case 14:
2006         case 15:
2007         case 18:
2008         case 19:
2009           /* Castling legal iff K & R start in normal positions */
2010           v = VariantNormal;
2011           break;
2012         case 6:
2013         case 20:
2014         case 21:
2015           /* Special wilds for position setup; unclear what to do here */
2016           v = VariantLoadable;
2017           break;
2018         case 9:
2019           /* Bizarre ICC game */
2020           v = VariantTwoKings;
2021           break;
2022         case 16:
2023           v = VariantKriegspiel;
2024           break;
2025         case 17:
2026           v = VariantLosers;
2027           break;
2028         case 22:
2029           v = VariantFischeRandom;
2030           break;
2031         case 23:
2032           v = VariantCrazyhouse;
2033           break;
2034         case 24:
2035           v = VariantBughouse;
2036           break;
2037         case 25:
2038           v = Variant3Check;
2039           break;
2040         case 26:
2041           /* Not quite the same as FICS suicide! */
2042           v = VariantGiveaway;
2043           break;
2044         case 27:
2045           v = VariantAtomic;
2046           break;
2047         case 28:
2048           v = VariantShatranj;
2049           break;
2050
2051         /* Temporary names for future ICC types.  The name *will* change in
2052            the next xboard/WinBoard release after ICC defines it. */
2053         case 29:
2054           v = Variant29;
2055           break;
2056         case 30:
2057           v = Variant30;
2058           break;
2059         case 31:
2060           v = Variant31;
2061           break;
2062         case 32:
2063           v = Variant32;
2064           break;
2065         case 33:
2066           v = Variant33;
2067           break;
2068         case 34:
2069           v = Variant34;
2070           break;
2071         case 35:
2072           v = Variant35;
2073           break;
2074         case 36:
2075           v = Variant36;
2076           break;
2077         case 37:
2078           v = VariantShogi;
2079           break;
2080         case 38:
2081           v = VariantXiangqi;
2082           break;
2083         case 39:
2084           v = VariantCourier;
2085           break;
2086         case 40:
2087           v = VariantGothic;
2088           break;
2089         case 41:
2090           v = VariantCapablanca;
2091           break;
2092         case 42:
2093           v = VariantKnightmate;
2094           break;
2095         case 43:
2096           v = VariantFairy;
2097           break;
2098         case 44:
2099           v = VariantCylinder;
2100           break;
2101         case 45:
2102           v = VariantFalcon;
2103           break;
2104         case 46:
2105           v = VariantCapaRandom;
2106           break;
2107         case 47:
2108           v = VariantBerolina;
2109           break;
2110         case 48:
2111           v = VariantJanus;
2112           break;
2113         case 49:
2114           v = VariantSuper;
2115           break;
2116         case 50:
2117           v = VariantGreat;
2118           break;
2119         case -1:
2120           /* Found "wild" or "w" in the string but no number;
2121              must assume it's normal chess. */
2122           v = VariantNormal;
2123           break;
2124         default:
2125           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2126           if( (len > MSG_SIZ) && appData.debugMode )
2127             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2128
2129           DisplayError(buf, 0);
2130           v = VariantUnknown;
2131           break;
2132         }
2133       }
2134     }
2135     if (appData.debugMode) {
2136       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2137               e, wnum, VariantName(v));
2138     }
2139     return v;
2140 }
2141
2142 static int leftover_start = 0, leftover_len = 0;
2143 char star_match[STAR_MATCH_N][MSG_SIZ];
2144
2145 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2146    advance *index beyond it, and set leftover_start to the new value of
2147    *index; else return FALSE.  If pattern contains the character '*', it
2148    matches any sequence of characters not containing '\r', '\n', or the
2149    character following the '*' (if any), and the matched sequence(s) are
2150    copied into star_match.
2151    */
2152 int
2153 looking_at(buf, index, pattern)
2154      char *buf;
2155      int *index;
2156      char *pattern;
2157 {
2158     char *bufp = &buf[*index], *patternp = pattern;
2159     int star_count = 0;
2160     char *matchp = star_match[0];
2161
2162     for (;;) {
2163         if (*patternp == NULLCHAR) {
2164             *index = leftover_start = bufp - buf;
2165             *matchp = NULLCHAR;
2166             return TRUE;
2167         }
2168         if (*bufp == NULLCHAR) return FALSE;
2169         if (*patternp == '*') {
2170             if (*bufp == *(patternp + 1)) {
2171                 *matchp = NULLCHAR;
2172                 matchp = star_match[++star_count];
2173                 patternp += 2;
2174                 bufp++;
2175                 continue;
2176             } else if (*bufp == '\n' || *bufp == '\r') {
2177                 patternp++;
2178                 if (*patternp == NULLCHAR)
2179                   continue;
2180                 else
2181                   return FALSE;
2182             } else {
2183                 *matchp++ = *bufp++;
2184                 continue;
2185             }
2186         }
2187         if (*patternp != *bufp) return FALSE;
2188         patternp++;
2189         bufp++;
2190     }
2191 }
2192
2193 void
2194 SendToPlayer(data, length)
2195      char *data;
2196      int length;
2197 {
2198     int error, outCount;
2199     outCount = OutputToProcess(NoProc, data, length, &error);
2200     if (outCount < length) {
2201         DisplayFatalError(_("Error writing to display"), error, 1);
2202     }
2203 }
2204
2205 void
2206 PackHolding(packed, holding)
2207      char packed[];
2208      char *holding;
2209 {
2210     char *p = holding;
2211     char *q = packed;
2212     int runlength = 0;
2213     int curr = 9999;
2214     do {
2215         if (*p == curr) {
2216             runlength++;
2217         } else {
2218             switch (runlength) {
2219               case 0:
2220                 break;
2221               case 1:
2222                 *q++ = curr;
2223                 break;
2224               case 2:
2225                 *q++ = curr;
2226                 *q++ = curr;
2227                 break;
2228               default:
2229                 sprintf(q, "%d", runlength);
2230                 while (*q) q++;
2231                 *q++ = curr;
2232                 break;
2233             }
2234             runlength = 1;
2235             curr = *p;
2236         }
2237     } while (*p++);
2238     *q = NULLCHAR;
2239 }
2240
2241 /* Telnet protocol requests from the front end */
2242 void
2243 TelnetRequest(ddww, option)
2244      unsigned char ddww, option;
2245 {
2246     unsigned char msg[3];
2247     int outCount, outError;
2248
2249     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2250
2251     if (appData.debugMode) {
2252         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2253         switch (ddww) {
2254           case TN_DO:
2255             ddwwStr = "DO";
2256             break;
2257           case TN_DONT:
2258             ddwwStr = "DONT";
2259             break;
2260           case TN_WILL:
2261             ddwwStr = "WILL";
2262             break;
2263           case TN_WONT:
2264             ddwwStr = "WONT";
2265             break;
2266           default:
2267             ddwwStr = buf1;
2268             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2269             break;
2270         }
2271         switch (option) {
2272           case TN_ECHO:
2273             optionStr = "ECHO";
2274             break;
2275           default:
2276             optionStr = buf2;
2277             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2278             break;
2279         }
2280         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2281     }
2282     msg[0] = TN_IAC;
2283     msg[1] = ddww;
2284     msg[2] = option;
2285     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2286     if (outCount < 3) {
2287         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2288     }
2289 }
2290
2291 void
2292 DoEcho()
2293 {
2294     if (!appData.icsActive) return;
2295     TelnetRequest(TN_DO, TN_ECHO);
2296 }
2297
2298 void
2299 DontEcho()
2300 {
2301     if (!appData.icsActive) return;
2302     TelnetRequest(TN_DONT, TN_ECHO);
2303 }
2304
2305 void
2306 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2307 {
2308     /* put the holdings sent to us by the server on the board holdings area */
2309     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2310     char p;
2311     ChessSquare piece;
2312
2313     if(gameInfo.holdingsWidth < 2)  return;
2314     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2315         return; // prevent overwriting by pre-board holdings
2316
2317     if( (int)lowestPiece >= BlackPawn ) {
2318         holdingsColumn = 0;
2319         countsColumn = 1;
2320         holdingsStartRow = BOARD_HEIGHT-1;
2321         direction = -1;
2322     } else {
2323         holdingsColumn = BOARD_WIDTH-1;
2324         countsColumn = BOARD_WIDTH-2;
2325         holdingsStartRow = 0;
2326         direction = 1;
2327     }
2328
2329     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2330         board[i][holdingsColumn] = EmptySquare;
2331         board[i][countsColumn]   = (ChessSquare) 0;
2332     }
2333     while( (p=*holdings++) != NULLCHAR ) {
2334         piece = CharToPiece( ToUpper(p) );
2335         if(piece == EmptySquare) continue;
2336         /*j = (int) piece - (int) WhitePawn;*/
2337         j = PieceToNumber(piece);
2338         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2339         if(j < 0) continue;               /* should not happen */
2340         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2341         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2342         board[holdingsStartRow+j*direction][countsColumn]++;
2343     }
2344 }
2345
2346
2347 void
2348 VariantSwitch(Board board, VariantClass newVariant)
2349 {
2350    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2351    static Board oldBoard;
2352
2353    startedFromPositionFile = FALSE;
2354    if(gameInfo.variant == newVariant) return;
2355
2356    /* [HGM] This routine is called each time an assignment is made to
2357     * gameInfo.variant during a game, to make sure the board sizes
2358     * are set to match the new variant. If that means adding or deleting
2359     * holdings, we shift the playing board accordingly
2360     * This kludge is needed because in ICS observe mode, we get boards
2361     * of an ongoing game without knowing the variant, and learn about the
2362     * latter only later. This can be because of the move list we requested,
2363     * in which case the game history is refilled from the beginning anyway,
2364     * but also when receiving holdings of a crazyhouse game. In the latter
2365     * case we want to add those holdings to the already received position.
2366     */
2367
2368
2369    if (appData.debugMode) {
2370      fprintf(debugFP, "Switch board from %s to %s\n",
2371              VariantName(gameInfo.variant), VariantName(newVariant));
2372      setbuf(debugFP, NULL);
2373    }
2374    shuffleOpenings = 0;       /* [HGM] shuffle */
2375    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2376    switch(newVariant)
2377      {
2378      case VariantShogi:
2379        newWidth = 9;  newHeight = 9;
2380        gameInfo.holdingsSize = 7;
2381      case VariantBughouse:
2382      case VariantCrazyhouse:
2383        newHoldingsWidth = 2; break;
2384      case VariantGreat:
2385        newWidth = 10;
2386      case VariantSuper:
2387        newHoldingsWidth = 2;
2388        gameInfo.holdingsSize = 8;
2389        break;
2390      case VariantGothic:
2391      case VariantCapablanca:
2392      case VariantCapaRandom:
2393        newWidth = 10;
2394      default:
2395        newHoldingsWidth = gameInfo.holdingsSize = 0;
2396      };
2397
2398    if(newWidth  != gameInfo.boardWidth  ||
2399       newHeight != gameInfo.boardHeight ||
2400       newHoldingsWidth != gameInfo.holdingsWidth ) {
2401
2402      /* shift position to new playing area, if needed */
2403      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2404        for(i=0; i<BOARD_HEIGHT; i++)
2405          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2406            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2407              board[i][j];
2408        for(i=0; i<newHeight; i++) {
2409          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2410          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2411        }
2412      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2413        for(i=0; i<BOARD_HEIGHT; i++)
2414          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2415            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2416              board[i][j];
2417      }
2418      gameInfo.boardWidth  = newWidth;
2419      gameInfo.boardHeight = newHeight;
2420      gameInfo.holdingsWidth = newHoldingsWidth;
2421      gameInfo.variant = newVariant;
2422      InitDrawingSizes(-2, 0);
2423    } else gameInfo.variant = newVariant;
2424    CopyBoard(oldBoard, board);   // remember correctly formatted board
2425      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2426    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2427 }
2428
2429 static int loggedOn = FALSE;
2430
2431 /*-- Game start info cache: --*/
2432 int gs_gamenum;
2433 char gs_kind[MSG_SIZ];
2434 static char player1Name[128] = "";
2435 static char player2Name[128] = "";
2436 static char cont_seq[] = "\n\\   ";
2437 static int player1Rating = -1;
2438 static int player2Rating = -1;
2439 /*----------------------------*/
2440
2441 ColorClass curColor = ColorNormal;
2442 int suppressKibitz = 0;
2443
2444 // [HGM] seekgraph
2445 Boolean soughtPending = FALSE;
2446 Boolean seekGraphUp;
2447 #define MAX_SEEK_ADS 200
2448 #define SQUARE 0x80
2449 char *seekAdList[MAX_SEEK_ADS];
2450 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2451 float tcList[MAX_SEEK_ADS];
2452 char colorList[MAX_SEEK_ADS];
2453 int nrOfSeekAds = 0;
2454 int minRating = 1010, maxRating = 2800;
2455 int hMargin = 10, vMargin = 20, h, w;
2456 extern int squareSize, lineGap;
2457
2458 void
2459 PlotSeekAd(int i)
2460 {
2461         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2462         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2463         if(r < minRating+100 && r >=0 ) r = minRating+100;
2464         if(r > maxRating) r = maxRating;
2465         if(tc < 1.) tc = 1.;
2466         if(tc > 95.) tc = 95.;
2467         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2468         y = ((double)r - minRating)/(maxRating - minRating)
2469             * (h-vMargin-squareSize/8-1) + vMargin;
2470         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2471         if(strstr(seekAdList[i], " u ")) color = 1;
2472         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2473            !strstr(seekAdList[i], "bullet") &&
2474            !strstr(seekAdList[i], "blitz") &&
2475            !strstr(seekAdList[i], "standard") ) color = 2;
2476         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2477         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2478 }
2479
2480 void
2481 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2482 {
2483         char buf[MSG_SIZ], *ext = "";
2484         VariantClass v = StringToVariant(type);
2485         if(strstr(type, "wild")) {
2486             ext = type + 4; // append wild number
2487             if(v == VariantFischeRandom) type = "chess960"; else
2488             if(v == VariantLoadable) type = "setup"; else
2489             type = VariantName(v);
2490         }
2491         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2492         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2493             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2494             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2495             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2496             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2497             seekNrList[nrOfSeekAds] = nr;
2498             zList[nrOfSeekAds] = 0;
2499             seekAdList[nrOfSeekAds++] = StrSave(buf);
2500             if(plot) PlotSeekAd(nrOfSeekAds-1);
2501         }
2502 }
2503
2504 void
2505 EraseSeekDot(int i)
2506 {
2507     int x = xList[i], y = yList[i], d=squareSize/4, k;
2508     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2509     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2510     // now replot every dot that overlapped
2511     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2512         int xx = xList[k], yy = yList[k];
2513         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2514             DrawSeekDot(xx, yy, colorList[k]);
2515     }
2516 }
2517
2518 void
2519 RemoveSeekAd(int nr)
2520 {
2521         int i;
2522         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2523             EraseSeekDot(i);
2524             if(seekAdList[i]) free(seekAdList[i]);
2525             seekAdList[i] = seekAdList[--nrOfSeekAds];
2526             seekNrList[i] = seekNrList[nrOfSeekAds];
2527             ratingList[i] = ratingList[nrOfSeekAds];
2528             colorList[i]  = colorList[nrOfSeekAds];
2529             tcList[i] = tcList[nrOfSeekAds];
2530             xList[i]  = xList[nrOfSeekAds];
2531             yList[i]  = yList[nrOfSeekAds];
2532             zList[i]  = zList[nrOfSeekAds];
2533             seekAdList[nrOfSeekAds] = NULL;
2534             break;
2535         }
2536 }
2537
2538 Boolean
2539 MatchSoughtLine(char *line)
2540 {
2541     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2542     int nr, base, inc, u=0; char dummy;
2543
2544     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2545        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2546        (u=1) &&
2547        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2548         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2549         // match: compact and save the line
2550         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2551         return TRUE;
2552     }
2553     return FALSE;
2554 }
2555
2556 int
2557 DrawSeekGraph()
2558 {
2559     int i;
2560     if(!seekGraphUp) return FALSE;
2561     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2562     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2563
2564     DrawSeekBackground(0, 0, w, h);
2565     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2566     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2567     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2568         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2569         yy = h-1-yy;
2570         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2571         if(i%500 == 0) {
2572             char buf[MSG_SIZ];
2573             snprintf(buf, MSG_SIZ, "%d", i);
2574             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2575         }
2576     }
2577     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2578     for(i=1; i<100; i+=(i<10?1:5)) {
2579         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2580         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2581         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2582             char buf[MSG_SIZ];
2583             snprintf(buf, MSG_SIZ, "%d", i);
2584             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2585         }
2586     }
2587     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2588     return TRUE;
2589 }
2590
2591 int SeekGraphClick(ClickType click, int x, int y, int moving)
2592 {
2593     static int lastDown = 0, displayed = 0, lastSecond;
2594     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2595         if(click == Release || moving) return FALSE;
2596         nrOfSeekAds = 0;
2597         soughtPending = TRUE;
2598         SendToICS(ics_prefix);
2599         SendToICS("sought\n"); // should this be "sought all"?
2600     } else { // issue challenge based on clicked ad
2601         int dist = 10000; int i, closest = 0, second = 0;
2602         for(i=0; i<nrOfSeekAds; i++) {
2603             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2604             if(d < dist) { dist = d; closest = i; }
2605             second += (d - zList[i] < 120); // count in-range ads
2606             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2607         }
2608         if(dist < 120) {
2609             char buf[MSG_SIZ];
2610             second = (second > 1);
2611             if(displayed != closest || second != lastSecond) {
2612                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2613                 lastSecond = second; displayed = closest;
2614             }
2615             if(click == Press) {
2616                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2617                 lastDown = closest;
2618                 return TRUE;
2619             } // on press 'hit', only show info
2620             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2621             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2622             SendToICS(ics_prefix);
2623             SendToICS(buf);
2624             return TRUE; // let incoming board of started game pop down the graph
2625         } else if(click == Release) { // release 'miss' is ignored
2626             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2627             if(moving == 2) { // right up-click
2628                 nrOfSeekAds = 0; // refresh graph
2629                 soughtPending = TRUE;
2630                 SendToICS(ics_prefix);
2631                 SendToICS("sought\n"); // should this be "sought all"?
2632             }
2633             return TRUE;
2634         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2635         // press miss or release hit 'pop down' seek graph
2636         seekGraphUp = FALSE;
2637         DrawPosition(TRUE, NULL);
2638     }
2639     return TRUE;
2640 }
2641
2642 void
2643 read_from_ics(isr, closure, data, count, error)
2644      InputSourceRef isr;
2645      VOIDSTAR closure;
2646      char *data;
2647      int count;
2648      int error;
2649 {
2650 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2651 #define STARTED_NONE 0
2652 #define STARTED_MOVES 1
2653 #define STARTED_BOARD 2
2654 #define STARTED_OBSERVE 3
2655 #define STARTED_HOLDINGS 4
2656 #define STARTED_CHATTER 5
2657 #define STARTED_COMMENT 6
2658 #define STARTED_MOVES_NOHIDE 7
2659
2660     static int started = STARTED_NONE;
2661     static char parse[20000];
2662     static int parse_pos = 0;
2663     static char buf[BUF_SIZE + 1];
2664     static int firstTime = TRUE, intfSet = FALSE;
2665     static ColorClass prevColor = ColorNormal;
2666     static int savingComment = FALSE;
2667     static int cmatch = 0; // continuation sequence match
2668     char *bp;
2669     char str[MSG_SIZ];
2670     int i, oldi;
2671     int buf_len;
2672     int next_out;
2673     int tkind;
2674     int backup;    /* [DM] For zippy color lines */
2675     char *p;
2676     char talker[MSG_SIZ]; // [HGM] chat
2677     int channel;
2678
2679     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2680
2681     if (appData.debugMode) {
2682       if (!error) {
2683         fprintf(debugFP, "<ICS: ");
2684         show_bytes(debugFP, data, count);
2685         fprintf(debugFP, "\n");
2686       }
2687     }
2688
2689     if (appData.debugMode) { int f = forwardMostMove;
2690         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2691                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2692                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2693     }
2694     if (count > 0) {
2695         /* If last read ended with a partial line that we couldn't parse,
2696            prepend it to the new read and try again. */
2697         if (leftover_len > 0) {
2698             for (i=0; i<leftover_len; i++)
2699               buf[i] = buf[leftover_start + i];
2700         }
2701
2702     /* copy new characters into the buffer */
2703     bp = buf + leftover_len;
2704     buf_len=leftover_len;
2705     for (i=0; i<count; i++)
2706     {
2707         // ignore these
2708         if (data[i] == '\r')
2709             continue;
2710
2711         // join lines split by ICS?
2712         if (!appData.noJoin)
2713         {
2714             /*
2715                 Joining just consists of finding matches against the
2716                 continuation sequence, and discarding that sequence
2717                 if found instead of copying it.  So, until a match
2718                 fails, there's nothing to do since it might be the
2719                 complete sequence, and thus, something we don't want
2720                 copied.
2721             */
2722             if (data[i] == cont_seq[cmatch])
2723             {
2724                 cmatch++;
2725                 if (cmatch == strlen(cont_seq))
2726                 {
2727                     cmatch = 0; // complete match.  just reset the counter
2728
2729                     /*
2730                         it's possible for the ICS to not include the space
2731                         at the end of the last word, making our [correct]
2732                         join operation fuse two separate words.  the server
2733                         does this when the space occurs at the width setting.
2734                     */
2735                     if (!buf_len || buf[buf_len-1] != ' ')
2736                     {
2737                         *bp++ = ' ';
2738                         buf_len++;
2739                     }
2740                 }
2741                 continue;
2742             }
2743             else if (cmatch)
2744             {
2745                 /*
2746                     match failed, so we have to copy what matched before
2747                     falling through and copying this character.  In reality,
2748                     this will only ever be just the newline character, but
2749                     it doesn't hurt to be precise.
2750                 */
2751                 strncpy(bp, cont_seq, cmatch);
2752                 bp += cmatch;
2753                 buf_len += cmatch;
2754                 cmatch = 0;
2755             }
2756         }
2757
2758         // copy this char
2759         *bp++ = data[i];
2760         buf_len++;
2761     }
2762
2763         buf[buf_len] = NULLCHAR;
2764 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2765         next_out = 0;
2766         leftover_start = 0;
2767
2768         i = 0;
2769         while (i < buf_len) {
2770             /* Deal with part of the TELNET option negotiation
2771                protocol.  We refuse to do anything beyond the
2772                defaults, except that we allow the WILL ECHO option,
2773                which ICS uses to turn off password echoing when we are
2774                directly connected to it.  We reject this option
2775                if localLineEditing mode is on (always on in xboard)
2776                and we are talking to port 23, which might be a real
2777                telnet server that will try to keep WILL ECHO on permanently.
2778              */
2779             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2780                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2781                 unsigned char option;
2782                 oldi = i;
2783                 switch ((unsigned char) buf[++i]) {
2784                   case TN_WILL:
2785                     if (appData.debugMode)
2786                       fprintf(debugFP, "\n<WILL ");
2787                     switch (option = (unsigned char) buf[++i]) {
2788                       case TN_ECHO:
2789                         if (appData.debugMode)
2790                           fprintf(debugFP, "ECHO ");
2791                         /* Reply only if this is a change, according
2792                            to the protocol rules. */
2793                         if (remoteEchoOption) break;
2794                         if (appData.localLineEditing &&
2795                             atoi(appData.icsPort) == TN_PORT) {
2796                             TelnetRequest(TN_DONT, TN_ECHO);
2797                         } else {
2798                             EchoOff();
2799                             TelnetRequest(TN_DO, TN_ECHO);
2800                             remoteEchoOption = TRUE;
2801                         }
2802                         break;
2803                       default:
2804                         if (appData.debugMode)
2805                           fprintf(debugFP, "%d ", option);
2806                         /* Whatever this is, we don't want it. */
2807                         TelnetRequest(TN_DONT, option);
2808                         break;
2809                     }
2810                     break;
2811                   case TN_WONT:
2812                     if (appData.debugMode)
2813                       fprintf(debugFP, "\n<WONT ");
2814                     switch (option = (unsigned char) buf[++i]) {
2815                       case TN_ECHO:
2816                         if (appData.debugMode)
2817                           fprintf(debugFP, "ECHO ");
2818                         /* Reply only if this is a change, according
2819                            to the protocol rules. */
2820                         if (!remoteEchoOption) break;
2821                         EchoOn();
2822                         TelnetRequest(TN_DONT, TN_ECHO);
2823                         remoteEchoOption = FALSE;
2824                         break;
2825                       default:
2826                         if (appData.debugMode)
2827                           fprintf(debugFP, "%d ", (unsigned char) option);
2828                         /* Whatever this is, it must already be turned
2829                            off, because we never agree to turn on
2830                            anything non-default, so according to the
2831                            protocol rules, we don't reply. */
2832                         break;
2833                     }
2834                     break;
2835                   case TN_DO:
2836                     if (appData.debugMode)
2837                       fprintf(debugFP, "\n<DO ");
2838                     switch (option = (unsigned char) buf[++i]) {
2839                       default:
2840                         /* Whatever this is, we refuse to do it. */
2841                         if (appData.debugMode)
2842                           fprintf(debugFP, "%d ", option);
2843                         TelnetRequest(TN_WONT, option);
2844                         break;
2845                     }
2846                     break;
2847                   case TN_DONT:
2848                     if (appData.debugMode)
2849                       fprintf(debugFP, "\n<DONT ");
2850                     switch (option = (unsigned char) buf[++i]) {
2851                       default:
2852                         if (appData.debugMode)
2853                           fprintf(debugFP, "%d ", option);
2854                         /* Whatever this is, we are already not doing
2855                            it, because we never agree to do anything
2856                            non-default, so according to the protocol
2857                            rules, we don't reply. */
2858                         break;
2859                     }
2860                     break;
2861                   case TN_IAC:
2862                     if (appData.debugMode)
2863                       fprintf(debugFP, "\n<IAC ");
2864                     /* Doubled IAC; pass it through */
2865                     i--;
2866                     break;
2867                   default:
2868                     if (appData.debugMode)
2869                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2870                     /* Drop all other telnet commands on the floor */
2871                     break;
2872                 }
2873                 if (oldi > next_out)
2874                   SendToPlayer(&buf[next_out], oldi - next_out);
2875                 if (++i > next_out)
2876                   next_out = i;
2877                 continue;
2878             }
2879
2880             /* OK, this at least will *usually* work */
2881             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2882                 loggedOn = TRUE;
2883             }
2884
2885             if (loggedOn && !intfSet) {
2886                 if (ics_type == ICS_ICC) {
2887                   snprintf(str, MSG_SIZ,
2888                           "/set-quietly interface %s\n/set-quietly style 12\n",
2889                           programVersion);
2890                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2891                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2892                 } else if (ics_type == ICS_CHESSNET) {
2893                   snprintf(str, MSG_SIZ, "/style 12\n");
2894                 } else {
2895                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2896                   strcat(str, programVersion);
2897                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2898                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2899                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2900 #ifdef WIN32
2901                   strcat(str, "$iset nohighlight 1\n");
2902 #endif
2903                   strcat(str, "$iset lock 1\n$style 12\n");
2904                 }
2905                 SendToICS(str);
2906                 NotifyFrontendLogin();
2907                 intfSet = TRUE;
2908             }
2909
2910             if (started == STARTED_COMMENT) {
2911                 /* Accumulate characters in comment */
2912                 parse[parse_pos++] = buf[i];
2913                 if (buf[i] == '\n') {
2914                     parse[parse_pos] = NULLCHAR;
2915                     if(chattingPartner>=0) {
2916                         char mess[MSG_SIZ];
2917                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2918                         OutputChatMessage(chattingPartner, mess);
2919                         chattingPartner = -1;
2920                         next_out = i+1; // [HGM] suppress printing in ICS window
2921                     } else
2922                     if(!suppressKibitz) // [HGM] kibitz
2923                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2924                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2925                         int nrDigit = 0, nrAlph = 0, j;
2926                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2927                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2928                         parse[parse_pos] = NULLCHAR;
2929                         // try to be smart: if it does not look like search info, it should go to
2930                         // ICS interaction window after all, not to engine-output window.
2931                         for(j=0; j<parse_pos; j++) { // count letters and digits
2932                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2933                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2934                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2935                         }
2936                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2937                             int depth=0; float score;
2938                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2939                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2940                                 pvInfoList[forwardMostMove-1].depth = depth;
2941                                 pvInfoList[forwardMostMove-1].score = 100*score;
2942                             }
2943                             OutputKibitz(suppressKibitz, parse);
2944                         } else {
2945                             char tmp[MSG_SIZ];
2946                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2947                             SendToPlayer(tmp, strlen(tmp));
2948                         }
2949                         next_out = i+1; // [HGM] suppress printing in ICS window
2950                     }
2951                     started = STARTED_NONE;
2952                 } else {
2953                     /* Don't match patterns against characters in comment */
2954                     i++;
2955                     continue;
2956                 }
2957             }
2958             if (started == STARTED_CHATTER) {
2959                 if (buf[i] != '\n') {
2960                     /* Don't match patterns against characters in chatter */
2961                     i++;
2962                     continue;
2963                 }
2964                 started = STARTED_NONE;
2965                 if(suppressKibitz) next_out = i+1;
2966             }
2967
2968             /* Kludge to deal with rcmd protocol */
2969             if (firstTime && looking_at(buf, &i, "\001*")) {
2970                 DisplayFatalError(&buf[1], 0, 1);
2971                 continue;
2972             } else {
2973                 firstTime = FALSE;
2974             }
2975
2976             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2977                 ics_type = ICS_ICC;
2978                 ics_prefix = "/";
2979                 if (appData.debugMode)
2980                   fprintf(debugFP, "ics_type %d\n", ics_type);
2981                 continue;
2982             }
2983             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2984                 ics_type = ICS_FICS;
2985                 ics_prefix = "$";
2986                 if (appData.debugMode)
2987                   fprintf(debugFP, "ics_type %d\n", ics_type);
2988                 continue;
2989             }
2990             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2991                 ics_type = ICS_CHESSNET;
2992                 ics_prefix = "/";
2993                 if (appData.debugMode)
2994                   fprintf(debugFP, "ics_type %d\n", ics_type);
2995                 continue;
2996             }
2997
2998             if (!loggedOn &&
2999                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3000                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3001                  looking_at(buf, &i, "will be \"*\""))) {
3002               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3003               continue;
3004             }
3005
3006             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3007               char buf[MSG_SIZ];
3008               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3009               DisplayIcsInteractionTitle(buf);
3010               have_set_title = TRUE;
3011             }
3012
3013             /* skip finger notes */
3014             if (started == STARTED_NONE &&
3015                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3016                  (buf[i] == '1' && buf[i+1] == '0')) &&
3017                 buf[i+2] == ':' && buf[i+3] == ' ') {
3018               started = STARTED_CHATTER;
3019               i += 3;
3020               continue;
3021             }
3022
3023             oldi = i;
3024             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3025             if(appData.seekGraph) {
3026                 if(soughtPending && MatchSoughtLine(buf+i)) {
3027                     i = strstr(buf+i, "rated") - buf;
3028                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3029                     next_out = leftover_start = i;
3030                     started = STARTED_CHATTER;
3031                     suppressKibitz = TRUE;
3032                     continue;
3033                 }
3034                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3035                         && looking_at(buf, &i, "* ads displayed")) {
3036                     soughtPending = FALSE;
3037                     seekGraphUp = TRUE;
3038                     DrawSeekGraph();
3039                     continue;
3040                 }
3041                 if(appData.autoRefresh) {
3042                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3043                         int s = (ics_type == ICS_ICC); // ICC format differs
3044                         if(seekGraphUp)
3045                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3046                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3047                         looking_at(buf, &i, "*% "); // eat prompt
3048                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3049                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050                         next_out = i; // suppress
3051                         continue;
3052                     }
3053                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3054                         char *p = star_match[0];
3055                         while(*p) {
3056                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3057                             while(*p && *p++ != ' '); // next
3058                         }
3059                         looking_at(buf, &i, "*% "); // eat prompt
3060                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3061                         next_out = i;
3062                         continue;
3063                     }
3064                 }
3065             }
3066
3067             /* skip formula vars */
3068             if (started == STARTED_NONE &&
3069                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3070               started = STARTED_CHATTER;
3071               i += 3;
3072               continue;
3073             }
3074
3075             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3076             if (appData.autoKibitz && started == STARTED_NONE &&
3077                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3078                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3079                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3080                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3081                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3082                         suppressKibitz = TRUE;
3083                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084                         next_out = i;
3085                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3086                                 && (gameMode == IcsPlayingWhite)) ||
3087                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3088                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3089                             started = STARTED_CHATTER; // own kibitz we simply discard
3090                         else {
3091                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3092                             parse_pos = 0; parse[0] = NULLCHAR;
3093                             savingComment = TRUE;
3094                             suppressKibitz = gameMode != IcsObserving ? 2 :
3095                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3096                         }
3097                         continue;
3098                 } else
3099                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3100                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3101                          && atoi(star_match[0])) {
3102                     // suppress the acknowledgements of our own autoKibitz
3103                     char *p;
3104                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3106                     SendToPlayer(star_match[0], strlen(star_match[0]));
3107                     if(looking_at(buf, &i, "*% ")) // eat prompt
3108                         suppressKibitz = FALSE;
3109                     next_out = i;
3110                     continue;
3111                 }
3112             } // [HGM] kibitz: end of patch
3113
3114             // [HGM] chat: intercept tells by users for which we have an open chat window
3115             channel = -1;
3116             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3117                                            looking_at(buf, &i, "* whispers:") ||
3118                                            looking_at(buf, &i, "* kibitzes:") ||
3119                                            looking_at(buf, &i, "* shouts:") ||
3120                                            looking_at(buf, &i, "* c-shouts:") ||
3121                                            looking_at(buf, &i, "--> * ") ||
3122                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3123                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3124                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3125                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3126                 int p;
3127                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3128                 chattingPartner = -1;
3129
3130                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3131                 for(p=0; p<MAX_CHAT; p++) {
3132                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3133                     talker[0] = '['; strcat(talker, "] ");
3134                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3135                     chattingPartner = p; break;
3136                     }
3137                 } else
3138                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3139                 for(p=0; p<MAX_CHAT; p++) {
3140                     if(!strcmp("kibitzes", chatPartner[p])) {
3141                         talker[0] = '['; strcat(talker, "] ");
3142                         chattingPartner = p; break;
3143                     }
3144                 } else
3145                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3146                 for(p=0; p<MAX_CHAT; p++) {
3147                     if(!strcmp("whispers", chatPartner[p])) {
3148                         talker[0] = '['; strcat(talker, "] ");
3149                         chattingPartner = p; break;
3150                     }
3151                 } else
3152                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3153                   if(buf[i-8] == '-' && buf[i-3] == 't')
3154                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3155                     if(!strcmp("c-shouts", chatPartner[p])) {
3156                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3157                         chattingPartner = p; break;
3158                     }
3159                   }
3160                   if(chattingPartner < 0)
3161                   for(p=0; p<MAX_CHAT; p++) {
3162                     if(!strcmp("shouts", chatPartner[p])) {
3163                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3164                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3165                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3166                         chattingPartner = p; break;
3167                     }
3168                   }
3169                 }
3170                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3171                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3172                     talker[0] = 0; Colorize(ColorTell, FALSE);
3173                     chattingPartner = p; break;
3174                 }
3175                 if(chattingPartner<0) i = oldi; else {
3176                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3177                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3178                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3179                     started = STARTED_COMMENT;
3180                     parse_pos = 0; parse[0] = NULLCHAR;
3181                     savingComment = 3 + chattingPartner; // counts as TRUE
3182                     suppressKibitz = TRUE;
3183                     continue;
3184                 }
3185             } // [HGM] chat: end of patch
3186
3187           backup = i;
3188             if (appData.zippyTalk || appData.zippyPlay) {
3189                 /* [DM] Backup address for color zippy lines */
3190 #if ZIPPY
3191                if (loggedOn == TRUE)
3192                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3193                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3194 #endif
3195             } // [DM] 'else { ' deleted
3196                 if (
3197                     /* Regular tells and says */
3198                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3199                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3200                     looking_at(buf, &i, "* says: ") ||
3201                     /* Don't color "message" or "messages" output */
3202                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3203                     looking_at(buf, &i, "*. * at *:*: ") ||
3204                     looking_at(buf, &i, "--* (*:*): ") ||
3205                     /* Message notifications (same color as tells) */
3206                     looking_at(buf, &i, "* has left a message ") ||
3207                     looking_at(buf, &i, "* just sent you a message:\n") ||
3208                     /* Whispers and kibitzes */
3209                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3210                     looking_at(buf, &i, "* kibitzes: ") ||
3211                     /* Channel tells */
3212                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3213
3214                   if (tkind == 1 && strchr(star_match[0], ':')) {
3215                       /* Avoid "tells you:" spoofs in channels */
3216                      tkind = 3;
3217                   }
3218                   if (star_match[0][0] == NULLCHAR ||
3219                       strchr(star_match[0], ' ') ||
3220                       (tkind == 3 && strchr(star_match[1], ' '))) {
3221                     /* Reject bogus matches */
3222                     i = oldi;
3223                   } else {
3224                     if (appData.colorize) {
3225                       if (oldi > next_out) {
3226                         SendToPlayer(&buf[next_out], oldi - next_out);
3227                         next_out = oldi;
3228                       }
3229                       switch (tkind) {
3230                       case 1:
3231                         Colorize(ColorTell, FALSE);
3232                         curColor = ColorTell;
3233                         break;
3234                       case 2:
3235                         Colorize(ColorKibitz, FALSE);
3236                         curColor = ColorKibitz;
3237                         break;
3238                       case 3:
3239                         p = strrchr(star_match[1], '(');
3240                         if (p == NULL) {
3241                           p = star_match[1];
3242                         } else {
3243                           p++;
3244                         }
3245                         if (atoi(p) == 1) {
3246                           Colorize(ColorChannel1, FALSE);
3247                           curColor = ColorChannel1;
3248                         } else {
3249                           Colorize(ColorChannel, FALSE);
3250                           curColor = ColorChannel;
3251                         }
3252                         break;
3253                       case 5:
3254                         curColor = ColorNormal;
3255                         break;
3256                       }
3257                     }
3258                     if (started == STARTED_NONE && appData.autoComment &&
3259                         (gameMode == IcsObserving ||
3260                          gameMode == IcsPlayingWhite ||
3261                          gameMode == IcsPlayingBlack)) {
3262                       parse_pos = i - oldi;
3263                       memcpy(parse, &buf[oldi], parse_pos);
3264                       parse[parse_pos] = NULLCHAR;
3265                       started = STARTED_COMMENT;
3266                       savingComment = TRUE;
3267                     } else {
3268                       started = STARTED_CHATTER;
3269                       savingComment = FALSE;
3270                     }
3271                     loggedOn = TRUE;
3272                     continue;
3273                   }
3274                 }
3275
3276                 if (looking_at(buf, &i, "* s-shouts: ") ||
3277                     looking_at(buf, &i, "* c-shouts: ")) {
3278                     if (appData.colorize) {
3279                         if (oldi > next_out) {
3280                             SendToPlayer(&buf[next_out], oldi - next_out);
3281                             next_out = oldi;
3282                         }
3283                         Colorize(ColorSShout, FALSE);
3284                         curColor = ColorSShout;
3285                     }
3286                     loggedOn = TRUE;
3287                     started = STARTED_CHATTER;
3288                     continue;
3289                 }
3290
3291                 if (looking_at(buf, &i, "--->")) {
3292                     loggedOn = TRUE;
3293                     continue;
3294                 }
3295
3296                 if (looking_at(buf, &i, "* shouts: ") ||
3297                     looking_at(buf, &i, "--> ")) {
3298                     if (appData.colorize) {
3299                         if (oldi > next_out) {
3300                             SendToPlayer(&buf[next_out], oldi - next_out);
3301                             next_out = oldi;
3302                         }
3303                         Colorize(ColorShout, FALSE);
3304                         curColor = ColorShout;
3305                     }
3306                     loggedOn = TRUE;
3307                     started = STARTED_CHATTER;
3308                     continue;
3309                 }
3310
3311                 if (looking_at( buf, &i, "Challenge:")) {
3312                     if (appData.colorize) {
3313                         if (oldi > next_out) {
3314                             SendToPlayer(&buf[next_out], oldi - next_out);
3315                             next_out = oldi;
3316                         }
3317                         Colorize(ColorChallenge, FALSE);
3318                         curColor = ColorChallenge;
3319                     }
3320                     loggedOn = TRUE;
3321                     continue;
3322                 }
3323
3324                 if (looking_at(buf, &i, "* offers you") ||
3325                     looking_at(buf, &i, "* offers to be") ||
3326                     looking_at(buf, &i, "* would like to") ||
3327                     looking_at(buf, &i, "* requests to") ||
3328                     looking_at(buf, &i, "Your opponent offers") ||
3329                     looking_at(buf, &i, "Your opponent requests")) {
3330
3331                     if (appData.colorize) {
3332                         if (oldi > next_out) {
3333                             SendToPlayer(&buf[next_out], oldi - next_out);
3334                             next_out = oldi;
3335                         }
3336                         Colorize(ColorRequest, FALSE);
3337                         curColor = ColorRequest;
3338                     }
3339                     continue;
3340                 }
3341
3342                 if (looking_at(buf, &i, "* (*) seeking")) {
3343                     if (appData.colorize) {
3344                         if (oldi > next_out) {
3345                             SendToPlayer(&buf[next_out], oldi - next_out);
3346                             next_out = oldi;
3347                         }
3348                         Colorize(ColorSeek, FALSE);
3349                         curColor = ColorSeek;
3350                     }
3351                     continue;
3352             }
3353
3354           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3355
3356             if (looking_at(buf, &i, "\\   ")) {
3357                 if (prevColor != ColorNormal) {
3358                     if (oldi > next_out) {
3359                         SendToPlayer(&buf[next_out], oldi - next_out);
3360                         next_out = oldi;
3361                     }
3362                     Colorize(prevColor, TRUE);
3363                     curColor = prevColor;
3364                 }
3365                 if (savingComment) {
3366                     parse_pos = i - oldi;
3367                     memcpy(parse, &buf[oldi], parse_pos);
3368                     parse[parse_pos] = NULLCHAR;
3369                     started = STARTED_COMMENT;
3370                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3371                         chattingPartner = savingComment - 3; // kludge to remember the box
3372                 } else {
3373                     started = STARTED_CHATTER;
3374                 }
3375                 continue;
3376             }
3377
3378             if (looking_at(buf, &i, "Black Strength :") ||
3379                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3380                 looking_at(buf, &i, "<10>") ||
3381                 looking_at(buf, &i, "#@#")) {
3382                 /* Wrong board style */
3383                 loggedOn = TRUE;
3384                 SendToICS(ics_prefix);
3385                 SendToICS("set style 12\n");
3386                 SendToICS(ics_prefix);
3387                 SendToICS("refresh\n");
3388                 continue;
3389             }
3390
3391             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3392                 ICSInitScript();
3393                 have_sent_ICS_logon = 1;
3394                 continue;
3395             }
3396
3397             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3398                 (looking_at(buf, &i, "\n<12> ") ||
3399                  looking_at(buf, &i, "<12> "))) {
3400                 loggedOn = TRUE;
3401                 if (oldi > next_out) {
3402                     SendToPlayer(&buf[next_out], oldi - next_out);
3403                 }
3404                 next_out = i;
3405                 started = STARTED_BOARD;
3406                 parse_pos = 0;
3407                 continue;
3408             }
3409
3410             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3411                 looking_at(buf, &i, "<b1> ")) {
3412                 if (oldi > next_out) {
3413                     SendToPlayer(&buf[next_out], oldi - next_out);
3414                 }
3415                 next_out = i;
3416                 started = STARTED_HOLDINGS;
3417                 parse_pos = 0;
3418                 continue;
3419             }
3420
3421             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3422                 loggedOn = TRUE;
3423                 /* Header for a move list -- first line */
3424
3425                 switch (ics_getting_history) {
3426                   case H_FALSE:
3427                     switch (gameMode) {
3428                       case IcsIdle:
3429                       case BeginningOfGame:
3430                         /* User typed "moves" or "oldmoves" while we
3431                            were idle.  Pretend we asked for these
3432                            moves and soak them up so user can step
3433                            through them and/or save them.
3434                            */
3435                         Reset(FALSE, TRUE);
3436                         gameMode = IcsObserving;
3437                         ModeHighlight();
3438                         ics_gamenum = -1;
3439                         ics_getting_history = H_GOT_UNREQ_HEADER;
3440                         break;
3441                       case EditGame: /*?*/
3442                       case EditPosition: /*?*/
3443                         /* Should above feature work in these modes too? */
3444                         /* For now it doesn't */
3445                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3446                         break;
3447                       default:
3448                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3449                         break;
3450                     }
3451                     break;
3452                   case H_REQUESTED:
3453                     /* Is this the right one? */
3454                     if (gameInfo.white && gameInfo.black &&
3455                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3456                         strcmp(gameInfo.black, star_match[2]) == 0) {
3457                         /* All is well */
3458                         ics_getting_history = H_GOT_REQ_HEADER;
3459                     }
3460                     break;
3461                   case H_GOT_REQ_HEADER:
3462                   case H_GOT_UNREQ_HEADER:
3463                   case H_GOT_UNWANTED_HEADER:
3464                   case H_GETTING_MOVES:
3465                     /* Should not happen */
3466                     DisplayError(_("Error gathering move list: two headers"), 0);
3467                     ics_getting_history = H_FALSE;
3468                     break;
3469                 }
3470
3471                 /* Save player ratings into gameInfo if needed */
3472                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3473                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3474                     (gameInfo.whiteRating == -1 ||
3475                      gameInfo.blackRating == -1)) {
3476
3477                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3478                     gameInfo.blackRating = string_to_rating(star_match[3]);
3479                     if (appData.debugMode)
3480                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3481                               gameInfo.whiteRating, gameInfo.blackRating);
3482                 }
3483                 continue;
3484             }
3485
3486             if (looking_at(buf, &i,
3487               "* * match, initial time: * minute*, increment: * second")) {
3488                 /* Header for a move list -- second line */
3489                 /* Initial board will follow if this is a wild game */
3490                 if (gameInfo.event != NULL) free(gameInfo.event);
3491                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3492                 gameInfo.event = StrSave(str);
3493                 /* [HGM] we switched variant. Translate boards if needed. */
3494                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3495                 continue;
3496             }
3497
3498             if (looking_at(buf, &i, "Move  ")) {
3499                 /* Beginning of a move list */
3500                 switch (ics_getting_history) {
3501                   case H_FALSE:
3502                     /* Normally should not happen */
3503                     /* Maybe user hit reset while we were parsing */
3504                     break;
3505                   case H_REQUESTED:
3506                     /* Happens if we are ignoring a move list that is not
3507                      * the one we just requested.  Common if the user
3508                      * tries to observe two games without turning off
3509                      * getMoveList */
3510                     break;
3511                   case H_GETTING_MOVES:
3512                     /* Should not happen */
3513                     DisplayError(_("Error gathering move list: nested"), 0);
3514                     ics_getting_history = H_FALSE;
3515                     break;
3516                   case H_GOT_REQ_HEADER:
3517                     ics_getting_history = H_GETTING_MOVES;
3518                     started = STARTED_MOVES;
3519                     parse_pos = 0;
3520                     if (oldi > next_out) {
3521                         SendToPlayer(&buf[next_out], oldi - next_out);
3522                     }
3523                     break;
3524                   case H_GOT_UNREQ_HEADER:
3525                     ics_getting_history = H_GETTING_MOVES;
3526                     started = STARTED_MOVES_NOHIDE;
3527                     parse_pos = 0;
3528                     break;
3529                   case H_GOT_UNWANTED_HEADER:
3530                     ics_getting_history = H_FALSE;
3531                     break;
3532                 }
3533                 continue;
3534             }
3535
3536             if (looking_at(buf, &i, "% ") ||
3537                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3538                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3539                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3540                     soughtPending = FALSE;
3541                     seekGraphUp = TRUE;
3542                     DrawSeekGraph();
3543                 }
3544                 if(suppressKibitz) next_out = i;
3545                 savingComment = FALSE;
3546                 suppressKibitz = 0;
3547                 switch (started) {
3548                   case STARTED_MOVES:
3549                   case STARTED_MOVES_NOHIDE:
3550                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3551                     parse[parse_pos + i - oldi] = NULLCHAR;
3552                     ParseGameHistory(parse);
3553 #if ZIPPY
3554                     if (appData.zippyPlay && first.initDone) {
3555                         FeedMovesToProgram(&first, forwardMostMove);
3556                         if (gameMode == IcsPlayingWhite) {
3557                             if (WhiteOnMove(forwardMostMove)) {
3558                                 if (first.sendTime) {
3559                                   if (first.useColors) {
3560                                     SendToProgram("black\n", &first);
3561                                   }
3562                                   SendTimeRemaining(&first, TRUE);
3563                                 }
3564                                 if (first.useColors) {
3565                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3566                                 }
3567                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3568                                 first.maybeThinking = TRUE;
3569                             } else {
3570                                 if (first.usePlayother) {
3571                                   if (first.sendTime) {
3572                                     SendTimeRemaining(&first, TRUE);
3573                                   }
3574                                   SendToProgram("playother\n", &first);
3575                                   firstMove = FALSE;
3576                                 } else {
3577                                   firstMove = TRUE;
3578                                 }
3579                             }
3580                         } else if (gameMode == IcsPlayingBlack) {
3581                             if (!WhiteOnMove(forwardMostMove)) {
3582                                 if (first.sendTime) {
3583                                   if (first.useColors) {
3584                                     SendToProgram("white\n", &first);
3585                                   }
3586                                   SendTimeRemaining(&first, FALSE);
3587                                 }
3588                                 if (first.useColors) {
3589                                   SendToProgram("black\n", &first);
3590                                 }
3591                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3592                                 first.maybeThinking = TRUE;
3593                             } else {
3594                                 if (first.usePlayother) {
3595                                   if (first.sendTime) {
3596                                     SendTimeRemaining(&first, FALSE);
3597                                   }
3598                                   SendToProgram("playother\n", &first);
3599                                   firstMove = FALSE;
3600                                 } else {
3601                                   firstMove = TRUE;
3602                                 }
3603                             }
3604                         }
3605                     }
3606 #endif
3607                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3608                         /* Moves came from oldmoves or moves command
3609                            while we weren't doing anything else.
3610                            */
3611                         currentMove = forwardMostMove;
3612                         ClearHighlights();/*!!could figure this out*/
3613                         flipView = appData.flipView;
3614                         DrawPosition(TRUE, boards[currentMove]);
3615                         DisplayBothClocks();
3616                         snprintf(str, MSG_SIZ, "%s vs. %s",
3617                                 gameInfo.white, gameInfo.black);
3618                         DisplayTitle(str);
3619                         gameMode = IcsIdle;
3620                     } else {
3621                         /* Moves were history of an active game */
3622                         if (gameInfo.resultDetails != NULL) {
3623                             free(gameInfo.resultDetails);
3624                             gameInfo.resultDetails = NULL;
3625                         }
3626                     }
3627                     HistorySet(parseList, backwardMostMove,
3628                                forwardMostMove, currentMove-1);
3629                     DisplayMove(currentMove - 1);
3630                     if (started == STARTED_MOVES) next_out = i;
3631                     started = STARTED_NONE;
3632                     ics_getting_history = H_FALSE;
3633                     break;
3634
3635                   case STARTED_OBSERVE:
3636                     started = STARTED_NONE;
3637                     SendToICS(ics_prefix);
3638                     SendToICS("refresh\n");
3639                     break;
3640
3641                   default:
3642                     break;
3643                 }
3644                 if(bookHit) { // [HGM] book: simulate book reply
3645                     static char bookMove[MSG_SIZ]; // a bit generous?
3646
3647                     programStats.nodes = programStats.depth = programStats.time =
3648                     programStats.score = programStats.got_only_move = 0;
3649                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3650
3651                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3652                     strcat(bookMove, bookHit);
3653                     HandleMachineMove(bookMove, &first);
3654                 }
3655                 continue;
3656             }
3657
3658             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3659                  started == STARTED_HOLDINGS ||
3660                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3661                 /* Accumulate characters in move list or board */
3662                 parse[parse_pos++] = buf[i];
3663             }
3664
3665             /* Start of game messages.  Mostly we detect start of game
3666                when the first board image arrives.  On some versions
3667                of the ICS, though, we need to do a "refresh" after starting
3668                to observe in order to get the current board right away. */
3669             if (looking_at(buf, &i, "Adding game * to observation list")) {
3670                 started = STARTED_OBSERVE;
3671                 continue;
3672             }
3673
3674             /* Handle auto-observe */
3675             if (appData.autoObserve &&
3676                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3677                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3678                 char *player;
3679                 /* Choose the player that was highlighted, if any. */
3680                 if (star_match[0][0] == '\033' ||
3681                     star_match[1][0] != '\033') {
3682                     player = star_match[0];
3683                 } else {
3684                     player = star_match[2];
3685                 }
3686                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3687                         ics_prefix, StripHighlightAndTitle(player));
3688                 SendToICS(str);
3689
3690                 /* Save ratings from notify string */
3691                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3692                 player1Rating = string_to_rating(star_match[1]);
3693                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3694                 player2Rating = string_to_rating(star_match[3]);
3695
3696                 if (appData.debugMode)
3697                   fprintf(debugFP,
3698                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3699                           player1Name, player1Rating,
3700                           player2Name, player2Rating);
3701
3702                 continue;
3703             }
3704
3705             /* Deal with automatic examine mode after a game,
3706                and with IcsObserving -> IcsExamining transition */
3707             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3708                 looking_at(buf, &i, "has made you an examiner of game *")) {
3709
3710                 int gamenum = atoi(star_match[0]);
3711                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3712                     gamenum == ics_gamenum) {
3713                     /* We were already playing or observing this game;
3714                        no need to refetch history */
3715                     gameMode = IcsExamining;
3716                     if (pausing) {
3717                         pauseExamForwardMostMove = forwardMostMove;
3718                     } else if (currentMove < forwardMostMove) {
3719                         ForwardInner(forwardMostMove);
3720                     }
3721                 } else {
3722                     /* I don't think this case really can happen */
3723                     SendToICS(ics_prefix);
3724                     SendToICS("refresh\n");
3725                 }
3726                 continue;
3727             }
3728
3729             /* Error messages */
3730 //          if (ics_user_moved) {
3731             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3732                 if (looking_at(buf, &i, "Illegal move") ||
3733                     looking_at(buf, &i, "Not a legal move") ||
3734                     looking_at(buf, &i, "Your king is in check") ||
3735                     looking_at(buf, &i, "It isn't your turn") ||
3736                     looking_at(buf, &i, "It is not your move")) {
3737                     /* Illegal move */
3738                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3739                         currentMove = forwardMostMove-1;
3740                         DisplayMove(currentMove - 1); /* before DMError */
3741                         DrawPosition(FALSE, boards[currentMove]);
3742                         SwitchClocks(forwardMostMove-1); // [HGM] race
3743                         DisplayBothClocks();
3744                     }
3745                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3746                     ics_user_moved = 0;
3747                     continue;
3748                 }
3749             }
3750
3751             if (looking_at(buf, &i, "still have time") ||
3752                 looking_at(buf, &i, "not out of time") ||
3753                 looking_at(buf, &i, "either player is out of time") ||
3754                 looking_at(buf, &i, "has timeseal; checking")) {
3755                 /* We must have called his flag a little too soon */
3756                 whiteFlag = blackFlag = FALSE;
3757                 continue;
3758             }
3759
3760             if (looking_at(buf, &i, "added * seconds to") ||
3761                 looking_at(buf, &i, "seconds were added to")) {
3762                 /* Update the clocks */
3763                 SendToICS(ics_prefix);
3764                 SendToICS("refresh\n");
3765                 continue;
3766             }
3767
3768             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3769                 ics_clock_paused = TRUE;
3770                 StopClocks();
3771                 continue;
3772             }
3773
3774             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3775                 ics_clock_paused = FALSE;
3776                 StartClocks();
3777                 continue;
3778             }
3779
3780             /* Grab player ratings from the Creating: message.
3781                Note we have to check for the special case when
3782                the ICS inserts things like [white] or [black]. */
3783             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3784                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3785                 /* star_matches:
3786                    0    player 1 name (not necessarily white)
3787                    1    player 1 rating
3788                    2    empty, white, or black (IGNORED)
3789                    3    player 2 name (not necessarily black)
3790                    4    player 2 rating
3791
3792                    The names/ratings are sorted out when the game
3793                    actually starts (below).
3794                 */
3795                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3796                 player1Rating = string_to_rating(star_match[1]);
3797                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3798                 player2Rating = string_to_rating(star_match[4]);
3799
3800                 if (appData.debugMode)
3801                   fprintf(debugFP,
3802                           "Ratings from 'Creating:' %s %d, %s %d\n",
3803                           player1Name, player1Rating,
3804                           player2Name, player2Rating);
3805
3806                 continue;
3807             }
3808
3809             /* Improved generic start/end-of-game messages */
3810             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3811                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3812                 /* If tkind == 0: */
3813                 /* star_match[0] is the game number */
3814                 /*           [1] is the white player's name */
3815                 /*           [2] is the black player's name */
3816                 /* For end-of-game: */
3817                 /*           [3] is the reason for the game end */
3818                 /*           [4] is a PGN end game-token, preceded by " " */
3819                 /* For start-of-game: */
3820                 /*           [3] begins with "Creating" or "Continuing" */
3821                 /*           [4] is " *" or empty (don't care). */
3822                 int gamenum = atoi(star_match[0]);
3823                 char *whitename, *blackname, *why, *endtoken;
3824                 ChessMove endtype = EndOfFile;
3825
3826                 if (tkind == 0) {
3827                   whitename = star_match[1];
3828                   blackname = star_match[2];
3829                   why = star_match[3];
3830                   endtoken = star_match[4];
3831                 } else {
3832                   whitename = star_match[1];
3833                   blackname = star_match[3];
3834                   why = star_match[5];
3835                   endtoken = star_match[6];
3836                 }
3837
3838                 /* Game start messages */
3839                 if (strncmp(why, "Creating ", 9) == 0 ||
3840                     strncmp(why, "Continuing ", 11) == 0) {
3841                     gs_gamenum = gamenum;
3842                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3843                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3844 #if ZIPPY
3845                     if (appData.zippyPlay) {
3846                         ZippyGameStart(whitename, blackname);
3847                     }
3848 #endif /*ZIPPY*/
3849                     partnerBoardValid = FALSE; // [HGM] bughouse
3850                     continue;
3851                 }
3852
3853                 /* Game end messages */
3854                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3855                     ics_gamenum != gamenum) {
3856                     continue;
3857                 }
3858                 while (endtoken[0] == ' ') endtoken++;
3859                 switch (endtoken[0]) {
3860                   case '*':
3861                   default:
3862                     endtype = GameUnfinished;
3863                     break;
3864                   case '0':
3865                     endtype = BlackWins;
3866                     break;
3867                   case '1':
3868                     if (endtoken[1] == '/')
3869                       endtype = GameIsDrawn;
3870                     else
3871                       endtype = WhiteWins;
3872                     break;
3873                 }
3874                 GameEnds(endtype, why, GE_ICS);
3875 #if ZIPPY
3876                 if (appData.zippyPlay && first.initDone) {
3877                     ZippyGameEnd(endtype, why);
3878                     if (first.pr == NULL) {
3879                       /* Start the next process early so that we'll
3880                          be ready for the next challenge */
3881                       StartChessProgram(&first);
3882                     }
3883                     /* Send "new" early, in case this command takes
3884                        a long time to finish, so that we'll be ready
3885                        for the next challenge. */
3886                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3887                     Reset(TRUE, TRUE);
3888                 }
3889 #endif /*ZIPPY*/
3890                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3891                 continue;
3892             }
3893
3894             if (looking_at(buf, &i, "Removing game * from observation") ||
3895                 looking_at(buf, &i, "no longer observing game *") ||
3896                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3897                 if (gameMode == IcsObserving &&
3898                     atoi(star_match[0]) == ics_gamenum)
3899                   {
3900                       /* icsEngineAnalyze */
3901                       if (appData.icsEngineAnalyze) {
3902                             ExitAnalyzeMode();
3903                             ModeHighlight();
3904                       }
3905                       StopClocks();
3906                       gameMode = IcsIdle;
3907                       ics_gamenum = -1;
3908                       ics_user_moved = FALSE;
3909                   }
3910                 continue;
3911             }
3912
3913             if (looking_at(buf, &i, "no longer examining game *")) {
3914                 if (gameMode == IcsExamining &&
3915                     atoi(star_match[0]) == ics_gamenum)
3916                   {
3917                       gameMode = IcsIdle;
3918                       ics_gamenum = -1;
3919                       ics_user_moved = FALSE;
3920                   }
3921                 continue;
3922             }
3923
3924             /* Advance leftover_start past any newlines we find,
3925                so only partial lines can get reparsed */
3926             if (looking_at(buf, &i, "\n")) {
3927                 prevColor = curColor;
3928                 if (curColor != ColorNormal) {
3929                     if (oldi > next_out) {
3930                         SendToPlayer(&buf[next_out], oldi - next_out);
3931                         next_out = oldi;
3932                     }
3933                     Colorize(ColorNormal, FALSE);
3934                     curColor = ColorNormal;
3935                 }
3936                 if (started == STARTED_BOARD) {
3937                     started = STARTED_NONE;
3938                     parse[parse_pos] = NULLCHAR;
3939                     ParseBoard12(parse);
3940                     ics_user_moved = 0;
3941
3942                     /* Send premove here */
3943                     if (appData.premove) {
3944                       char str[MSG_SIZ];
3945                       if (currentMove == 0 &&
3946                           gameMode == IcsPlayingWhite &&
3947                           appData.premoveWhite) {
3948                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3949                         if (appData.debugMode)
3950                           fprintf(debugFP, "Sending premove:\n");
3951                         SendToICS(str);
3952                       } else if (currentMove == 1 &&
3953                                  gameMode == IcsPlayingBlack &&
3954                                  appData.premoveBlack) {
3955                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3956                         if (appData.debugMode)
3957                           fprintf(debugFP, "Sending premove:\n");
3958                         SendToICS(str);
3959                       } else if (gotPremove) {
3960                         gotPremove = 0;
3961                         ClearPremoveHighlights();
3962                         if (appData.debugMode)
3963                           fprintf(debugFP, "Sending premove:\n");
3964                           UserMoveEvent(premoveFromX, premoveFromY,
3965                                         premoveToX, premoveToY,
3966                                         premovePromoChar);
3967                       }
3968                     }
3969
3970                     /* Usually suppress following prompt */
3971                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3972                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3973                         if (looking_at(buf, &i, "*% ")) {
3974                             savingComment = FALSE;
3975                             suppressKibitz = 0;
3976                         }
3977                     }
3978                     next_out = i;
3979                 } else if (started == STARTED_HOLDINGS) {
3980                     int gamenum;
3981                     char new_piece[MSG_SIZ];
3982                     started = STARTED_NONE;
3983                     parse[parse_pos] = NULLCHAR;
3984                     if (appData.debugMode)
3985                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3986                                                         parse, currentMove);
3987                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3988                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3989                         if (gameInfo.variant == VariantNormal) {
3990                           /* [HGM] We seem to switch variant during a game!
3991                            * Presumably no holdings were displayed, so we have
3992                            * to move the position two files to the right to
3993                            * create room for them!
3994                            */
3995                           VariantClass newVariant;
3996                           switch(gameInfo.boardWidth) { // base guess on board width
3997                                 case 9:  newVariant = VariantShogi; break;
3998                                 case 10: newVariant = VariantGreat; break;
3999                                 default: newVariant = VariantCrazyhouse; break;
4000                           }
4001                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4002                           /* Get a move list just to see the header, which
4003                              will tell us whether this is really bug or zh */
4004                           if (ics_getting_history == H_FALSE) {
4005                             ics_getting_history = H_REQUESTED;
4006                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4007                             SendToICS(str);
4008                           }
4009                         }
4010                         new_piece[0] = NULLCHAR;
4011                         sscanf(parse, "game %d white [%s black [%s <- %s",
4012                                &gamenum, white_holding, black_holding,
4013                                new_piece);
4014                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4015                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4016                         /* [HGM] copy holdings to board holdings area */
4017                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4018                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4019                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4020 #if ZIPPY
4021                         if (appData.zippyPlay && first.initDone) {
4022                             ZippyHoldings(white_holding, black_holding,
4023                                           new_piece);
4024                         }
4025 #endif /*ZIPPY*/
4026                         if (tinyLayout || smallLayout) {
4027                             char wh[16], bh[16];
4028                             PackHolding(wh, white_holding);
4029                             PackHolding(bh, black_holding);
4030                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4031                                     gameInfo.white, gameInfo.black);
4032                         } else {
4033                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4034                                     gameInfo.white, white_holding,
4035                                     gameInfo.black, black_holding);
4036                         }
4037                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4038                         DrawPosition(FALSE, boards[currentMove]);
4039                         DisplayTitle(str);
4040                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4041                         sscanf(parse, "game %d white [%s black [%s <- %s",
4042                                &gamenum, white_holding, black_holding,
4043                                new_piece);
4044                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4045                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4046                         /* [HGM] copy holdings to partner-board holdings area */
4047                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4048                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4049                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4050                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4051                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4052                       }
4053                     }
4054                     /* Suppress following prompt */
4055                     if (looking_at(buf, &i, "*% ")) {
4056                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4057                         savingComment = FALSE;
4058                         suppressKibitz = 0;
4059                     }
4060                     next_out = i;
4061                 }
4062                 continue;
4063             }
4064
4065             i++;                /* skip unparsed character and loop back */
4066         }
4067
4068         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4069 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4070 //          SendToPlayer(&buf[next_out], i - next_out);
4071             started != STARTED_HOLDINGS && leftover_start > next_out) {
4072             SendToPlayer(&buf[next_out], leftover_start - next_out);
4073             next_out = i;
4074         }
4075
4076         leftover_len = buf_len - leftover_start;
4077         /* if buffer ends with something we couldn't parse,
4078            reparse it after appending the next read */
4079
4080     } else if (count == 0) {
4081         RemoveInputSource(isr);
4082         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4083     } else {
4084         DisplayFatalError(_("Error reading from ICS"), error, 1);
4085     }
4086 }
4087
4088
4089 /* Board style 12 looks like this:
4090
4091    <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
4092
4093  * The "<12> " is stripped before it gets to this routine.  The two
4094  * trailing 0's (flip state and clock ticking) are later addition, and
4095  * some chess servers may not have them, or may have only the first.
4096  * Additional trailing fields may be added in the future.
4097  */
4098
4099 #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"
4100
4101 #define RELATION_OBSERVING_PLAYED    0
4102 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4103 #define RELATION_PLAYING_MYMOVE      1
4104 #define RELATION_PLAYING_NOTMYMOVE  -1
4105 #define RELATION_EXAMINING           2
4106 #define RELATION_ISOLATED_BOARD     -3
4107 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4108
4109 void
4110 ParseBoard12(string)
4111      char *string;
4112 {
4113     GameMode newGameMode;
4114     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4115     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4116     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4117     char to_play, board_chars[200];
4118     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4119     char black[32], white[32];
4120     Board board;
4121     int prevMove = currentMove;
4122     int ticking = 2;
4123     ChessMove moveType;
4124     int fromX, fromY, toX, toY;
4125     char promoChar;
4126     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4127     char *bookHit = NULL; // [HGM] book
4128     Boolean weird = FALSE, reqFlag = FALSE;
4129
4130     fromX = fromY = toX = toY = -1;
4131
4132     newGame = FALSE;
4133
4134     if (appData.debugMode)
4135       fprintf(debugFP, _("Parsing board: %s\n"), string);
4136
4137     move_str[0] = NULLCHAR;
4138     elapsed_time[0] = NULLCHAR;
4139     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4140         int  i = 0, j;
4141         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4142             if(string[i] == ' ') { ranks++; files = 0; }
4143             else files++;
4144             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4145             i++;
4146         }
4147         for(j = 0; j <i; j++) board_chars[j] = string[j];
4148         board_chars[i] = '\0';
4149         string += i + 1;
4150     }
4151     n = sscanf(string, PATTERN, &to_play, &double_push,
4152                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4153                &gamenum, white, black, &relation, &basetime, &increment,
4154                &white_stren, &black_stren, &white_time, &black_time,
4155                &moveNum, str, elapsed_time, move_str, &ics_flip,
4156                &ticking);
4157
4158     if (n < 21) {
4159         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4160         DisplayError(str, 0);
4161         return;
4162     }
4163
4164     /* Convert the move number to internal form */
4165     moveNum = (moveNum - 1) * 2;
4166     if (to_play == 'B') moveNum++;
4167     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4168       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4169                         0, 1);
4170       return;
4171     }
4172
4173     switch (relation) {
4174       case RELATION_OBSERVING_PLAYED:
4175       case RELATION_OBSERVING_STATIC:
4176         if (gamenum == -1) {
4177             /* Old ICC buglet */
4178             relation = RELATION_OBSERVING_STATIC;
4179         }
4180         newGameMode = IcsObserving;
4181         break;
4182       case RELATION_PLAYING_MYMOVE:
4183       case RELATION_PLAYING_NOTMYMOVE:
4184         newGameMode =
4185           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4186             IcsPlayingWhite : IcsPlayingBlack;
4187         break;
4188       case RELATION_EXAMINING:
4189         newGameMode = IcsExamining;
4190         break;
4191       case RELATION_ISOLATED_BOARD:
4192       default:
4193         /* Just display this board.  If user was doing something else,
4194            we will forget about it until the next board comes. */
4195         newGameMode = IcsIdle;
4196         break;
4197       case RELATION_STARTING_POSITION:
4198         newGameMode = gameMode;
4199         break;
4200     }
4201
4202     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4203          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4204       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4205       char *toSqr;
4206       for (k = 0; k < ranks; k++) {
4207         for (j = 0; j < files; j++)
4208           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4209         if(gameInfo.holdingsWidth > 1) {
4210              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4211              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4212         }
4213       }
4214       CopyBoard(partnerBoard, board);
4215       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4216         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4217         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4218       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4219       if(toSqr = strchr(str, '-')) {
4220         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4221         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4222       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4223       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4224       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4225       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4226       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4227       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4228                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4229       DisplayMessage(partnerStatus, "");
4230         partnerBoardValid = TRUE;
4231       return;
4232     }
4233
4234     /* Modify behavior for initial board display on move listing
4235        of wild games.
4236        */
4237     switch (ics_getting_history) {
4238       case H_FALSE:
4239       case H_REQUESTED:
4240         break;
4241       case H_GOT_REQ_HEADER:
4242       case H_GOT_UNREQ_HEADER:
4243         /* This is the initial position of the current game */
4244         gamenum = ics_gamenum;
4245         moveNum = 0;            /* old ICS bug workaround */
4246         if (to_play == 'B') {
4247           startedFromSetupPosition = TRUE;
4248           blackPlaysFirst = TRUE;
4249           moveNum = 1;
4250           if (forwardMostMove == 0) forwardMostMove = 1;
4251           if (backwardMostMove == 0) backwardMostMove = 1;
4252           if (currentMove == 0) currentMove = 1;
4253         }
4254         newGameMode = gameMode;
4255         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4256         break;
4257       case H_GOT_UNWANTED_HEADER:
4258         /* This is an initial board that we don't want */
4259         return;
4260       case H_GETTING_MOVES:
4261         /* Should not happen */
4262         DisplayError(_("Error gathering move list: extra board"), 0);
4263         ics_getting_history = H_FALSE;
4264         return;
4265     }
4266
4267    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4268                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4269      /* [HGM] We seem to have switched variant unexpectedly
4270       * Try to guess new variant from board size
4271       */
4272           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4273           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4274           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4275           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4276           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4277           if(!weird) newVariant = VariantNormal;
4278           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4279           /* Get a move list just to see the header, which
4280              will tell us whether this is really bug or zh */
4281           if (ics_getting_history == H_FALSE) {
4282             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4283             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4284             SendToICS(str);
4285           }
4286     }
4287
4288     /* Take action if this is the first board of a new game, or of a
4289        different game than is currently being displayed.  */
4290     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4291         relation == RELATION_ISOLATED_BOARD) {
4292
4293         /* Forget the old game and get the history (if any) of the new one */
4294         if (gameMode != BeginningOfGame) {
4295           Reset(TRUE, TRUE);
4296         }
4297         newGame = TRUE;
4298         if (appData.autoRaiseBoard) BoardToTop();
4299         prevMove = -3;
4300         if (gamenum == -1) {
4301             newGameMode = IcsIdle;
4302         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4303                    appData.getMoveList && !reqFlag) {
4304             /* Need to get game history */
4305             ics_getting_history = H_REQUESTED;
4306             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4307             SendToICS(str);
4308         }
4309
4310         /* Initially flip the board to have black on the bottom if playing
4311            black or if the ICS flip flag is set, but let the user change
4312            it with the Flip View button. */
4313         flipView = appData.autoFlipView ?
4314           (newGameMode == IcsPlayingBlack) || ics_flip :
4315           appData.flipView;
4316
4317         /* Done with values from previous mode; copy in new ones */
4318         gameMode = newGameMode;
4319         ModeHighlight();
4320         ics_gamenum = gamenum;
4321         if (gamenum == gs_gamenum) {
4322             int klen = strlen(gs_kind);
4323             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4324             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4325             gameInfo.event = StrSave(str);
4326         } else {
4327             gameInfo.event = StrSave("ICS game");
4328         }
4329         gameInfo.site = StrSave(appData.icsHost);
4330         gameInfo.date = PGNDate();
4331         gameInfo.round = StrSave("-");
4332         gameInfo.white = StrSave(white);
4333         gameInfo.black = StrSave(black);
4334         timeControl = basetime * 60 * 1000;
4335         timeControl_2 = 0;
4336         timeIncrement = increment * 1000;
4337         movesPerSession = 0;
4338         gameInfo.timeControl = TimeControlTagValue();
4339         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4340   if (appData.debugMode) {
4341     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4342     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4343     setbuf(debugFP, NULL);
4344   }
4345
4346         gameInfo.outOfBook = NULL;
4347
4348         /* Do we have the ratings? */
4349         if (strcmp(player1Name, white) == 0 &&
4350             strcmp(player2Name, black) == 0) {
4351             if (appData.debugMode)
4352               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4353                       player1Rating, player2Rating);
4354             gameInfo.whiteRating = player1Rating;
4355             gameInfo.blackRating = player2Rating;
4356         } else if (strcmp(player2Name, white) == 0 &&
4357                    strcmp(player1Name, black) == 0) {
4358             if (appData.debugMode)
4359               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4360                       player2Rating, player1Rating);
4361             gameInfo.whiteRating = player2Rating;
4362             gameInfo.blackRating = player1Rating;
4363         }
4364         player1Name[0] = player2Name[0] = NULLCHAR;
4365
4366         /* Silence shouts if requested */
4367         if (appData.quietPlay &&
4368             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4369             SendToICS(ics_prefix);
4370             SendToICS("set shout 0\n");
4371         }
4372     }
4373
4374     /* Deal with midgame name changes */
4375     if (!newGame) {
4376         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4377             if (gameInfo.white) free(gameInfo.white);
4378             gameInfo.white = StrSave(white);
4379         }
4380         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4381             if (gameInfo.black) free(gameInfo.black);
4382             gameInfo.black = StrSave(black);
4383         }
4384     }
4385
4386     /* Throw away game result if anything actually changes in examine mode */
4387     if (gameMode == IcsExamining && !newGame) {
4388         gameInfo.result = GameUnfinished;
4389         if (gameInfo.resultDetails != NULL) {
4390             free(gameInfo.resultDetails);
4391             gameInfo.resultDetails = NULL;
4392         }
4393     }
4394
4395     /* In pausing && IcsExamining mode, we ignore boards coming
4396        in if they are in a different variation than we are. */
4397     if (pauseExamInvalid) return;
4398     if (pausing && gameMode == IcsExamining) {
4399         if (moveNum <= pauseExamForwardMostMove) {
4400             pauseExamInvalid = TRUE;
4401             forwardMostMove = pauseExamForwardMostMove;
4402             return;
4403         }
4404     }
4405
4406   if (appData.debugMode) {
4407     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4408   }
4409     /* Parse the board */
4410     for (k = 0; k < ranks; k++) {
4411       for (j = 0; j < files; j++)
4412         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4413       if(gameInfo.holdingsWidth > 1) {
4414            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4415            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4416       }
4417     }
4418     CopyBoard(boards[moveNum], board);
4419     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4420     if (moveNum == 0) {
4421         startedFromSetupPosition =
4422           !CompareBoards(board, initialPosition);
4423         if(startedFromSetupPosition)
4424             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4425     }
4426
4427     /* [HGM] Set castling rights. Take the outermost Rooks,
4428        to make it also work for FRC opening positions. Note that board12
4429        is really defective for later FRC positions, as it has no way to
4430        indicate which Rook can castle if they are on the same side of King.
4431        For the initial position we grant rights to the outermost Rooks,
4432        and remember thos rights, and we then copy them on positions
4433        later in an FRC game. This means WB might not recognize castlings with
4434        Rooks that have moved back to their original position as illegal,
4435        but in ICS mode that is not its job anyway.
4436     */
4437     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4438     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4439
4440         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4441             if(board[0][i] == WhiteRook) j = i;
4442         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4443         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4444             if(board[0][i] == WhiteRook) j = i;
4445         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4446         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4447             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4448         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4449         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4450             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4451         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4452
4453         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4454         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4455             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4456         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4457             if(board[BOARD_HEIGHT-1][k] == bKing)
4458                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4459         if(gameInfo.variant == VariantTwoKings) {
4460             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4461             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4462             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4463         }
4464     } else { int r;
4465         r = boards[moveNum][CASTLING][0] = initialRights[0];
4466         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4467         r = boards[moveNum][CASTLING][1] = initialRights[1];
4468         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4469         r = boards[moveNum][CASTLING][3] = initialRights[3];
4470         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4471         r = boards[moveNum][CASTLING][4] = initialRights[4];
4472         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4473         /* wildcastle kludge: always assume King has rights */
4474         r = boards[moveNum][CASTLING][2] = initialRights[2];
4475         r = boards[moveNum][CASTLING][5] = initialRights[5];
4476     }
4477     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4478     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4479
4480
4481     if (ics_getting_history == H_GOT_REQ_HEADER ||
4482         ics_getting_history == H_GOT_UNREQ_HEADER) {
4483         /* This was an initial position from a move list, not
4484            the current position */
4485         return;
4486     }
4487
4488     /* Update currentMove and known move number limits */
4489     newMove = newGame || moveNum > forwardMostMove;
4490
4491     if (newGame) {
4492         forwardMostMove = backwardMostMove = currentMove = moveNum;
4493         if (gameMode == IcsExamining && moveNum == 0) {
4494           /* Workaround for ICS limitation: we are not told the wild
4495              type when starting to examine a game.  But if we ask for
4496              the move list, the move list header will tell us */
4497             ics_getting_history = H_REQUESTED;
4498             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4499             SendToICS(str);
4500         }
4501     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4502                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4503 #if ZIPPY
4504         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4505         /* [HGM] applied this also to an engine that is silently watching        */
4506         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4507             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4508             gameInfo.variant == currentlyInitializedVariant) {
4509           takeback = forwardMostMove - moveNum;
4510           for (i = 0; i < takeback; i++) {
4511             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4512             SendToProgram("undo\n", &first);
4513           }
4514         }
4515 #endif
4516
4517         forwardMostMove = moveNum;
4518         if (!pausing || currentMove > forwardMostMove)
4519           currentMove = forwardMostMove;
4520     } else {
4521         /* New part of history that is not contiguous with old part */
4522         if (pausing && gameMode == IcsExamining) {
4523             pauseExamInvalid = TRUE;
4524             forwardMostMove = pauseExamForwardMostMove;
4525             return;
4526         }
4527         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4528 #if ZIPPY
4529             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4530                 // [HGM] when we will receive the move list we now request, it will be
4531                 // fed to the engine from the first move on. So if the engine is not
4532                 // in the initial position now, bring it there.
4533                 InitChessProgram(&first, 0);
4534             }
4535 #endif
4536             ics_getting_history = H_REQUESTED;
4537             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4538             SendToICS(str);
4539         }
4540         forwardMostMove = backwardMostMove = currentMove = moveNum;
4541     }
4542
4543     /* Update the clocks */
4544     if (strchr(elapsed_time, '.')) {
4545       /* Time is in ms */
4546       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4547       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4548     } else {
4549       /* Time is in seconds */
4550       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4551       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4552     }
4553
4554
4555 #if ZIPPY
4556     if (appData.zippyPlay && newGame &&
4557         gameMode != IcsObserving && gameMode != IcsIdle &&
4558         gameMode != IcsExamining)
4559       ZippyFirstBoard(moveNum, basetime, increment);
4560 #endif
4561
4562     /* Put the move on the move list, first converting
4563        to canonical algebraic form. */
4564     if (moveNum > 0) {
4565   if (appData.debugMode) {
4566     if (appData.debugMode) { int f = forwardMostMove;
4567         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4568                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4569                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4570     }
4571     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4572     fprintf(debugFP, "moveNum = %d\n", moveNum);
4573     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4574     setbuf(debugFP, NULL);
4575   }
4576         if (moveNum <= backwardMostMove) {
4577             /* We don't know what the board looked like before
4578                this move.  Punt. */
4579           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4580             strcat(parseList[moveNum - 1], " ");
4581             strcat(parseList[moveNum - 1], elapsed_time);
4582             moveList[moveNum - 1][0] = NULLCHAR;
4583         } else if (strcmp(move_str, "none") == 0) {
4584             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4585             /* Again, we don't know what the board looked like;
4586                this is really the start of the game. */
4587             parseList[moveNum - 1][0] = NULLCHAR;
4588             moveList[moveNum - 1][0] = NULLCHAR;
4589             backwardMostMove = moveNum;
4590             startedFromSetupPosition = TRUE;
4591             fromX = fromY = toX = toY = -1;
4592         } else {
4593           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4594           //                 So we parse the long-algebraic move string in stead of the SAN move
4595           int valid; char buf[MSG_SIZ], *prom;
4596
4597           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4598                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4599           // str looks something like "Q/a1-a2"; kill the slash
4600           if(str[1] == '/')
4601             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4602           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4603           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4604                 strcat(buf, prom); // long move lacks promo specification!
4605           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4606                 if(appData.debugMode)
4607                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4608                 safeStrCpy(move_str, buf, MSG_SIZ);
4609           }
4610           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4611                                 &fromX, &fromY, &toX, &toY, &promoChar)
4612                || ParseOneMove(buf, moveNum - 1, &moveType,
4613                                 &fromX, &fromY, &toX, &toY, &promoChar);
4614           // end of long SAN patch
4615           if (valid) {
4616             (void) CoordsToAlgebraic(boards[moveNum - 1],
4617                                      PosFlags(moveNum - 1),
4618                                      fromY, fromX, toY, toX, promoChar,
4619                                      parseList[moveNum-1]);
4620             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4621               case MT_NONE:
4622               case MT_STALEMATE:
4623               default:
4624                 break;
4625               case MT_CHECK:
4626                 if(gameInfo.variant != VariantShogi)
4627                     strcat(parseList[moveNum - 1], "+");
4628                 break;
4629               case MT_CHECKMATE:
4630               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4631                 strcat(parseList[moveNum - 1], "#");
4632                 break;
4633             }
4634             strcat(parseList[moveNum - 1], " ");
4635             strcat(parseList[moveNum - 1], elapsed_time);
4636             /* currentMoveString is set as a side-effect of ParseOneMove */
4637             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4638             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4639             strcat(moveList[moveNum - 1], "\n");
4640
4641             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4642                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4643               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4644                 ChessSquare old, new = boards[moveNum][k][j];
4645                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4646                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4647                   if(old == new) continue;
4648                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4649                   else if(new == WhiteWazir || new == BlackWazir) {
4650                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4651                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4652                       else boards[moveNum][k][j] = old; // preserve type of Gold
4653                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4654                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4655               }
4656           } else {
4657             /* Move from ICS was illegal!?  Punt. */
4658             if (appData.debugMode) {
4659               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4660               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4661             }
4662             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4663             strcat(parseList[moveNum - 1], " ");
4664             strcat(parseList[moveNum - 1], elapsed_time);
4665             moveList[moveNum - 1][0] = NULLCHAR;
4666             fromX = fromY = toX = toY = -1;
4667           }
4668         }
4669   if (appData.debugMode) {
4670     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4671     setbuf(debugFP, NULL);
4672   }
4673
4674 #if ZIPPY
4675         /* Send move to chess program (BEFORE animating it). */
4676         if (appData.zippyPlay && !newGame && newMove &&
4677            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4678
4679             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4680                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4681                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4682                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4683                             move_str);
4684                     DisplayError(str, 0);
4685                 } else {
4686                     if (first.sendTime) {
4687                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4688                     }
4689                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4690                     if (firstMove && !bookHit) {
4691                         firstMove = FALSE;
4692                         if (first.useColors) {
4693                           SendToProgram(gameMode == IcsPlayingWhite ?
4694                                         "white\ngo\n" :
4695                                         "black\ngo\n", &first);
4696                         } else {
4697                           SendToProgram("go\n", &first);
4698                         }
4699                         first.maybeThinking = TRUE;
4700                     }
4701                 }
4702             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4703               if (moveList[moveNum - 1][0] == NULLCHAR) {
4704                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4705                 DisplayError(str, 0);
4706               } else {
4707                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4708                 SendMoveToProgram(moveNum - 1, &first);
4709               }
4710             }
4711         }
4712 #endif
4713     }
4714
4715     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4716         /* If move comes from a remote source, animate it.  If it
4717            isn't remote, it will have already been animated. */
4718         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4719             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4720         }
4721         if (!pausing && appData.highlightLastMove) {
4722             SetHighlights(fromX, fromY, toX, toY);
4723         }
4724     }
4725
4726     /* Start the clocks */
4727     whiteFlag = blackFlag = FALSE;
4728     appData.clockMode = !(basetime == 0 && increment == 0);
4729     if (ticking == 0) {
4730       ics_clock_paused = TRUE;
4731       StopClocks();
4732     } else if (ticking == 1) {
4733       ics_clock_paused = FALSE;
4734     }
4735     if (gameMode == IcsIdle ||
4736         relation == RELATION_OBSERVING_STATIC ||
4737         relation == RELATION_EXAMINING ||
4738         ics_clock_paused)
4739       DisplayBothClocks();
4740     else
4741       StartClocks();
4742
4743     /* Display opponents and material strengths */
4744     if (gameInfo.variant != VariantBughouse &&
4745         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4746         if (tinyLayout || smallLayout) {
4747             if(gameInfo.variant == VariantNormal)
4748               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4749                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4750                     basetime, increment);
4751             else
4752               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4753                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4754                     basetime, increment, (int) gameInfo.variant);
4755         } else {
4756             if(gameInfo.variant == VariantNormal)
4757               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4758                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4759                     basetime, increment);
4760             else
4761               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4762                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4763                     basetime, increment, VariantName(gameInfo.variant));
4764         }
4765         DisplayTitle(str);
4766   if (appData.debugMode) {
4767     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4768   }
4769     }
4770
4771
4772     /* Display the board */
4773     if (!pausing && !appData.noGUI) {
4774
4775       if (appData.premove)
4776           if (!gotPremove ||
4777              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4778              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4779               ClearPremoveHighlights();
4780
4781       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4782         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4783       DrawPosition(j, boards[currentMove]);
4784
4785       DisplayMove(moveNum - 1);
4786       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4787             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4788               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4789         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4790       }
4791     }
4792
4793     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4794 #if ZIPPY
4795     if(bookHit) { // [HGM] book: simulate book reply
4796         static char bookMove[MSG_SIZ]; // a bit generous?
4797
4798         programStats.nodes = programStats.depth = programStats.time =
4799         programStats.score = programStats.got_only_move = 0;
4800         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4801
4802         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4803         strcat(bookMove, bookHit);
4804         HandleMachineMove(bookMove, &first);
4805     }
4806 #endif
4807 }
4808
4809 void
4810 GetMoveListEvent()
4811 {
4812     char buf[MSG_SIZ];
4813     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4814         ics_getting_history = H_REQUESTED;
4815         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4816         SendToICS(buf);
4817     }
4818 }
4819
4820 void
4821 AnalysisPeriodicEvent(force)
4822      int force;
4823 {
4824     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4825          && !force) || !appData.periodicUpdates)
4826       return;
4827
4828     /* Send . command to Crafty to collect stats */
4829     SendToProgram(".\n", &first);
4830
4831     /* Don't send another until we get a response (this makes
4832        us stop sending to old Crafty's which don't understand
4833        the "." command (sending illegal cmds resets node count & time,
4834        which looks bad)) */
4835     programStats.ok_to_send = 0;
4836 }
4837
4838 void ics_update_width(new_width)
4839         int new_width;
4840 {
4841         ics_printf("set width %d\n", new_width);
4842 }
4843
4844 void
4845 SendMoveToProgram(moveNum, cps)
4846      int moveNum;
4847      ChessProgramState *cps;
4848 {
4849     char buf[MSG_SIZ];
4850
4851     if (cps->useUsermove) {
4852       SendToProgram("usermove ", cps);
4853     }
4854     if (cps->useSAN) {
4855       char *space;
4856       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4857         int len = space - parseList[moveNum];
4858         memcpy(buf, parseList[moveNum], len);
4859         buf[len++] = '\n';
4860         buf[len] = NULLCHAR;
4861       } else {
4862         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4863       }
4864       SendToProgram(buf, cps);
4865     } else {
4866       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4867         AlphaRank(moveList[moveNum], 4);
4868         SendToProgram(moveList[moveNum], cps);
4869         AlphaRank(moveList[moveNum], 4); // and back
4870       } else
4871       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4872        * the engine. It would be nice to have a better way to identify castle
4873        * moves here. */
4874       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4875                                                                          && cps->useOOCastle) {
4876         int fromX = moveList[moveNum][0] - AAA;
4877         int fromY = moveList[moveNum][1] - ONE;
4878         int toX = moveList[moveNum][2] - AAA;
4879         int toY = moveList[moveNum][3] - ONE;
4880         if((boards[moveNum][fromY][fromX] == WhiteKing
4881             && boards[moveNum][toY][toX] == WhiteRook)
4882            || (boards[moveNum][fromY][fromX] == BlackKing
4883                && boards[moveNum][toY][toX] == BlackRook)) {
4884           if(toX > fromX) SendToProgram("O-O\n", cps);
4885           else SendToProgram("O-O-O\n", cps);
4886         }
4887         else SendToProgram(moveList[moveNum], cps);
4888       }
4889       else SendToProgram(moveList[moveNum], cps);
4890       /* End of additions by Tord */
4891     }
4892
4893     /* [HGM] setting up the opening has brought engine in force mode! */
4894     /*       Send 'go' if we are in a mode where machine should play. */
4895     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4896         (gameMode == TwoMachinesPlay   ||
4897 #if ZIPPY
4898          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4899 #endif
4900          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4901         SendToProgram("go\n", cps);
4902   if (appData.debugMode) {
4903     fprintf(debugFP, "(extra)\n");
4904   }
4905     }
4906     setboardSpoiledMachineBlack = 0;
4907 }
4908
4909 void
4910 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4911      ChessMove moveType;
4912      int fromX, fromY, toX, toY;
4913      char promoChar;
4914 {
4915     char user_move[MSG_SIZ];
4916
4917     switch (moveType) {
4918       default:
4919         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4920                 (int)moveType, fromX, fromY, toX, toY);
4921         DisplayError(user_move + strlen("say "), 0);
4922         break;
4923       case WhiteKingSideCastle:
4924       case BlackKingSideCastle:
4925       case WhiteQueenSideCastleWild:
4926       case BlackQueenSideCastleWild:
4927       /* PUSH Fabien */
4928       case WhiteHSideCastleFR:
4929       case BlackHSideCastleFR:
4930       /* POP Fabien */
4931         snprintf(user_move, MSG_SIZ, "o-o\n");
4932         break;
4933       case WhiteQueenSideCastle:
4934       case BlackQueenSideCastle:
4935       case WhiteKingSideCastleWild:
4936       case BlackKingSideCastleWild:
4937       /* PUSH Fabien */
4938       case WhiteASideCastleFR:
4939       case BlackASideCastleFR:
4940       /* POP Fabien */
4941         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4942         break;
4943       case WhiteNonPromotion:
4944       case BlackNonPromotion:
4945         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4946         break;
4947       case WhitePromotion:
4948       case BlackPromotion:
4949         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4950           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4951                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4952                 PieceToChar(WhiteFerz));
4953         else if(gameInfo.variant == VariantGreat)
4954           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4955                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4956                 PieceToChar(WhiteMan));
4957         else
4958           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4959                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4960                 promoChar);
4961         break;
4962       case WhiteDrop:
4963       case BlackDrop:
4964       drop:
4965         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4966                  ToUpper(PieceToChar((ChessSquare) fromX)),
4967                  AAA + toX, ONE + toY);
4968         break;
4969       case IllegalMove:  /* could be a variant we don't quite understand */
4970         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4971       case NormalMove:
4972       case WhiteCapturesEnPassant:
4973       case BlackCapturesEnPassant:
4974         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4975                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4976         break;
4977     }
4978     SendToICS(user_move);
4979     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4980         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4981 }
4982
4983 void
4984 UploadGameEvent()
4985 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4986     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4987     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4988     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4989         DisplayError("You cannot do this while you are playing or observing", 0);
4990         return;
4991     }
4992     if(gameMode != IcsExamining) { // is this ever not the case?
4993         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4994
4995         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4996           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4997         } else { // on FICS we must first go to general examine mode
4998           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4999         }
5000         if(gameInfo.variant != VariantNormal) {
5001             // try figure out wild number, as xboard names are not always valid on ICS
5002             for(i=1; i<=36; i++) {
5003               snprintf(buf, MSG_SIZ, "wild/%d", i);
5004                 if(StringToVariant(buf) == gameInfo.variant) break;
5005             }
5006             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5007             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5008             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5009         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5010         SendToICS(ics_prefix);
5011         SendToICS(buf);
5012         if(startedFromSetupPosition || backwardMostMove != 0) {
5013           fen = PositionToFEN(backwardMostMove, NULL);
5014           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5015             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5016             SendToICS(buf);
5017           } else { // FICS: everything has to set by separate bsetup commands
5018             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5019             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5020             SendToICS(buf);
5021             if(!WhiteOnMove(backwardMostMove)) {
5022                 SendToICS("bsetup tomove black\n");
5023             }
5024             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5025             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5026             SendToICS(buf);
5027             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5028             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5029             SendToICS(buf);
5030             i = boards[backwardMostMove][EP_STATUS];
5031             if(i >= 0) { // set e.p.
5032               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5033                 SendToICS(buf);
5034             }
5035             bsetup++;
5036           }
5037         }
5038       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5039             SendToICS("bsetup done\n"); // switch to normal examining.
5040     }
5041     for(i = backwardMostMove; i<last; i++) {
5042         char buf[20];
5043         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5044         SendToICS(buf);
5045     }
5046     SendToICS(ics_prefix);
5047     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5048 }
5049
5050 void
5051 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5052      int rf, ff, rt, ft;
5053      char promoChar;
5054      char move[7];
5055 {
5056     if (rf == DROP_RANK) {
5057       sprintf(move, "%c@%c%c\n",
5058                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5059     } else {
5060         if (promoChar == 'x' || promoChar == NULLCHAR) {
5061           sprintf(move, "%c%c%c%c\n",
5062                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5063         } else {
5064             sprintf(move, "%c%c%c%c%c\n",
5065                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5066         }
5067     }
5068 }
5069
5070 void
5071 ProcessICSInitScript(f)
5072      FILE *f;
5073 {
5074     char buf[MSG_SIZ];
5075
5076     while (fgets(buf, MSG_SIZ, f)) {
5077         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5078     }
5079
5080     fclose(f);
5081 }
5082
5083
5084 static int lastX, lastY, selectFlag, dragging;
5085
5086 void
5087 Sweep(int step)
5088 {
5089     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5090     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5091     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5092     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5093     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5094     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5095     do {
5096         promoSweep -= step;
5097         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5098         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5099         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5100         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5101         if(!step) step = 1;
5102     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5103             appData.testLegality && (promoSweep == king ||
5104             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5105     ChangeDragPiece(promoSweep);
5106 }
5107
5108 int PromoScroll(int x, int y)
5109 {
5110   int step = 0;
5111
5112   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5113   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5114   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5115   if(!step) return FALSE;
5116   lastX = x; lastY = y;
5117   if((promoSweep < BlackPawn) == flipView) step = -step;
5118   if(step > 0) selectFlag = 1;
5119   if(!selectFlag) Sweep(step);
5120   return FALSE;
5121 }
5122
5123 void
5124 NextPiece(int step)
5125 {
5126     ChessSquare piece = boards[currentMove][toY][toX];
5127     do {
5128         pieceSweep -= step;
5129         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5130         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5131         if(!step) step = -1;
5132     } while(PieceToChar(pieceSweep) == '.');
5133     boards[currentMove][toY][toX] = pieceSweep;
5134     DrawPosition(FALSE, boards[currentMove]);
5135     boards[currentMove][toY][toX] = piece;
5136 }
5137 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5138 void
5139 AlphaRank(char *move, int n)
5140 {
5141 //    char *p = move, c; int x, y;
5142
5143     if (appData.debugMode) {
5144         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5145     }
5146
5147     if(move[1]=='*' &&
5148        move[2]>='0' && move[2]<='9' &&
5149        move[3]>='a' && move[3]<='x'    ) {
5150         move[1] = '@';
5151         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5152         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5153     } else
5154     if(move[0]>='0' && move[0]<='9' &&
5155        move[1]>='a' && move[1]<='x' &&
5156        move[2]>='0' && move[2]<='9' &&
5157        move[3]>='a' && move[3]<='x'    ) {
5158         /* input move, Shogi -> normal */
5159         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5160         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5161         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5162         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5163     } else
5164     if(move[1]=='@' &&
5165        move[3]>='0' && move[3]<='9' &&
5166        move[2]>='a' && move[2]<='x'    ) {
5167         move[1] = '*';
5168         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5169         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5170     } else
5171     if(
5172        move[0]>='a' && move[0]<='x' &&
5173        move[3]>='0' && move[3]<='9' &&
5174        move[2]>='a' && move[2]<='x'    ) {
5175          /* output move, normal -> Shogi */
5176         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5177         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5178         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5179         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5180         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5181     }
5182     if (appData.debugMode) {
5183         fprintf(debugFP, "   out = '%s'\n", move);
5184     }
5185 }
5186
5187 char yy_textstr[8000];
5188
5189 /* Parser for moves from gnuchess, ICS, or user typein box */
5190 Boolean
5191 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5192      char *move;
5193      int moveNum;
5194      ChessMove *moveType;
5195      int *fromX, *fromY, *toX, *toY;
5196      char *promoChar;
5197 {
5198     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5199
5200     switch (*moveType) {
5201       case WhitePromotion:
5202       case BlackPromotion:
5203       case WhiteNonPromotion:
5204       case BlackNonPromotion:
5205       case NormalMove:
5206       case WhiteCapturesEnPassant:
5207       case BlackCapturesEnPassant:
5208       case WhiteKingSideCastle:
5209       case WhiteQueenSideCastle:
5210       case BlackKingSideCastle:
5211       case BlackQueenSideCastle:
5212       case WhiteKingSideCastleWild:
5213       case WhiteQueenSideCastleWild:
5214       case BlackKingSideCastleWild:
5215       case BlackQueenSideCastleWild:
5216       /* Code added by Tord: */
5217       case WhiteHSideCastleFR:
5218       case WhiteASideCastleFR:
5219       case BlackHSideCastleFR:
5220       case BlackASideCastleFR:
5221       /* End of code added by Tord */
5222       case IllegalMove:         /* bug or odd chess variant */
5223         *fromX = currentMoveString[0] - AAA;
5224         *fromY = currentMoveString[1] - ONE;
5225         *toX = currentMoveString[2] - AAA;
5226         *toY = currentMoveString[3] - ONE;
5227         *promoChar = currentMoveString[4];
5228         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5229             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5230     if (appData.debugMode) {
5231         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5232     }
5233             *fromX = *fromY = *toX = *toY = 0;
5234             return FALSE;
5235         }
5236         if (appData.testLegality) {
5237           return (*moveType != IllegalMove);
5238         } else {
5239           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5240                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5241         }
5242
5243       case WhiteDrop:
5244       case BlackDrop:
5245         *fromX = *moveType == WhiteDrop ?
5246           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5247           (int) CharToPiece(ToLower(currentMoveString[0]));
5248         *fromY = DROP_RANK;
5249         *toX = currentMoveString[2] - AAA;
5250         *toY = currentMoveString[3] - ONE;
5251         *promoChar = NULLCHAR;
5252         return TRUE;
5253
5254       case AmbiguousMove:
5255       case ImpossibleMove:
5256       case EndOfFile:
5257       case ElapsedTime:
5258       case Comment:
5259       case PGNTag:
5260       case NAG:
5261       case WhiteWins:
5262       case BlackWins:
5263       case GameIsDrawn:
5264       default:
5265     if (appData.debugMode) {
5266         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5267     }
5268         /* bug? */
5269         *fromX = *fromY = *toX = *toY = 0;
5270         *promoChar = NULLCHAR;
5271         return FALSE;
5272     }
5273 }
5274
5275 Boolean pushed = FALSE;
5276
5277 void
5278 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5279 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5280   int fromX, fromY, toX, toY; char promoChar;
5281   ChessMove moveType;
5282   Boolean valid;
5283   int nr = 0;
5284
5285   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5286     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5287     pushed = TRUE;
5288   }
5289   endPV = forwardMostMove;
5290   do {
5291     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5292     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5293     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5294 if(appData.debugMode){
5295 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);
5296 }
5297     if(!valid && nr == 0 &&
5298        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5299         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5300         // Hande case where played move is different from leading PV move
5301         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5302         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5303         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5304         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5305           endPV += 2; // if position different, keep this
5306           moveList[endPV-1][0] = fromX + AAA;
5307           moveList[endPV-1][1] = fromY + ONE;
5308           moveList[endPV-1][2] = toX + AAA;
5309           moveList[endPV-1][3] = toY + ONE;
5310           parseList[endPV-1][0] = NULLCHAR;
5311           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5312         }
5313       }
5314     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5315     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5316     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5317     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5318         valid++; // allow comments in PV
5319         continue;
5320     }
5321     nr++;
5322     if(endPV+1 > framePtr) break; // no space, truncate
5323     if(!valid) break;
5324     endPV++;
5325     CopyBoard(boards[endPV], boards[endPV-1]);
5326     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5327     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5328     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5329     CoordsToAlgebraic(boards[endPV - 1],
5330                              PosFlags(endPV - 1),
5331                              fromY, fromX, toY, toX, promoChar,
5332                              parseList[endPV - 1]);
5333   } while(valid);
5334   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5335   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5336   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5337                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5338   DrawPosition(TRUE, boards[currentMove]);
5339 }
5340
5341 int
5342 MultiPV(ChessProgramState *cps)
5343 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5344         int i;
5345         for(i=0; i<cps->nrOptions; i++)
5346             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5347                 return i;
5348         return -1;
5349 }
5350
5351 Boolean
5352 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5353 {
5354         int startPV, multi, lineStart, origIndex = index;
5355         char *p, buf2[MSG_SIZ];
5356
5357         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5358         lastX = x; lastY = y;
5359         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5360         lineStart = startPV = index;
5361         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5362         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5363         index = startPV;
5364         do{ while(buf[index] && buf[index] != '\n') index++;
5365         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5366         buf[index] = 0;
5367         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5368                 int n = first.option[multi].value;
5369                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5370                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5371                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5372                 first.option[multi].value = n;
5373                 *start = *end = 0;
5374                 return FALSE;
5375         }
5376         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5377         *start = startPV; *end = index-1;
5378         return TRUE;
5379 }
5380
5381 Boolean
5382 LoadPV(int x, int y)
5383 { // called on right mouse click to load PV
5384   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5385   lastX = x; lastY = y;
5386   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5387   return TRUE;
5388 }
5389
5390 void
5391 UnLoadPV()
5392 {
5393   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5394   if(endPV < 0) return;
5395   endPV = -1;
5396   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5397         Boolean saveAnimate = appData.animate;
5398         if(pushed) {
5399             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5400                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5401             } else storedGames--; // abandon shelved tail of original game
5402         }
5403         pushed = FALSE;
5404         forwardMostMove = currentMove;
5405         currentMove = oldFMM;
5406         appData.animate = FALSE;
5407         ToNrEvent(forwardMostMove);
5408         appData.animate = saveAnimate;
5409   }
5410   currentMove = forwardMostMove;
5411   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5412   ClearPremoveHighlights();
5413   DrawPosition(TRUE, boards[currentMove]);
5414 }
5415
5416 void
5417 MovePV(int x, int y, int h)
5418 { // step through PV based on mouse coordinates (called on mouse move)
5419   int margin = h>>3, step = 0;
5420
5421   // we must somehow check if right button is still down (might be released off board!)
5422   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5423   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5424   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5425   if(!step) return;
5426   lastX = x; lastY = y;
5427
5428   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5429   if(endPV < 0) return;
5430   if(y < margin) step = 1; else
5431   if(y > h - margin) step = -1;
5432   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5433   currentMove += step;
5434   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5435   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5436                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5437   DrawPosition(FALSE, boards[currentMove]);
5438 }
5439
5440
5441 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5442 // All positions will have equal probability, but the current method will not provide a unique
5443 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5444 #define DARK 1
5445 #define LITE 2
5446 #define ANY 3
5447
5448 int squaresLeft[4];
5449 int piecesLeft[(int)BlackPawn];
5450 int seed, nrOfShuffles;
5451
5452 void GetPositionNumber()
5453 {       // sets global variable seed
5454         int i;
5455
5456         seed = appData.defaultFrcPosition;
5457         if(seed < 0) { // randomize based on time for negative FRC position numbers
5458                 for(i=0; i<50; i++) seed += random();
5459                 seed = random() ^ random() >> 8 ^ random() << 8;
5460                 if(seed<0) seed = -seed;
5461         }
5462 }
5463
5464 int put(Board board, int pieceType, int rank, int n, int shade)
5465 // put the piece on the (n-1)-th empty squares of the given shade
5466 {
5467         int i;
5468
5469         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5470                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5471                         board[rank][i] = (ChessSquare) pieceType;
5472                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5473                         squaresLeft[ANY]--;
5474                         piecesLeft[pieceType]--;
5475                         return i;
5476                 }
5477         }
5478         return -1;
5479 }
5480
5481
5482 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5483 // calculate where the next piece goes, (any empty square), and put it there
5484 {
5485         int i;
5486
5487         i = seed % squaresLeft[shade];
5488         nrOfShuffles *= squaresLeft[shade];
5489         seed /= squaresLeft[shade];
5490         put(board, pieceType, rank, i, shade);
5491 }
5492
5493 void AddTwoPieces(Board board, int pieceType, int rank)
5494 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5495 {
5496         int i, n=squaresLeft[ANY], j=n-1, k;
5497
5498         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5499         i = seed % k;  // pick one
5500         nrOfShuffles *= k;
5501         seed /= k;
5502         while(i >= j) i -= j--;
5503         j = n - 1 - j; i += j;
5504         put(board, pieceType, rank, j, ANY);
5505         put(board, pieceType, rank, i, ANY);
5506 }
5507
5508 void SetUpShuffle(Board board, int number)
5509 {
5510         int i, p, first=1;
5511
5512         GetPositionNumber(); nrOfShuffles = 1;
5513
5514         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5515         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5516         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5517
5518         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5519
5520         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5521             p = (int) board[0][i];
5522             if(p < (int) BlackPawn) piecesLeft[p] ++;
5523             board[0][i] = EmptySquare;
5524         }
5525
5526         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5527             // shuffles restricted to allow normal castling put KRR first
5528             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5529                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5530             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5531                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5532             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5533                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5534             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5535                 put(board, WhiteRook, 0, 0, ANY);
5536             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5537         }
5538
5539         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5540             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5541             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5542                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5543                 while(piecesLeft[p] >= 2) {
5544                     AddOnePiece(board, p, 0, LITE);
5545                     AddOnePiece(board, p, 0, DARK);
5546                 }
5547                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5548             }
5549
5550         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5551             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5552             // but we leave King and Rooks for last, to possibly obey FRC restriction
5553             if(p == (int)WhiteRook) continue;
5554             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5555             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5556         }
5557
5558         // now everything is placed, except perhaps King (Unicorn) and Rooks
5559
5560         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5561             // Last King gets castling rights
5562             while(piecesLeft[(int)WhiteUnicorn]) {
5563                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5564                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5565             }
5566
5567             while(piecesLeft[(int)WhiteKing]) {
5568                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5569                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5570             }
5571
5572
5573         } else {
5574             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5575             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5576         }
5577
5578         // Only Rooks can be left; simply place them all
5579         while(piecesLeft[(int)WhiteRook]) {
5580                 i = put(board, WhiteRook, 0, 0, ANY);
5581                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5582                         if(first) {
5583                                 first=0;
5584                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5585                         }
5586                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5587                 }
5588         }
5589         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5590             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5591         }
5592
5593         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5594 }
5595
5596 int SetCharTable( char *table, const char * map )
5597 /* [HGM] moved here from winboard.c because of its general usefulness */
5598 /*       Basically a safe strcpy that uses the last character as King */
5599 {
5600     int result = FALSE; int NrPieces;
5601
5602     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5603                     && NrPieces >= 12 && !(NrPieces&1)) {
5604         int i; /* [HGM] Accept even length from 12 to 34 */
5605
5606         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5607         for( i=0; i<NrPieces/2-1; i++ ) {
5608             table[i] = map[i];
5609             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5610         }
5611         table[(int) WhiteKing]  = map[NrPieces/2-1];
5612         table[(int) BlackKing]  = map[NrPieces-1];
5613
5614         result = TRUE;
5615     }
5616
5617     return result;
5618 }
5619
5620 void Prelude(Board board)
5621 {       // [HGM] superchess: random selection of exo-pieces
5622         int i, j, k; ChessSquare p;
5623         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5624
5625         GetPositionNumber(); // use FRC position number
5626
5627         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5628             SetCharTable(pieceToChar, appData.pieceToCharTable);
5629             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5630                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5631         }
5632
5633         j = seed%4;                 seed /= 4;
5634         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5635         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5636         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5637         j = seed%3 + (seed%3 >= j); seed /= 3;
5638         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5639         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5640         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5641         j = seed%3;                 seed /= 3;
5642         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5643         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5644         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5645         j = seed%2 + (seed%2 >= j); seed /= 2;
5646         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5647         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5648         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5649         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5650         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5651         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5652         put(board, exoPieces[0],    0, 0, ANY);
5653         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5654 }
5655
5656 void
5657 InitPosition(redraw)
5658      int redraw;
5659 {
5660     ChessSquare (* pieces)[BOARD_FILES];
5661     int i, j, pawnRow, overrule,
5662     oldx = gameInfo.boardWidth,
5663     oldy = gameInfo.boardHeight,
5664     oldh = gameInfo.holdingsWidth;
5665     static int oldv;
5666
5667     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5668
5669     /* [AS] Initialize pv info list [HGM] and game status */
5670     {
5671         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5672             pvInfoList[i].depth = 0;
5673             boards[i][EP_STATUS] = EP_NONE;
5674             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5675         }
5676
5677         initialRulePlies = 0; /* 50-move counter start */
5678
5679         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5680         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5681     }
5682
5683
5684     /* [HGM] logic here is completely changed. In stead of full positions */
5685     /* the initialized data only consist of the two backranks. The switch */
5686     /* selects which one we will use, which is than copied to the Board   */
5687     /* initialPosition, which for the rest is initialized by Pawns and    */
5688     /* empty squares. This initial position is then copied to boards[0],  */
5689     /* possibly after shuffling, so that it remains available.            */
5690
5691     gameInfo.holdingsWidth = 0; /* default board sizes */
5692     gameInfo.boardWidth    = 8;
5693     gameInfo.boardHeight   = 8;
5694     gameInfo.holdingsSize  = 0;
5695     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5696     for(i=0; i<BOARD_FILES-2; i++)
5697       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5698     initialPosition[EP_STATUS] = EP_NONE;
5699     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5700     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5701          SetCharTable(pieceNickName, appData.pieceNickNames);
5702     else SetCharTable(pieceNickName, "............");
5703     pieces = FIDEArray;
5704
5705     switch (gameInfo.variant) {
5706     case VariantFischeRandom:
5707       shuffleOpenings = TRUE;
5708     default:
5709       break;
5710     case VariantShatranj:
5711       pieces = ShatranjArray;
5712       nrCastlingRights = 0;
5713       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5714       break;
5715     case VariantMakruk:
5716       pieces = makrukArray;
5717       nrCastlingRights = 0;
5718       startedFromSetupPosition = TRUE;
5719       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5720       break;
5721     case VariantTwoKings:
5722       pieces = twoKingsArray;
5723       break;
5724     case VariantCapaRandom:
5725       shuffleOpenings = TRUE;
5726     case VariantCapablanca:
5727       pieces = CapablancaArray;
5728       gameInfo.boardWidth = 10;
5729       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5730       break;
5731     case VariantGothic:
5732       pieces = GothicArray;
5733       gameInfo.boardWidth = 10;
5734       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5735       break;
5736     case VariantSChess:
5737       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5738       gameInfo.holdingsSize = 7;
5739       break;
5740     case VariantJanus:
5741       pieces = JanusArray;
5742       gameInfo.boardWidth = 10;
5743       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5744       nrCastlingRights = 6;
5745         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5746         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5747         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5748         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5749         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5750         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5751       break;
5752     case VariantFalcon:
5753       pieces = FalconArray;
5754       gameInfo.boardWidth = 10;
5755       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5756       break;
5757     case VariantXiangqi:
5758       pieces = XiangqiArray;
5759       gameInfo.boardWidth  = 9;
5760       gameInfo.boardHeight = 10;
5761       nrCastlingRights = 0;
5762       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5763       break;
5764     case VariantShogi:
5765       pieces = ShogiArray;
5766       gameInfo.boardWidth  = 9;
5767       gameInfo.boardHeight = 9;
5768       gameInfo.holdingsSize = 7;
5769       nrCastlingRights = 0;
5770       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5771       break;
5772     case VariantCourier:
5773       pieces = CourierArray;
5774       gameInfo.boardWidth  = 12;
5775       nrCastlingRights = 0;
5776       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5777       break;
5778     case VariantKnightmate:
5779       pieces = KnightmateArray;
5780       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5781       break;
5782     case VariantSpartan:
5783       pieces = SpartanArray;
5784       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5785       break;
5786     case VariantFairy:
5787       pieces = fairyArray;
5788       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5789       break;
5790     case VariantGreat:
5791       pieces = GreatArray;
5792       gameInfo.boardWidth = 10;
5793       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5794       gameInfo.holdingsSize = 8;
5795       break;
5796     case VariantSuper:
5797       pieces = FIDEArray;
5798       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5799       gameInfo.holdingsSize = 8;
5800       startedFromSetupPosition = TRUE;
5801       break;
5802     case VariantCrazyhouse:
5803     case VariantBughouse:
5804       pieces = FIDEArray;
5805       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5806       gameInfo.holdingsSize = 5;
5807       break;
5808     case VariantWildCastle:
5809       pieces = FIDEArray;
5810       /* !!?shuffle with kings guaranteed to be on d or e file */
5811       shuffleOpenings = 1;
5812       break;
5813     case VariantNoCastle:
5814       pieces = FIDEArray;
5815       nrCastlingRights = 0;
5816       /* !!?unconstrained back-rank shuffle */
5817       shuffleOpenings = 1;
5818       break;
5819     }
5820
5821     overrule = 0;
5822     if(appData.NrFiles >= 0) {
5823         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5824         gameInfo.boardWidth = appData.NrFiles;
5825     }
5826     if(appData.NrRanks >= 0) {
5827         gameInfo.boardHeight = appData.NrRanks;
5828     }
5829     if(appData.holdingsSize >= 0) {
5830         i = appData.holdingsSize;
5831         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5832         gameInfo.holdingsSize = i;
5833     }
5834     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5835     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5836         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5837
5838     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5839     if(pawnRow < 1) pawnRow = 1;
5840     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5841
5842     /* User pieceToChar list overrules defaults */
5843     if(appData.pieceToCharTable != NULL)
5844         SetCharTable(pieceToChar, appData.pieceToCharTable);
5845
5846     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5847
5848         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5849             s = (ChessSquare) 0; /* account holding counts in guard band */
5850         for( i=0; i<BOARD_HEIGHT; i++ )
5851             initialPosition[i][j] = s;
5852
5853         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5854         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5855         initialPosition[pawnRow][j] = WhitePawn;
5856         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5857         if(gameInfo.variant == VariantXiangqi) {
5858             if(j&1) {
5859                 initialPosition[pawnRow][j] =
5860                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5861                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5862                    initialPosition[2][j] = WhiteCannon;
5863                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5864                 }
5865             }
5866         }
5867         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5868     }
5869     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5870
5871             j=BOARD_LEFT+1;
5872             initialPosition[1][j] = WhiteBishop;
5873             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5874             j=BOARD_RGHT-2;
5875             initialPosition[1][j] = WhiteRook;
5876             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5877     }
5878
5879     if( nrCastlingRights == -1) {
5880         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5881         /*       This sets default castling rights from none to normal corners   */
5882         /* Variants with other castling rights must set them themselves above    */
5883         nrCastlingRights = 6;
5884
5885         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5886         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5887         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5888         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5889         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5890         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5891      }
5892
5893      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5894      if(gameInfo.variant == VariantGreat) { // promotion commoners
5895         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5896         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5897         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5898         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5899      }
5900      if( gameInfo.variant == VariantSChess ) {
5901       initialPosition[1][0] = BlackMarshall;
5902       initialPosition[2][0] = BlackAngel;
5903       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5904       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5905       initialPosition[1][1] = initialPosition[2][1] = 
5906       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5907      }
5908   if (appData.debugMode) {
5909     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5910   }
5911     if(shuffleOpenings) {
5912         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5913         startedFromSetupPosition = TRUE;
5914     }
5915     if(startedFromPositionFile) {
5916       /* [HGM] loadPos: use PositionFile for every new game */
5917       CopyBoard(initialPosition, filePosition);
5918       for(i=0; i<nrCastlingRights; i++)
5919           initialRights[i] = filePosition[CASTLING][i];
5920       startedFromSetupPosition = TRUE;
5921     }
5922
5923     CopyBoard(boards[0], initialPosition);
5924
5925     if(oldx != gameInfo.boardWidth ||
5926        oldy != gameInfo.boardHeight ||
5927        oldv != gameInfo.variant ||
5928        oldh != gameInfo.holdingsWidth
5929                                          )
5930             InitDrawingSizes(-2 ,0);
5931
5932     oldv = gameInfo.variant;
5933     if (redraw)
5934       DrawPosition(TRUE, boards[currentMove]);
5935 }
5936
5937 void
5938 SendBoard(cps, moveNum)
5939      ChessProgramState *cps;
5940      int moveNum;
5941 {
5942     char message[MSG_SIZ];
5943
5944     if (cps->useSetboard) {
5945       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5946       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5947       SendToProgram(message, cps);
5948       free(fen);
5949
5950     } else {
5951       ChessSquare *bp;
5952       int i, j;
5953       /* Kludge to set black to move, avoiding the troublesome and now
5954        * deprecated "black" command.
5955        */
5956       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5957         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5958
5959       SendToProgram("edit\n", cps);
5960       SendToProgram("#\n", cps);
5961       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5962         bp = &boards[moveNum][i][BOARD_LEFT];
5963         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5964           if ((int) *bp < (int) BlackPawn) {
5965             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5966                     AAA + j, ONE + i);
5967             if(message[0] == '+' || message[0] == '~') {
5968               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5969                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5970                         AAA + j, ONE + i);
5971             }
5972             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5973                 message[1] = BOARD_RGHT   - 1 - j + '1';
5974                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5975             }
5976             SendToProgram(message, cps);
5977           }
5978         }
5979       }
5980
5981       SendToProgram("c\n", cps);
5982       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5983         bp = &boards[moveNum][i][BOARD_LEFT];
5984         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5985           if (((int) *bp != (int) EmptySquare)
5986               && ((int) *bp >= (int) BlackPawn)) {
5987             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5988                     AAA + j, ONE + i);
5989             if(message[0] == '+' || message[0] == '~') {
5990               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5991                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5992                         AAA + j, ONE + i);
5993             }
5994             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5995                 message[1] = BOARD_RGHT   - 1 - j + '1';
5996                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5997             }
5998             SendToProgram(message, cps);
5999           }
6000         }
6001       }
6002
6003       SendToProgram(".\n", cps);
6004     }
6005     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6006 }
6007
6008 ChessSquare
6009 DefaultPromoChoice(int white)
6010 {
6011     ChessSquare result;
6012     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6013         result = WhiteFerz; // no choice
6014     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6015         result= WhiteKing; // in Suicide Q is the last thing we want
6016     else if(gameInfo.variant == VariantSpartan)
6017         result = white ? WhiteQueen : WhiteAngel;
6018     else result = WhiteQueen;
6019     if(!white) result = WHITE_TO_BLACK result;
6020     return result;
6021 }
6022
6023 static int autoQueen; // [HGM] oneclick
6024
6025 int
6026 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6027 {
6028     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6029     /* [HGM] add Shogi promotions */
6030     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6031     ChessSquare piece;
6032     ChessMove moveType;
6033     Boolean premove;
6034
6035     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6036     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6037
6038     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6039       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6040         return FALSE;
6041
6042     piece = boards[currentMove][fromY][fromX];
6043     if(gameInfo.variant == VariantShogi) {
6044         promotionZoneSize = BOARD_HEIGHT/3;
6045         highestPromotingPiece = (int)WhiteFerz;
6046     } else if(gameInfo.variant == VariantMakruk) {
6047         promotionZoneSize = 3;
6048     }
6049
6050     // Treat Lance as Pawn when it is not representing Amazon
6051     if(gameInfo.variant != VariantSuper) {
6052         if(piece == WhiteLance) piece = WhitePawn; else
6053         if(piece == BlackLance) piece = BlackPawn;
6054     }
6055
6056     // next weed out all moves that do not touch the promotion zone at all
6057     if((int)piece >= BlackPawn) {
6058         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6059              return FALSE;
6060         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6061     } else {
6062         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6063            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6064     }
6065
6066     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6067
6068     // weed out mandatory Shogi promotions
6069     if(gameInfo.variant == VariantShogi) {
6070         if(piece >= BlackPawn) {
6071             if(toY == 0 && piece == BlackPawn ||
6072                toY == 0 && piece == BlackQueen ||
6073                toY <= 1 && piece == BlackKnight) {
6074                 *promoChoice = '+';
6075                 return FALSE;
6076             }
6077         } else {
6078             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6079                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6080                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6081                 *promoChoice = '+';
6082                 return FALSE;
6083             }
6084         }
6085     }
6086
6087     // weed out obviously illegal Pawn moves
6088     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6089         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6090         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6091         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6092         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6093         // note we are not allowed to test for valid (non-)capture, due to premove
6094     }
6095
6096     // we either have a choice what to promote to, or (in Shogi) whether to promote
6097     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6098         *promoChoice = PieceToChar(BlackFerz);  // no choice
6099         return FALSE;
6100     }
6101     // no sense asking what we must promote to if it is going to explode...
6102     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6103         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6104         return FALSE;
6105     }
6106     // give caller the default choice even if we will not make it
6107     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6108     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6109     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6110                            && gameInfo.variant != VariantShogi
6111                            && gameInfo.variant != VariantSuper) return FALSE;
6112     if(autoQueen) return FALSE; // predetermined
6113
6114     // suppress promotion popup on illegal moves that are not premoves
6115     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6116               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6117     if(appData.testLegality && !premove) {
6118         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6119                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6120         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6121             return FALSE;
6122     }
6123
6124     return TRUE;
6125 }
6126
6127 int
6128 InPalace(row, column)
6129      int row, column;
6130 {   /* [HGM] for Xiangqi */
6131     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6132          column < (BOARD_WIDTH + 4)/2 &&
6133          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6134     return FALSE;
6135 }
6136
6137 int
6138 PieceForSquare (x, y)
6139      int x;
6140      int y;
6141 {
6142   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6143      return -1;
6144   else
6145      return boards[currentMove][y][x];
6146 }
6147
6148 int
6149 OKToStartUserMove(x, y)
6150      int x, y;
6151 {
6152     ChessSquare from_piece;
6153     int white_piece;
6154
6155     if (matchMode) return FALSE;
6156     if (gameMode == EditPosition) return TRUE;
6157
6158     if (x >= 0 && y >= 0)
6159       from_piece = boards[currentMove][y][x];
6160     else
6161       from_piece = EmptySquare;
6162
6163     if (from_piece == EmptySquare) return FALSE;
6164
6165     white_piece = (int)from_piece >= (int)WhitePawn &&
6166       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6167
6168     switch (gameMode) {
6169       case PlayFromGameFile:
6170       case AnalyzeFile:
6171       case TwoMachinesPlay:
6172       case EndOfGame:
6173         return FALSE;
6174
6175       case IcsObserving:
6176       case IcsIdle:
6177         return FALSE;
6178
6179       case MachinePlaysWhite:
6180       case IcsPlayingBlack:
6181         if (appData.zippyPlay) return FALSE;
6182         if (white_piece) {
6183             DisplayMoveError(_("You are playing Black"));
6184             return FALSE;
6185         }
6186         break;
6187
6188       case MachinePlaysBlack:
6189       case IcsPlayingWhite:
6190         if (appData.zippyPlay) return FALSE;
6191         if (!white_piece) {
6192             DisplayMoveError(_("You are playing White"));
6193             return FALSE;
6194         }
6195         break;
6196
6197       case EditGame:
6198         if (!white_piece && WhiteOnMove(currentMove)) {
6199             DisplayMoveError(_("It is White's turn"));
6200             return FALSE;
6201         }
6202         if (white_piece && !WhiteOnMove(currentMove)) {
6203             DisplayMoveError(_("It is Black's turn"));
6204             return FALSE;
6205         }
6206         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6207             /* Editing correspondence game history */
6208             /* Could disallow this or prompt for confirmation */
6209             cmailOldMove = -1;
6210         }
6211         break;
6212
6213       case BeginningOfGame:
6214         if (appData.icsActive) return FALSE;
6215         if (!appData.noChessProgram) {
6216             if (!white_piece) {
6217                 DisplayMoveError(_("You are playing White"));
6218                 return FALSE;
6219             }
6220         }
6221         break;
6222
6223       case Training:
6224         if (!white_piece && WhiteOnMove(currentMove)) {
6225             DisplayMoveError(_("It is White's turn"));
6226             return FALSE;
6227         }
6228         if (white_piece && !WhiteOnMove(currentMove)) {
6229             DisplayMoveError(_("It is Black's turn"));
6230             return FALSE;
6231         }
6232         break;
6233
6234       default:
6235       case IcsExamining:
6236         break;
6237     }
6238     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6239         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6240         && gameMode != AnalyzeFile && gameMode != Training) {
6241         DisplayMoveError(_("Displayed position is not current"));
6242         return FALSE;
6243     }
6244     return TRUE;
6245 }
6246
6247 Boolean
6248 OnlyMove(int *x, int *y, Boolean captures) {
6249     DisambiguateClosure cl;
6250     if (appData.zippyPlay) return FALSE;
6251     switch(gameMode) {
6252       case MachinePlaysBlack:
6253       case IcsPlayingWhite:
6254       case BeginningOfGame:
6255         if(!WhiteOnMove(currentMove)) return FALSE;
6256         break;
6257       case MachinePlaysWhite:
6258       case IcsPlayingBlack:
6259         if(WhiteOnMove(currentMove)) return FALSE;
6260         break;
6261       case EditGame:
6262         break;
6263       default:
6264         return FALSE;
6265     }
6266     cl.pieceIn = EmptySquare;
6267     cl.rfIn = *y;
6268     cl.ffIn = *x;
6269     cl.rtIn = -1;
6270     cl.ftIn = -1;
6271     cl.promoCharIn = NULLCHAR;
6272     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6273     if( cl.kind == NormalMove ||
6274         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6275         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6276         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6277       fromX = cl.ff;
6278       fromY = cl.rf;
6279       *x = cl.ft;
6280       *y = cl.rt;
6281       return TRUE;
6282     }
6283     if(cl.kind != ImpossibleMove) return FALSE;
6284     cl.pieceIn = EmptySquare;
6285     cl.rfIn = -1;
6286     cl.ffIn = -1;
6287     cl.rtIn = *y;
6288     cl.ftIn = *x;
6289     cl.promoCharIn = NULLCHAR;
6290     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6291     if( cl.kind == NormalMove ||
6292         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6293         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6294         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6295       fromX = cl.ff;
6296       fromY = cl.rf;
6297       *x = cl.ft;
6298       *y = cl.rt;
6299       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6300       return TRUE;
6301     }
6302     return FALSE;
6303 }
6304
6305 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6306 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6307 int lastLoadGameUseList = FALSE;
6308 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6309 ChessMove lastLoadGameStart = EndOfFile;
6310
6311 void
6312 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6313      int fromX, fromY, toX, toY;
6314      int promoChar;
6315 {
6316     ChessMove moveType;
6317     ChessSquare pdown, pup;
6318
6319     /* Check if the user is playing in turn.  This is complicated because we
6320        let the user "pick up" a piece before it is his turn.  So the piece he
6321        tried to pick up may have been captured by the time he puts it down!
6322        Therefore we use the color the user is supposed to be playing in this
6323        test, not the color of the piece that is currently on the starting
6324        square---except in EditGame mode, where the user is playing both
6325        sides; fortunately there the capture race can't happen.  (It can
6326        now happen in IcsExamining mode, but that's just too bad.  The user
6327        will get a somewhat confusing message in that case.)
6328        */
6329
6330     switch (gameMode) {
6331       case PlayFromGameFile:
6332       case AnalyzeFile:
6333       case TwoMachinesPlay:
6334       case EndOfGame:
6335       case IcsObserving:
6336       case IcsIdle:
6337         /* We switched into a game mode where moves are not accepted,
6338            perhaps while the mouse button was down. */
6339         return;
6340
6341       case MachinePlaysWhite:
6342         /* User is moving for Black */
6343         if (WhiteOnMove(currentMove)) {
6344             DisplayMoveError(_("It is White's turn"));
6345             return;
6346         }
6347         break;
6348
6349       case MachinePlaysBlack:
6350         /* User is moving for White */
6351         if (!WhiteOnMove(currentMove)) {
6352             DisplayMoveError(_("It is Black's turn"));
6353             return;
6354         }
6355         break;
6356
6357       case EditGame:
6358       case IcsExamining:
6359       case BeginningOfGame:
6360       case AnalyzeMode:
6361       case Training:
6362         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6363         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6364             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6365             /* User is moving for Black */
6366             if (WhiteOnMove(currentMove)) {
6367                 DisplayMoveError(_("It is White's turn"));
6368                 return;
6369             }
6370         } else {
6371             /* User is moving for White */
6372             if (!WhiteOnMove(currentMove)) {
6373                 DisplayMoveError(_("It is Black's turn"));
6374                 return;
6375             }
6376         }
6377         break;
6378
6379       case IcsPlayingBlack:
6380         /* User is moving for Black */
6381         if (WhiteOnMove(currentMove)) {
6382             if (!appData.premove) {
6383                 DisplayMoveError(_("It is White's turn"));
6384             } else if (toX >= 0 && toY >= 0) {
6385                 premoveToX = toX;
6386                 premoveToY = toY;
6387                 premoveFromX = fromX;
6388                 premoveFromY = fromY;
6389                 premovePromoChar = promoChar;
6390                 gotPremove = 1;
6391                 if (appData.debugMode)
6392                     fprintf(debugFP, "Got premove: fromX %d,"
6393                             "fromY %d, toX %d, toY %d\n",
6394                             fromX, fromY, toX, toY);
6395             }
6396             return;
6397         }
6398         break;
6399
6400       case IcsPlayingWhite:
6401         /* User is moving for White */
6402         if (!WhiteOnMove(currentMove)) {
6403             if (!appData.premove) {
6404                 DisplayMoveError(_("It is Black's turn"));
6405             } else if (toX >= 0 && toY >= 0) {
6406                 premoveToX = toX;
6407                 premoveToY = toY;
6408                 premoveFromX = fromX;
6409                 premoveFromY = fromY;
6410                 premovePromoChar = promoChar;
6411                 gotPremove = 1;
6412                 if (appData.debugMode)
6413                     fprintf(debugFP, "Got premove: fromX %d,"
6414                             "fromY %d, toX %d, toY %d\n",
6415                             fromX, fromY, toX, toY);
6416             }
6417             return;
6418         }
6419         break;
6420
6421       default:
6422         break;
6423
6424       case EditPosition:
6425         /* EditPosition, empty square, or different color piece;
6426            click-click move is possible */
6427         if (toX == -2 || toY == -2) {
6428             boards[0][fromY][fromX] = EmptySquare;
6429             DrawPosition(FALSE, boards[currentMove]);
6430             return;
6431         } else if (toX >= 0 && toY >= 0) {
6432             boards[0][toY][toX] = boards[0][fromY][fromX];
6433             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6434                 if(boards[0][fromY][0] != EmptySquare) {
6435                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6436                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6437                 }
6438             } else
6439             if(fromX == BOARD_RGHT+1) {
6440                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6441                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6442                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6443                 }
6444             } else
6445             boards[0][fromY][fromX] = EmptySquare;
6446             DrawPosition(FALSE, boards[currentMove]);
6447             return;
6448         }
6449         return;
6450     }
6451
6452     if(toX < 0 || toY < 0) return;
6453     pdown = boards[currentMove][fromY][fromX];
6454     pup = boards[currentMove][toY][toX];
6455
6456     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6457     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6458          if( pup != EmptySquare ) return;
6459          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6460            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6461                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6462            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6463            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6464            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6465            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6466          fromY = DROP_RANK;
6467     }
6468
6469     /* [HGM] always test for legality, to get promotion info */
6470     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6471                                          fromY, fromX, toY, toX, promoChar);
6472     /* [HGM] but possibly ignore an IllegalMove result */
6473     if (appData.testLegality) {
6474         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6475             DisplayMoveError(_("Illegal move"));
6476             return;
6477         }
6478     }
6479
6480     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6481 }
6482
6483 /* Common tail of UserMoveEvent and DropMenuEvent */
6484 int
6485 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6486      ChessMove moveType;
6487      int fromX, fromY, toX, toY;
6488      /*char*/int promoChar;
6489 {
6490     char *bookHit = 0;
6491
6492     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6493         // [HGM] superchess: suppress promotions to non-available piece
6494         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6495         if(WhiteOnMove(currentMove)) {
6496             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6497         } else {
6498             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6499         }
6500     }
6501
6502     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6503        move type in caller when we know the move is a legal promotion */
6504     if(moveType == NormalMove && promoChar)
6505         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6506
6507     /* [HGM] <popupFix> The following if has been moved here from
6508        UserMoveEvent(). Because it seemed to belong here (why not allow
6509        piece drops in training games?), and because it can only be
6510        performed after it is known to what we promote. */
6511     if (gameMode == Training) {
6512       /* compare the move played on the board to the next move in the
6513        * game. If they match, display the move and the opponent's response.
6514        * If they don't match, display an error message.
6515        */
6516       int saveAnimate;
6517       Board testBoard;
6518       CopyBoard(testBoard, boards[currentMove]);
6519       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6520
6521       if (CompareBoards(testBoard, boards[currentMove+1])) {
6522         ForwardInner(currentMove+1);
6523
6524         /* Autoplay the opponent's response.
6525          * if appData.animate was TRUE when Training mode was entered,
6526          * the response will be animated.
6527          */
6528         saveAnimate = appData.animate;
6529         appData.animate = animateTraining;
6530         ForwardInner(currentMove+1);
6531         appData.animate = saveAnimate;
6532
6533         /* check for the end of the game */
6534         if (currentMove >= forwardMostMove) {
6535           gameMode = PlayFromGameFile;
6536           ModeHighlight();
6537           SetTrainingModeOff();
6538           DisplayInformation(_("End of game"));
6539         }
6540       } else {
6541         DisplayError(_("Incorrect move"), 0);
6542       }
6543       return 1;
6544     }
6545
6546   /* Ok, now we know that the move is good, so we can kill
6547      the previous line in Analysis Mode */
6548   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6549                                 && currentMove < forwardMostMove) {
6550     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6551     else forwardMostMove = currentMove;
6552   }
6553
6554   /* If we need the chess program but it's dead, restart it */
6555   ResurrectChessProgram();
6556
6557   /* A user move restarts a paused game*/
6558   if (pausing)
6559     PauseEvent();
6560
6561   thinkOutput[0] = NULLCHAR;
6562
6563   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6564
6565   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6566     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6567     return 1;
6568   }
6569
6570   if (gameMode == BeginningOfGame) {
6571     if (appData.noChessProgram) {
6572       gameMode = EditGame;
6573       SetGameInfo();
6574     } else {
6575       char buf[MSG_SIZ];
6576       gameMode = MachinePlaysBlack;
6577       StartClocks();
6578       SetGameInfo();
6579       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6580       DisplayTitle(buf);
6581       if (first.sendName) {
6582         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6583         SendToProgram(buf, &first);
6584       }
6585       StartClocks();
6586     }
6587     ModeHighlight();
6588   }
6589
6590   /* Relay move to ICS or chess engine */
6591   if (appData.icsActive) {
6592     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6593         gameMode == IcsExamining) {
6594       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6595         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6596         SendToICS("draw ");
6597         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6598       }
6599       // also send plain move, in case ICS does not understand atomic claims
6600       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6601       ics_user_moved = 1;
6602     }
6603   } else {
6604     if (first.sendTime && (gameMode == BeginningOfGame ||
6605                            gameMode == MachinePlaysWhite ||
6606                            gameMode == MachinePlaysBlack)) {
6607       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6608     }
6609     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6610          // [HGM] book: if program might be playing, let it use book
6611         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6612         first.maybeThinking = TRUE;
6613     } else SendMoveToProgram(forwardMostMove-1, &first);
6614     if (currentMove == cmailOldMove + 1) {
6615       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6616     }
6617   }
6618
6619   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6620
6621   switch (gameMode) {
6622   case EditGame:
6623     if(appData.testLegality)
6624     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6625     case MT_NONE:
6626     case MT_CHECK:
6627       break;
6628     case MT_CHECKMATE:
6629     case MT_STAINMATE:
6630       if (WhiteOnMove(currentMove)) {
6631         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6632       } else {
6633         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6634       }
6635       break;
6636     case MT_STALEMATE:
6637       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6638       break;
6639     }
6640     break;
6641
6642   case MachinePlaysBlack:
6643   case MachinePlaysWhite:
6644     /* disable certain menu options while machine is thinking */
6645     SetMachineThinkingEnables();
6646     break;
6647
6648   default:
6649     break;
6650   }
6651
6652   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6653   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6654
6655   if(bookHit) { // [HGM] book: simulate book reply
6656         static char bookMove[MSG_SIZ]; // a bit generous?
6657
6658         programStats.nodes = programStats.depth = programStats.time =
6659         programStats.score = programStats.got_only_move = 0;
6660         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6661
6662         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6663         strcat(bookMove, bookHit);
6664         HandleMachineMove(bookMove, &first);
6665   }
6666   return 1;
6667 }
6668
6669 void
6670 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6671      Board board;
6672      int flags;
6673      ChessMove kind;
6674      int rf, ff, rt, ft;
6675      VOIDSTAR closure;
6676 {
6677     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6678     Markers *m = (Markers *) closure;
6679     if(rf == fromY && ff == fromX)
6680         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6681                          || kind == WhiteCapturesEnPassant
6682                          || kind == BlackCapturesEnPassant);
6683     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6684 }
6685
6686 void
6687 MarkTargetSquares(int clear)
6688 {
6689   int x, y;
6690   if(!appData.markers || !appData.highlightDragging ||
6691      !appData.testLegality || gameMode == EditPosition) return;
6692   if(clear) {
6693     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6694   } else {
6695     int capt = 0;
6696     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6697     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6698       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6699       if(capt)
6700       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6701     }
6702   }
6703   DrawPosition(TRUE, NULL);
6704 }
6705
6706 int
6707 Explode(Board board, int fromX, int fromY, int toX, int toY)
6708 {
6709     if(gameInfo.variant == VariantAtomic &&
6710        (board[toY][toX] != EmptySquare ||                     // capture?
6711         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6712                          board[fromY][fromX] == BlackPawn   )
6713       )) {
6714         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6715         return TRUE;
6716     }
6717     return FALSE;
6718 }
6719
6720 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6721
6722 int CanPromote(ChessSquare piece, int y)
6723 {
6724         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6725         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6726         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6727            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6728            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6729                                                   gameInfo.variant == VariantMakruk) return FALSE;
6730         return (piece == BlackPawn && y == 1 ||
6731                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6732                 piece == BlackLance && y == 1 ||
6733                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6734 }
6735
6736 void LeftClick(ClickType clickType, int xPix, int yPix)
6737 {
6738     int x, y;
6739     Boolean saveAnimate;
6740     static int second = 0, promotionChoice = 0, clearFlag = 0;
6741     char promoChoice = NULLCHAR;
6742     ChessSquare piece;
6743
6744     if(appData.seekGraph && appData.icsActive && loggedOn &&
6745         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6746         SeekGraphClick(clickType, xPix, yPix, 0);
6747         return;
6748     }
6749
6750     if (clickType == Press) ErrorPopDown();
6751     MarkTargetSquares(1);
6752
6753     x = EventToSquare(xPix, BOARD_WIDTH);
6754     y = EventToSquare(yPix, BOARD_HEIGHT);
6755     if (!flipView && y >= 0) {
6756         y = BOARD_HEIGHT - 1 - y;
6757     }
6758     if (flipView && x >= 0) {
6759         x = BOARD_WIDTH - 1 - x;
6760     }
6761
6762     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6763         defaultPromoChoice = promoSweep;
6764         promoSweep = EmptySquare;   // terminate sweep
6765         promoDefaultAltered = TRUE;
6766         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6767     }
6768
6769     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6770         if(clickType == Release) return; // ignore upclick of click-click destination
6771         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6772         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6773         if(gameInfo.holdingsWidth &&
6774                 (WhiteOnMove(currentMove)
6775                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6776                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6777             // click in right holdings, for determining promotion piece
6778             ChessSquare p = boards[currentMove][y][x];
6779             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6780             if(p != EmptySquare) {
6781                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6782                 fromX = fromY = -1;
6783                 return;
6784             }
6785         }
6786         DrawPosition(FALSE, boards[currentMove]);
6787         return;
6788     }
6789
6790     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6791     if(clickType == Press
6792             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6793               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6794               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6795         return;
6796
6797     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6798         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6799
6800     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6801         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6802                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6803         defaultPromoChoice = DefaultPromoChoice(side);
6804     }
6805
6806     autoQueen = appData.alwaysPromoteToQueen;
6807
6808     if (fromX == -1) {
6809       int originalY = y;
6810       gatingPiece = EmptySquare;
6811       if (clickType != Press) {
6812         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6813             DragPieceEnd(xPix, yPix); dragging = 0;
6814             DrawPosition(FALSE, NULL);
6815         }
6816         return;
6817       }
6818       fromX = x; fromY = y;
6819       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6820          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6821          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6822             /* First square */
6823             if (OKToStartUserMove(fromX, fromY)) {
6824                 second = 0;
6825                 MarkTargetSquares(0);
6826                 DragPieceBegin(xPix, yPix); dragging = 1;
6827                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6828                     promoSweep = defaultPromoChoice;
6829                     selectFlag = 0; lastX = xPix; lastY = yPix;
6830                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6831                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6832                 }
6833                 if (appData.highlightDragging) {
6834                     SetHighlights(fromX, fromY, -1, -1);
6835                 }
6836             } else fromX = fromY = -1;
6837             return;
6838         }
6839     }
6840
6841     /* fromX != -1 */
6842     if (clickType == Press && gameMode != EditPosition) {
6843         ChessSquare fromP;
6844         ChessSquare toP;
6845         int frc;
6846
6847         // ignore off-board to clicks
6848         if(y < 0 || x < 0) return;
6849
6850         /* Check if clicking again on the same color piece */
6851         fromP = boards[currentMove][fromY][fromX];
6852         toP = boards[currentMove][y][x];
6853         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6854         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6855              WhitePawn <= toP && toP <= WhiteKing &&
6856              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6857              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6858             (BlackPawn <= fromP && fromP <= BlackKing &&
6859              BlackPawn <= toP && toP <= BlackKing &&
6860              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6861              !(fromP == BlackKing && toP == BlackRook && frc))) {
6862             /* Clicked again on same color piece -- changed his mind */
6863             second = (x == fromX && y == fromY);
6864             promoDefaultAltered = FALSE;
6865            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6866             if (appData.highlightDragging) {
6867                 SetHighlights(x, y, -1, -1);
6868             } else {
6869                 ClearHighlights();
6870             }
6871             if (OKToStartUserMove(x, y)) {
6872                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6873                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6874                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6875                  gatingPiece = boards[currentMove][fromY][fromX];
6876                 else gatingPiece = EmptySquare;
6877                 fromX = x;
6878                 fromY = y; dragging = 1;
6879                 MarkTargetSquares(0);
6880                 DragPieceBegin(xPix, yPix);
6881                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6882                     promoSweep = defaultPromoChoice;
6883                     selectFlag = 0; lastX = xPix; lastY = yPix;
6884                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6885                 }
6886             }
6887            }
6888            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6889            second = FALSE; 
6890         }
6891         // ignore clicks on holdings
6892         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6893     }
6894
6895     if (clickType == Release && x == fromX && y == fromY) {
6896         DragPieceEnd(xPix, yPix); dragging = 0;
6897         if(clearFlag) {
6898             // a deferred attempt to click-click move an empty square on top of a piece
6899             boards[currentMove][y][x] = EmptySquare;
6900             ClearHighlights();
6901             DrawPosition(FALSE, boards[currentMove]);
6902             fromX = fromY = -1; clearFlag = 0;
6903             return;
6904         }
6905         if (appData.animateDragging) {
6906             /* Undo animation damage if any */
6907             DrawPosition(FALSE, NULL);
6908         }
6909         if (second) {
6910             /* Second up/down in same square; just abort move */
6911             second = 0;
6912             fromX = fromY = -1;
6913             gatingPiece = EmptySquare;
6914             ClearHighlights();
6915             gotPremove = 0;
6916             ClearPremoveHighlights();
6917         } else {
6918             /* First upclick in same square; start click-click mode */
6919             SetHighlights(x, y, -1, -1);
6920         }
6921         return;
6922     }
6923
6924     clearFlag = 0;
6925
6926     /* we now have a different from- and (possibly off-board) to-square */
6927     /* Completed move */
6928     toX = x;
6929     toY = y;
6930     saveAnimate = appData.animate;
6931     if (clickType == Press) {
6932         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6933             // must be Edit Position mode with empty-square selected
6934             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6935             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6936             return;
6937         }
6938         /* Finish clickclick move */
6939         if (appData.animate || appData.highlightLastMove) {
6940             SetHighlights(fromX, fromY, toX, toY);
6941         } else {
6942             ClearHighlights();
6943         }
6944     } else {
6945         /* Finish drag move */
6946         if (appData.highlightLastMove) {
6947             SetHighlights(fromX, fromY, toX, toY);
6948         } else {
6949             ClearHighlights();
6950         }
6951         DragPieceEnd(xPix, yPix); dragging = 0;
6952         /* Don't animate move and drag both */
6953         appData.animate = FALSE;
6954     }
6955
6956     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6957     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6958         ChessSquare piece = boards[currentMove][fromY][fromX];
6959         if(gameMode == EditPosition && piece != EmptySquare &&
6960            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6961             int n;
6962
6963             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6964                 n = PieceToNumber(piece - (int)BlackPawn);
6965                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6966                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6967                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6968             } else
6969             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6970                 n = PieceToNumber(piece);
6971                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6972                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6973                 boards[currentMove][n][BOARD_WIDTH-2]++;
6974             }
6975             boards[currentMove][fromY][fromX] = EmptySquare;
6976         }
6977         ClearHighlights();
6978         fromX = fromY = -1;
6979         DrawPosition(TRUE, boards[currentMove]);
6980         return;
6981     }
6982
6983     // off-board moves should not be highlighted
6984     if(x < 0 || y < 0) ClearHighlights();
6985
6986     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6987
6988     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6989         SetHighlights(fromX, fromY, toX, toY);
6990         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6991             // [HGM] super: promotion to captured piece selected from holdings
6992             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6993             promotionChoice = TRUE;
6994             // kludge follows to temporarily execute move on display, without promoting yet
6995             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6996             boards[currentMove][toY][toX] = p;
6997             DrawPosition(FALSE, boards[currentMove]);
6998             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6999             boards[currentMove][toY][toX] = q;
7000             DisplayMessage("Click in holdings to choose piece", "");
7001             return;
7002         }
7003         PromotionPopUp();
7004     } else {
7005         int oldMove = currentMove;
7006         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7007         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7008         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7009         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7010            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7011             DrawPosition(TRUE, boards[currentMove]);
7012         fromX = fromY = -1;
7013     }
7014     appData.animate = saveAnimate;
7015     if (appData.animate || appData.animateDragging) {
7016         /* Undo animation damage if needed */
7017         DrawPosition(FALSE, NULL);
7018     }
7019 }
7020
7021 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7022 {   // front-end-free part taken out of PieceMenuPopup
7023     int whichMenu; int xSqr, ySqr;
7024
7025     if(seekGraphUp) { // [HGM] seekgraph
7026         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7027         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7028         return -2;
7029     }
7030
7031     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7032          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7033         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7034         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7035         if(action == Press)   {
7036             originalFlip = flipView;
7037             flipView = !flipView; // temporarily flip board to see game from partners perspective
7038             DrawPosition(TRUE, partnerBoard);
7039             DisplayMessage(partnerStatus, "");
7040             partnerUp = TRUE;
7041         } else if(action == Release) {
7042             flipView = originalFlip;
7043             DrawPosition(TRUE, boards[currentMove]);
7044             partnerUp = FALSE;
7045         }
7046         return -2;
7047     }
7048
7049     xSqr = EventToSquare(x, BOARD_WIDTH);
7050     ySqr = EventToSquare(y, BOARD_HEIGHT);
7051     if (action == Release) {
7052         if(pieceSweep != EmptySquare) {
7053             EditPositionMenuEvent(pieceSweep, toX, toY);
7054             pieceSweep = EmptySquare;
7055         } else UnLoadPV(); // [HGM] pv
7056     }
7057     if (action != Press) return -2; // return code to be ignored
7058     switch (gameMode) {
7059       case IcsExamining:
7060         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7061       case EditPosition:
7062         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7063         if (xSqr < 0 || ySqr < 0) return -1;
7064         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7065         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7066         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7067         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7068         NextPiece(0);
7069         return -2;\r
7070       case IcsObserving:
7071         if(!appData.icsEngineAnalyze) return -1;
7072       case IcsPlayingWhite:
7073       case IcsPlayingBlack:
7074         if(!appData.zippyPlay) goto noZip;
7075       case AnalyzeMode:
7076       case AnalyzeFile:
7077       case MachinePlaysWhite:
7078       case MachinePlaysBlack:
7079       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7080         if (!appData.dropMenu) {
7081           LoadPV(x, y);
7082           return 2; // flag front-end to grab mouse events
7083         }
7084         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7085            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7086       case EditGame:
7087       noZip:
7088         if (xSqr < 0 || ySqr < 0) return -1;
7089         if (!appData.dropMenu || appData.testLegality &&
7090             gameInfo.variant != VariantBughouse &&
7091             gameInfo.variant != VariantCrazyhouse) return -1;
7092         whichMenu = 1; // drop menu
7093         break;
7094       default:
7095         return -1;
7096     }
7097
7098     if (((*fromX = xSqr) < 0) ||
7099         ((*fromY = ySqr) < 0)) {
7100         *fromX = *fromY = -1;
7101         return -1;
7102     }
7103     if (flipView)
7104       *fromX = BOARD_WIDTH - 1 - *fromX;
7105     else
7106       *fromY = BOARD_HEIGHT - 1 - *fromY;
7107
7108     return whichMenu;
7109 }
7110
7111 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7112 {
7113 //    char * hint = lastHint;
7114     FrontEndProgramStats stats;
7115
7116     stats.which = cps == &first ? 0 : 1;
7117     stats.depth = cpstats->depth;
7118     stats.nodes = cpstats->nodes;
7119     stats.score = cpstats->score;
7120     stats.time = cpstats->time;
7121     stats.pv = cpstats->movelist;
7122     stats.hint = lastHint;
7123     stats.an_move_index = 0;
7124     stats.an_move_count = 0;
7125
7126     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7127         stats.hint = cpstats->move_name;
7128         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7129         stats.an_move_count = cpstats->nr_moves;
7130     }
7131
7132     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
7133
7134     SetProgramStats( &stats );
7135 }
7136
7137 #define MAXPLAYERS 500
7138
7139 char *
7140 TourneyStandings(int display)
7141 {
7142     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7143     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7144     char result, *p, *names[MAXPLAYERS];
7145
7146     if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7147
7148     names[0] = p = strdup(appData.participants);
7149     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7150
7151     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7152
7153     while(result = appData.results[nr]) {
7154         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7155         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7156         wScore = bScore = 0;
7157         switch(result) {
7158           case '+': wScore = 2; break;
7159           case '-': bScore = 2; break;
7160           case '=': wScore = bScore = 1; break;
7161           case ' ':
7162           case '*': return strdup("busy"); // tourney not finished
7163         }
7164         score[w] += wScore;
7165         score[b] += bScore;
7166         games[w]++;
7167         games[b]++;
7168         nr++;
7169     }
7170     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7171     for(w=0; w<nPlayers; w++) {
7172         bScore = -1;
7173         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7174         ranking[w] = b; points[w] = bScore; score[b] = -2;
7175     }
7176     p = malloc(nPlayers*34+1);
7177     for(w=0; w<nPlayers && w<display; w++)
7178         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7179     free(names[0]);
7180     return p;
7181 }
7182
7183 void
7184 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7185 {       // count all piece types
7186         int p, f, r;
7187         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7188         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7189         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7190                 p = board[r][f];
7191                 pCnt[p]++;
7192                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7193                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7194                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7195                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7196                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7197                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7198         }
7199 }
7200
7201 int
7202 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7203 {
7204         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7205         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7206
7207         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7208         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7209         if(myPawns == 2 && nMine == 3) // KPP
7210             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7211         if(myPawns == 1 && nMine == 2) // KP
7212             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7213         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7214             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7215         if(myPawns) return FALSE;
7216         if(pCnt[WhiteRook+side])
7217             return pCnt[BlackRook-side] ||
7218                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7219                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7220                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7221         if(pCnt[WhiteCannon+side]) {
7222             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7223             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7224         }
7225         if(pCnt[WhiteKnight+side])
7226             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7227         return FALSE;
7228 }
7229
7230 int
7231 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7232 {
7233         VariantClass v = gameInfo.variant;
7234
7235         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7236         if(v == VariantShatranj) return TRUE; // always winnable through baring
7237         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7238         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7239
7240         if(v == VariantXiangqi) {
7241                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7242
7243                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7244                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7245                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7246                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7247                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7248                 if(stale) // we have at least one last-rank P plus perhaps C
7249                     return majors // KPKX
7250                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7251                 else // KCA*E*
7252                     return pCnt[WhiteFerz+side] // KCAK
7253                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7254                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7255                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7256
7257         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7258                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7259
7260                 if(nMine == 1) return FALSE; // bare King
7261                 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
7262                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7263                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7264                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7265                 if(pCnt[WhiteKnight+side])
7266                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7267                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7268                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7269                 if(nBishops)
7270                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7271                 if(pCnt[WhiteAlfil+side])
7272                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7273                 if(pCnt[WhiteWazir+side])
7274                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7275         }
7276
7277         return TRUE;
7278 }
7279
7280 int
7281 Adjudicate(ChessProgramState *cps)
7282 {       // [HGM] some adjudications useful with buggy engines
7283         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7284         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7285         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7286         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7287         int k, count = 0; static int bare = 1;
7288         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7289         Boolean canAdjudicate = !appData.icsActive;
7290
7291         // most tests only when we understand the game, i.e. legality-checking on
7292             if( appData.testLegality )
7293             {   /* [HGM] Some more adjudications for obstinate engines */
7294                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7295                 static int moveCount = 6;
7296                 ChessMove result;
7297                 char *reason = NULL;
7298
7299                 /* Count what is on board. */
7300                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7301
7302                 /* Some material-based adjudications that have to be made before stalemate test */
7303                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7304                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7305                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7306                      if(canAdjudicate && appData.checkMates) {
7307                          if(engineOpponent)
7308                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7309                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7310                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7311                          return 1;
7312                      }
7313                 }
7314
7315                 /* Bare King in Shatranj (loses) or Losers (wins) */
7316                 if( nrW == 1 || nrB == 1) {
7317                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7318                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7319                      if(canAdjudicate && appData.checkMates) {
7320                          if(engineOpponent)
7321                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7322                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7323                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7324                          return 1;
7325                      }
7326                   } else
7327                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7328                   {    /* bare King */
7329                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7330                         if(canAdjudicate && appData.checkMates) {
7331                             /* but only adjudicate if adjudication enabled */
7332                             if(engineOpponent)
7333                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7334                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7335                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7336                             return 1;
7337                         }
7338                   }
7339                 } else bare = 1;
7340
7341
7342             // don't wait for engine to announce game end if we can judge ourselves
7343             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7344               case MT_CHECK:
7345                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7346                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7347                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7348                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7349                             checkCnt++;
7350                         if(checkCnt >= 2) {
7351                             reason = "Xboard adjudication: 3rd check";
7352                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7353                             break;
7354                         }
7355                     }
7356                 }
7357               case MT_NONE:
7358               default:
7359                 break;
7360               case MT_STALEMATE:
7361               case MT_STAINMATE:
7362                 reason = "Xboard adjudication: Stalemate";
7363                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7364                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7365                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7366                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7367                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7368                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7369                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7370                                                                         EP_CHECKMATE : EP_WINS);
7371                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7372                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7373                 }
7374                 break;
7375               case MT_CHECKMATE:
7376                 reason = "Xboard adjudication: Checkmate";
7377                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7378                 break;
7379             }
7380
7381                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7382                     case EP_STALEMATE:
7383                         result = GameIsDrawn; break;
7384                     case EP_CHECKMATE:
7385                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7386                     case EP_WINS:
7387                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7388                     default:
7389                         result = EndOfFile;
7390                 }
7391                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7392                     if(engineOpponent)
7393                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7394                     GameEnds( result, reason, GE_XBOARD );
7395                     return 1;
7396                 }
7397
7398                 /* Next absolutely insufficient mating material. */
7399                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7400                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7401                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7402
7403                      /* always flag draws, for judging claims */
7404                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7405
7406                      if(canAdjudicate && appData.materialDraws) {
7407                          /* but only adjudicate them if adjudication enabled */
7408                          if(engineOpponent) {
7409                            SendToProgram("force\n", engineOpponent); // suppress reply
7410                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7411                          }
7412                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7413                          return 1;
7414                      }
7415                 }
7416
7417                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7418                 if(gameInfo.variant == VariantXiangqi ?
7419                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7420                  : nrW + nrB == 4 &&
7421                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7422                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7423                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7424                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7425                    ) ) {
7426                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7427                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7428                           if(engineOpponent) {
7429                             SendToProgram("force\n", engineOpponent); // suppress reply
7430                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7431                           }
7432                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7433                           return 1;
7434                      }
7435                 } else moveCount = 6;
7436             }
7437         if (appData.debugMode) { int i;
7438             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7439                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7440                     appData.drawRepeats);
7441             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7442               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7443
7444         }
7445
7446         // Repetition draws and 50-move rule can be applied independently of legality testing
7447
7448                 /* Check for rep-draws */
7449                 count = 0;
7450                 for(k = forwardMostMove-2;
7451                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7452                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7453                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7454                     k-=2)
7455                 {   int rights=0;
7456                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7457                         /* compare castling rights */
7458                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7459                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7460                                 rights++; /* King lost rights, while rook still had them */
7461                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7462                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7463                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7464                                    rights++; /* but at least one rook lost them */
7465                         }
7466                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7467                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7468                                 rights++;
7469                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7470                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7471                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7472                                    rights++;
7473                         }
7474                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7475                             && appData.drawRepeats > 1) {
7476                              /* adjudicate after user-specified nr of repeats */
7477                              int result = GameIsDrawn;
7478                              char *details = "XBoard adjudication: repetition draw";
7479                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7480                                 // [HGM] xiangqi: check for forbidden perpetuals
7481                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7482                                 for(m=forwardMostMove; m>k; m-=2) {
7483                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7484                                         ourPerpetual = 0; // the current mover did not always check
7485                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7486                                         hisPerpetual = 0; // the opponent did not always check
7487                                 }
7488                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7489                                                                         ourPerpetual, hisPerpetual);
7490                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7491                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7492                                     details = "Xboard adjudication: perpetual checking";
7493                                 } else
7494                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7495                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7496                                 } else
7497                                 // Now check for perpetual chases
7498                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7499                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7500                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7501                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7502                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7503                                         details = "Xboard adjudication: perpetual chasing";
7504                                     } else
7505                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7506                                         break; // Abort repetition-checking loop.
7507                                 }
7508                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7509                              }
7510                              if(engineOpponent) {
7511                                SendToProgram("force\n", engineOpponent); // suppress reply
7512                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7513                              }
7514                              GameEnds( result, details, GE_XBOARD );
7515                              return 1;
7516                         }
7517                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7518                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7519                     }
7520                 }
7521
7522                 /* Now we test for 50-move draws. Determine ply count */
7523                 count = forwardMostMove;
7524                 /* look for last irreversble move */
7525                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7526                     count--;
7527                 /* if we hit starting position, add initial plies */
7528                 if( count == backwardMostMove )
7529                     count -= initialRulePlies;
7530                 count = forwardMostMove - count;
7531                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7532                         // adjust reversible move counter for checks in Xiangqi
7533                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7534                         if(i < backwardMostMove) i = backwardMostMove;
7535                         while(i <= forwardMostMove) {
7536                                 lastCheck = inCheck; // check evasion does not count
7537                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7538                                 if(inCheck || lastCheck) count--; // check does not count
7539                                 i++;
7540                         }
7541                 }
7542                 if( count >= 100)
7543                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7544                          /* this is used to judge if draw claims are legal */
7545                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7546                          if(engineOpponent) {
7547                            SendToProgram("force\n", engineOpponent); // suppress reply
7548                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7549                          }
7550                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7551                          return 1;
7552                 }
7553
7554                 /* if draw offer is pending, treat it as a draw claim
7555                  * when draw condition present, to allow engines a way to
7556                  * claim draws before making their move to avoid a race
7557                  * condition occurring after their move
7558                  */
7559                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7560                          char *p = NULL;
7561                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7562                              p = "Draw claim: 50-move rule";
7563                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7564                              p = "Draw claim: 3-fold repetition";
7565                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7566                              p = "Draw claim: insufficient mating material";
7567                          if( p != NULL && canAdjudicate) {
7568                              if(engineOpponent) {
7569                                SendToProgram("force\n", engineOpponent); // suppress reply
7570                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7571                              }
7572                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7573                              return 1;
7574                          }
7575                 }
7576
7577                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
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( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7583                     return 1;
7584                 }
7585         return 0;
7586 }
7587
7588 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7589 {   // [HGM] book: this routine intercepts moves to simulate book replies
7590     char *bookHit = NULL;
7591
7592     //first determine if the incoming move brings opponent into his book
7593     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7594         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7595     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7596     if(bookHit != NULL && !cps->bookSuspend) {
7597         // make sure opponent is not going to reply after receiving move to book position
7598         SendToProgram("force\n", cps);
7599         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7600     }
7601     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7602     // now arrange restart after book miss
7603     if(bookHit) {
7604         // after a book hit we never send 'go', and the code after the call to this routine
7605         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7606         char buf[MSG_SIZ], *move = bookHit;
7607         if(cps->useSAN) {
7608             int fromX, fromY, toX, toY;
7609             char promoChar;
7610             ChessMove moveType;
7611             move = buf + 30;
7612             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7613                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7614                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7615                                     PosFlags(forwardMostMove),
7616                                     fromY, fromX, toY, toX, promoChar, move);
7617             } else {
7618                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7619                 bookHit = NULL;
7620             }
7621         }
7622         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7623         SendToProgram(buf, cps);
7624         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7625     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7626         SendToProgram("go\n", cps);
7627         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7628     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7629         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7630             SendToProgram("go\n", cps);
7631         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7632     }
7633     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7634 }
7635
7636 char *savedMessage;
7637 ChessProgramState *savedState;
7638 void DeferredBookMove(void)
7639 {
7640         if(savedState->lastPing != savedState->lastPong)
7641                     ScheduleDelayedEvent(DeferredBookMove, 10);
7642         else
7643         HandleMachineMove(savedMessage, savedState);
7644 }
7645
7646 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7647
7648 void
7649 HandleMachineMove(message, cps)
7650      char *message;
7651      ChessProgramState *cps;
7652 {
7653     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7654     char realname[MSG_SIZ];
7655     int fromX, fromY, toX, toY;
7656     ChessMove moveType;
7657     char promoChar;
7658     char *p;
7659     int machineWhite;
7660     char *bookHit;
7661
7662     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7663         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7664         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
7665         pairingReceived = 1;
7666         NextMatchGame();
7667         return; // Skim the pairing messages here.
7668     }
7669
7670     cps->userError = 0;
7671
7672 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7673     /*
7674      * Kludge to ignore BEL characters
7675      */
7676     while (*message == '\007') message++;
7677
7678     /*
7679      * [HGM] engine debug message: ignore lines starting with '#' character
7680      */
7681     if(cps->debug && *message == '#') return;
7682
7683     /*
7684      * Look for book output
7685      */
7686     if (cps == &first && bookRequested) {
7687         if (message[0] == '\t' || message[0] == ' ') {
7688             /* Part of the book output is here; append it */
7689             strcat(bookOutput, message);
7690             strcat(bookOutput, "  \n");
7691             return;
7692         } else if (bookOutput[0] != NULLCHAR) {
7693             /* All of book output has arrived; display it */
7694             char *p = bookOutput;
7695             while (*p != NULLCHAR) {
7696                 if (*p == '\t') *p = ' ';
7697                 p++;
7698             }
7699             DisplayInformation(bookOutput);
7700             bookRequested = FALSE;
7701             /* Fall through to parse the current output */
7702         }
7703     }
7704
7705     /*
7706      * Look for machine move.
7707      */
7708     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7709         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7710     {
7711         /* This method is only useful on engines that support ping */
7712         if (cps->lastPing != cps->lastPong) {
7713           if (gameMode == BeginningOfGame) {
7714             /* Extra move from before last new; ignore */
7715             if (appData.debugMode) {
7716                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7717             }
7718           } else {
7719             if (appData.debugMode) {
7720                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7721                         cps->which, gameMode);
7722             }
7723
7724             SendToProgram("undo\n", cps);
7725           }
7726           return;
7727         }
7728
7729         switch (gameMode) {
7730           case BeginningOfGame:
7731             /* Extra move from before last reset; ignore */
7732             if (appData.debugMode) {
7733                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7734             }
7735             return;
7736
7737           case EndOfGame:
7738           case IcsIdle:
7739           default:
7740             /* Extra move after we tried to stop.  The mode test is
7741                not a reliable way of detecting this problem, but it's
7742                the best we can do on engines that don't support ping.
7743             */
7744             if (appData.debugMode) {
7745                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7746                         cps->which, gameMode);
7747             }
7748             SendToProgram("undo\n", cps);
7749             return;
7750
7751           case MachinePlaysWhite:
7752           case IcsPlayingWhite:
7753             machineWhite = TRUE;
7754             break;
7755
7756           case MachinePlaysBlack:
7757           case IcsPlayingBlack:
7758             machineWhite = FALSE;
7759             break;
7760
7761           case TwoMachinesPlay:
7762             machineWhite = (cps->twoMachinesColor[0] == 'w');
7763             break;
7764         }
7765         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7766             if (appData.debugMode) {
7767                 fprintf(debugFP,
7768                         "Ignoring move out of turn by %s, gameMode %d"
7769                         ", forwardMost %d\n",
7770                         cps->which, gameMode, forwardMostMove);
7771             }
7772             return;
7773         }
7774
7775     if (appData.debugMode) { int f = forwardMostMove;
7776         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7777                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7778                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7779     }
7780         if(cps->alphaRank) AlphaRank(machineMove, 4);
7781         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7782                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7783             /* Machine move could not be parsed; ignore it. */
7784           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7785                     machineMove, _(cps->which));
7786             DisplayError(buf1, 0);
7787             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7788                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7789             if (gameMode == TwoMachinesPlay) {
7790               GameEnds(machineWhite ? BlackWins : WhiteWins,
7791                        buf1, GE_XBOARD);
7792             }
7793             return;
7794         }
7795
7796         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7797         /* So we have to redo legality test with true e.p. status here,  */
7798         /* to make sure an illegal e.p. capture does not slip through,   */
7799         /* to cause a forfeit on a justified illegal-move complaint      */
7800         /* of the opponent.                                              */
7801         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7802            ChessMove moveType;
7803            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7804                              fromY, fromX, toY, toX, promoChar);
7805             if (appData.debugMode) {
7806                 int i;
7807                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7808                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7809                 fprintf(debugFP, "castling rights\n");
7810             }
7811             if(moveType == IllegalMove) {
7812               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7813                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7814                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7815                            buf1, GE_XBOARD);
7816                 return;
7817            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7818            /* [HGM] Kludge to handle engines that send FRC-style castling
7819               when they shouldn't (like TSCP-Gothic) */
7820            switch(moveType) {
7821              case WhiteASideCastleFR:
7822              case BlackASideCastleFR:
7823                toX+=2;
7824                currentMoveString[2]++;
7825                break;
7826              case WhiteHSideCastleFR:
7827              case BlackHSideCastleFR:
7828                toX--;
7829                currentMoveString[2]--;
7830                break;
7831              default: ; // nothing to do, but suppresses warning of pedantic compilers
7832            }
7833         }
7834         hintRequested = FALSE;
7835         lastHint[0] = NULLCHAR;
7836         bookRequested = FALSE;
7837         /* Program may be pondering now */
7838         cps->maybeThinking = TRUE;
7839         if (cps->sendTime == 2) cps->sendTime = 1;
7840         if (cps->offeredDraw) cps->offeredDraw--;
7841
7842         /* [AS] Save move info*/
7843         pvInfoList[ forwardMostMove ].score = programStats.score;
7844         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7845         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7846
7847         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7848
7849         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7850         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7851             int count = 0;
7852
7853             while( count < adjudicateLossPlies ) {
7854                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7855
7856                 if( count & 1 ) {
7857                     score = -score; /* Flip score for winning side */
7858                 }
7859
7860                 if( score > adjudicateLossThreshold ) {
7861                     break;
7862                 }
7863
7864                 count++;
7865             }
7866
7867             if( count >= adjudicateLossPlies ) {
7868                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7869
7870                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7871                     "Xboard adjudication",
7872                     GE_XBOARD );
7873
7874                 return;
7875             }
7876         }
7877
7878         if(Adjudicate(cps)) {
7879             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7880             return; // [HGM] adjudicate: for all automatic game ends
7881         }
7882
7883 #if ZIPPY
7884         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7885             first.initDone) {
7886           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7887                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7888                 SendToICS("draw ");
7889                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7890           }
7891           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7892           ics_user_moved = 1;
7893           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7894                 char buf[3*MSG_SIZ];
7895
7896                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7897                         programStats.score / 100.,
7898                         programStats.depth,
7899                         programStats.time / 100.,
7900                         (unsigned int)programStats.nodes,
7901                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7902                         programStats.movelist);
7903                 SendToICS(buf);
7904 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7905           }
7906         }
7907 #endif
7908
7909         /* [AS] Clear stats for next move */
7910         ClearProgramStats();
7911         thinkOutput[0] = NULLCHAR;
7912         hiddenThinkOutputState = 0;
7913
7914         bookHit = NULL;
7915         if (gameMode == TwoMachinesPlay) {
7916             /* [HGM] relaying draw offers moved to after reception of move */
7917             /* and interpreting offer as claim if it brings draw condition */
7918             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7919                 SendToProgram("draw\n", cps->other);
7920             }
7921             if (cps->other->sendTime) {
7922                 SendTimeRemaining(cps->other,
7923                                   cps->other->twoMachinesColor[0] == 'w');
7924             }
7925             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7926             if (firstMove && !bookHit) {
7927                 firstMove = FALSE;
7928                 if (cps->other->useColors) {
7929                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7930                 }
7931                 SendToProgram("go\n", cps->other);
7932             }
7933             cps->other->maybeThinking = TRUE;
7934         }
7935
7936         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7937
7938         if (!pausing && appData.ringBellAfterMoves) {
7939             RingBell();
7940         }
7941
7942         /*
7943          * Reenable menu items that were disabled while
7944          * machine was thinking
7945          */
7946         if (gameMode != TwoMachinesPlay)
7947             SetUserThinkingEnables();
7948
7949         // [HGM] book: after book hit opponent has received move and is now in force mode
7950         // force the book reply into it, and then fake that it outputted this move by jumping
7951         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7952         if(bookHit) {
7953                 static char bookMove[MSG_SIZ]; // a bit generous?
7954
7955                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7956                 strcat(bookMove, bookHit);
7957                 message = bookMove;
7958                 cps = cps->other;
7959                 programStats.nodes = programStats.depth = programStats.time =
7960                 programStats.score = programStats.got_only_move = 0;
7961                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7962
7963                 if(cps->lastPing != cps->lastPong) {
7964                     savedMessage = message; // args for deferred call
7965                     savedState = cps;
7966                     ScheduleDelayedEvent(DeferredBookMove, 10);
7967                     return;
7968                 }
7969                 goto FakeBookMove;
7970         }
7971
7972         return;
7973     }
7974
7975     /* Set special modes for chess engines.  Later something general
7976      *  could be added here; for now there is just one kludge feature,
7977      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7978      *  when "xboard" is given as an interactive command.
7979      */
7980     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7981         cps->useSigint = FALSE;
7982         cps->useSigterm = FALSE;
7983     }
7984     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7985       ParseFeatures(message+8, cps);
7986       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7987     }
7988
7989     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7990       int dummy, s=6; char buf[MSG_SIZ];
7991       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7992       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7993       ParseFEN(boards[0], &dummy, message+s);
7994       DrawPosition(TRUE, boards[0]);
7995       startedFromSetupPosition = TRUE;
7996       return;
7997     }
7998     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7999      * want this, I was asked to put it in, and obliged.
8000      */
8001     if (!strncmp(message, "setboard ", 9)) {
8002         Board initial_position;
8003
8004         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8005
8006         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8007             DisplayError(_("Bad FEN received from engine"), 0);
8008             return ;
8009         } else {
8010            Reset(TRUE, FALSE);
8011            CopyBoard(boards[0], initial_position);
8012            initialRulePlies = FENrulePlies;
8013            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8014            else gameMode = MachinePlaysBlack;
8015            DrawPosition(FALSE, boards[currentMove]);
8016         }
8017         return;
8018     }
8019
8020     /*
8021      * Look for communication commands
8022      */
8023     if (!strncmp(message, "telluser ", 9)) {
8024         if(message[9] == '\\' && message[10] == '\\')
8025             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8026         DisplayNote(message + 9);
8027         return;
8028     }
8029     if (!strncmp(message, "tellusererror ", 14)) {
8030         cps->userError = 1;
8031         if(message[14] == '\\' && message[15] == '\\')
8032             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8033         DisplayError(message + 14, 0);
8034         return;
8035     }
8036     if (!strncmp(message, "tellopponent ", 13)) {
8037       if (appData.icsActive) {
8038         if (loggedOn) {
8039           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8040           SendToICS(buf1);
8041         }
8042       } else {
8043         DisplayNote(message + 13);
8044       }
8045       return;
8046     }
8047     if (!strncmp(message, "tellothers ", 11)) {
8048       if (appData.icsActive) {
8049         if (loggedOn) {
8050           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8051           SendToICS(buf1);
8052         }
8053       }
8054       return;
8055     }
8056     if (!strncmp(message, "tellall ", 8)) {
8057       if (appData.icsActive) {
8058         if (loggedOn) {
8059           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8060           SendToICS(buf1);
8061         }
8062       } else {
8063         DisplayNote(message + 8);
8064       }
8065       return;
8066     }
8067     if (strncmp(message, "warning", 7) == 0) {
8068         /* Undocumented feature, use tellusererror in new code */
8069         DisplayError(message, 0);
8070         return;
8071     }
8072     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8073         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8074         strcat(realname, " query");
8075         AskQuestion(realname, buf2, buf1, cps->pr);
8076         return;
8077     }
8078     /* Commands from the engine directly to ICS.  We don't allow these to be
8079      *  sent until we are logged on. Crafty kibitzes have been known to
8080      *  interfere with the login process.
8081      */
8082     if (loggedOn) {
8083         if (!strncmp(message, "tellics ", 8)) {
8084             SendToICS(message + 8);
8085             SendToICS("\n");
8086             return;
8087         }
8088         if (!strncmp(message, "tellicsnoalias ", 15)) {
8089             SendToICS(ics_prefix);
8090             SendToICS(message + 15);
8091             SendToICS("\n");
8092             return;
8093         }
8094         /* The following are for backward compatibility only */
8095         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8096             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8097             SendToICS(ics_prefix);
8098             SendToICS(message);
8099             SendToICS("\n");
8100             return;
8101         }
8102     }
8103     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8104         return;
8105     }
8106     /*
8107      * If the move is illegal, cancel it and redraw the board.
8108      * Also deal with other error cases.  Matching is rather loose
8109      * here to accommodate engines written before the spec.
8110      */
8111     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8112         strncmp(message, "Error", 5) == 0) {
8113         if (StrStr(message, "name") ||
8114             StrStr(message, "rating") || StrStr(message, "?") ||
8115             StrStr(message, "result") || StrStr(message, "board") ||
8116             StrStr(message, "bk") || StrStr(message, "computer") ||
8117             StrStr(message, "variant") || StrStr(message, "hint") ||
8118             StrStr(message, "random") || StrStr(message, "depth") ||
8119             StrStr(message, "accepted")) {
8120             return;
8121         }
8122         if (StrStr(message, "protover")) {
8123           /* Program is responding to input, so it's apparently done
8124              initializing, and this error message indicates it is
8125              protocol version 1.  So we don't need to wait any longer
8126              for it to initialize and send feature commands. */
8127           FeatureDone(cps, 1);
8128           cps->protocolVersion = 1;
8129           return;
8130         }
8131         cps->maybeThinking = FALSE;
8132
8133         if (StrStr(message, "draw")) {
8134             /* Program doesn't have "draw" command */
8135             cps->sendDrawOffers = 0;
8136             return;
8137         }
8138         if (cps->sendTime != 1 &&
8139             (StrStr(message, "time") || StrStr(message, "otim"))) {
8140           /* Program apparently doesn't have "time" or "otim" command */
8141           cps->sendTime = 0;
8142           return;
8143         }
8144         if (StrStr(message, "analyze")) {
8145             cps->analysisSupport = FALSE;
8146             cps->analyzing = FALSE;
8147             Reset(FALSE, TRUE);
8148             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8149             DisplayError(buf2, 0);
8150             return;
8151         }
8152         if (StrStr(message, "(no matching move)st")) {
8153           /* Special kludge for GNU Chess 4 only */
8154           cps->stKludge = TRUE;
8155           SendTimeControl(cps, movesPerSession, timeControl,
8156                           timeIncrement, appData.searchDepth,
8157                           searchTime);
8158           return;
8159         }
8160         if (StrStr(message, "(no matching move)sd")) {
8161           /* Special kludge for GNU Chess 4 only */
8162           cps->sdKludge = TRUE;
8163           SendTimeControl(cps, movesPerSession, timeControl,
8164                           timeIncrement, appData.searchDepth,
8165                           searchTime);
8166           return;
8167         }
8168         if (!StrStr(message, "llegal")) {
8169             return;
8170         }
8171         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8172             gameMode == IcsIdle) return;
8173         if (forwardMostMove <= backwardMostMove) return;
8174         if (pausing) PauseEvent();
8175       if(appData.forceIllegal) {
8176             // [HGM] illegal: machine refused move; force position after move into it
8177           SendToProgram("force\n", cps);
8178           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8179                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8180                 // when black is to move, while there might be nothing on a2 or black
8181                 // might already have the move. So send the board as if white has the move.
8182                 // But first we must change the stm of the engine, as it refused the last move
8183                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8184                 if(WhiteOnMove(forwardMostMove)) {
8185                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8186                     SendBoard(cps, forwardMostMove); // kludgeless board
8187                 } else {
8188                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8189                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8190                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8191                 }
8192           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8193             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8194                  gameMode == TwoMachinesPlay)
8195               SendToProgram("go\n", cps);
8196             return;
8197       } else
8198         if (gameMode == PlayFromGameFile) {
8199             /* Stop reading this game file */
8200             gameMode = EditGame;
8201             ModeHighlight();
8202         }
8203         /* [HGM] illegal-move claim should forfeit game when Xboard */
8204         /* only passes fully legal moves                            */
8205         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8206             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8207                                 "False illegal-move claim", GE_XBOARD );
8208             return; // do not take back move we tested as valid
8209         }
8210         currentMove = forwardMostMove-1;
8211         DisplayMove(currentMove-1); /* before DisplayMoveError */
8212         SwitchClocks(forwardMostMove-1); // [HGM] race
8213         DisplayBothClocks();
8214         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8215                 parseList[currentMove], _(cps->which));
8216         DisplayMoveError(buf1);
8217         DrawPosition(FALSE, boards[currentMove]);
8218         return;
8219     }
8220     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8221         /* Program has a broken "time" command that
8222            outputs a string not ending in newline.
8223            Don't use it. */
8224         cps->sendTime = 0;
8225     }
8226
8227     /*
8228      * If chess program startup fails, exit with an error message.
8229      * Attempts to recover here are futile.
8230      */
8231     if ((StrStr(message, "unknown host") != NULL)
8232         || (StrStr(message, "No remote directory") != NULL)
8233         || (StrStr(message, "not found") != NULL)
8234         || (StrStr(message, "No such file") != NULL)
8235         || (StrStr(message, "can't alloc") != NULL)
8236         || (StrStr(message, "Permission denied") != NULL)) {
8237
8238         cps->maybeThinking = FALSE;
8239         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8240                 _(cps->which), cps->program, cps->host, message);
8241         RemoveInputSource(cps->isr);
8242         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8243             if(cps == &first) appData.noChessProgram = TRUE;
8244             DisplayError(buf1, 0);
8245         }
8246         return;
8247     }
8248
8249     /*
8250      * Look for hint output
8251      */
8252     if (sscanf(message, "Hint: %s", buf1) == 1) {
8253         if (cps == &first && hintRequested) {
8254             hintRequested = FALSE;
8255             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8256                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8257                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8258                                     PosFlags(forwardMostMove),
8259                                     fromY, fromX, toY, toX, promoChar, buf1);
8260                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8261                 DisplayInformation(buf2);
8262             } else {
8263                 /* Hint move could not be parsed!? */
8264               snprintf(buf2, sizeof(buf2),
8265                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8266                         buf1, _(cps->which));
8267                 DisplayError(buf2, 0);
8268             }
8269         } else {
8270           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8271         }
8272         return;
8273     }
8274
8275     /*
8276      * Ignore other messages if game is not in progress
8277      */
8278     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8279         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8280
8281     /*
8282      * look for win, lose, draw, or draw offer
8283      */
8284     if (strncmp(message, "1-0", 3) == 0) {
8285         char *p, *q, *r = "";
8286         p = strchr(message, '{');
8287         if (p) {
8288             q = strchr(p, '}');
8289             if (q) {
8290                 *q = NULLCHAR;
8291                 r = p + 1;
8292             }
8293         }
8294         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8295         return;
8296     } else if (strncmp(message, "0-1", 3) == 0) {
8297         char *p, *q, *r = "";
8298         p = strchr(message, '{');
8299         if (p) {
8300             q = strchr(p, '}');
8301             if (q) {
8302                 *q = NULLCHAR;
8303                 r = p + 1;
8304             }
8305         }
8306         /* Kludge for Arasan 4.1 bug */
8307         if (strcmp(r, "Black resigns") == 0) {
8308             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8309             return;
8310         }
8311         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8312         return;
8313     } else if (strncmp(message, "1/2", 3) == 0) {
8314         char *p, *q, *r = "";
8315         p = strchr(message, '{');
8316         if (p) {
8317             q = strchr(p, '}');
8318             if (q) {
8319                 *q = NULLCHAR;
8320                 r = p + 1;
8321             }
8322         }
8323
8324         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8325         return;
8326
8327     } else if (strncmp(message, "White resign", 12) == 0) {
8328         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8329         return;
8330     } else if (strncmp(message, "Black resign", 12) == 0) {
8331         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8332         return;
8333     } else if (strncmp(message, "White matches", 13) == 0 ||
8334                strncmp(message, "Black matches", 13) == 0   ) {
8335         /* [HGM] ignore GNUShogi noises */
8336         return;
8337     } else if (strncmp(message, "White", 5) == 0 &&
8338                message[5] != '(' &&
8339                StrStr(message, "Black") == NULL) {
8340         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8341         return;
8342     } else if (strncmp(message, "Black", 5) == 0 &&
8343                message[5] != '(') {
8344         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8345         return;
8346     } else if (strcmp(message, "resign") == 0 ||
8347                strcmp(message, "computer resigns") == 0) {
8348         switch (gameMode) {
8349           case MachinePlaysBlack:
8350           case IcsPlayingBlack:
8351             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8352             break;
8353           case MachinePlaysWhite:
8354           case IcsPlayingWhite:
8355             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8356             break;
8357           case TwoMachinesPlay:
8358             if (cps->twoMachinesColor[0] == 'w')
8359               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8360             else
8361               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8362             break;
8363           default:
8364             /* can't happen */
8365             break;
8366         }
8367         return;
8368     } else if (strncmp(message, "opponent mates", 14) == 0) {
8369         switch (gameMode) {
8370           case MachinePlaysBlack:
8371           case IcsPlayingBlack:
8372             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8373             break;
8374           case MachinePlaysWhite:
8375           case IcsPlayingWhite:
8376             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8377             break;
8378           case TwoMachinesPlay:
8379             if (cps->twoMachinesColor[0] == 'w')
8380               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8381             else
8382               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8383             break;
8384           default:
8385             /* can't happen */
8386             break;
8387         }
8388         return;
8389     } else if (strncmp(message, "computer mates", 14) == 0) {
8390         switch (gameMode) {
8391           case MachinePlaysBlack:
8392           case IcsPlayingBlack:
8393             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8394             break;
8395           case MachinePlaysWhite:
8396           case IcsPlayingWhite:
8397             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8398             break;
8399           case TwoMachinesPlay:
8400             if (cps->twoMachinesColor[0] == 'w')
8401               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8402             else
8403               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8404             break;
8405           default:
8406             /* can't happen */
8407             break;
8408         }
8409         return;
8410     } else if (strncmp(message, "checkmate", 9) == 0) {
8411         if (WhiteOnMove(forwardMostMove)) {
8412             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8413         } else {
8414             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8415         }
8416         return;
8417     } else if (strstr(message, "Draw") != NULL ||
8418                strstr(message, "game is a draw") != NULL) {
8419         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8420         return;
8421     } else if (strstr(message, "offer") != NULL &&
8422                strstr(message, "draw") != NULL) {
8423 #if ZIPPY
8424         if (appData.zippyPlay && first.initDone) {
8425             /* Relay offer to ICS */
8426             SendToICS(ics_prefix);
8427             SendToICS("draw\n");
8428         }
8429 #endif
8430         cps->offeredDraw = 2; /* valid until this engine moves twice */
8431         if (gameMode == TwoMachinesPlay) {
8432             if (cps->other->offeredDraw) {
8433                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8434             /* [HGM] in two-machine mode we delay relaying draw offer      */
8435             /* until after we also have move, to see if it is really claim */
8436             }
8437         } else if (gameMode == MachinePlaysWhite ||
8438                    gameMode == MachinePlaysBlack) {
8439           if (userOfferedDraw) {
8440             DisplayInformation(_("Machine accepts your draw offer"));
8441             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8442           } else {
8443             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8444           }
8445         }
8446     }
8447
8448
8449     /*
8450      * Look for thinking output
8451      */
8452     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8453           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8454                                 ) {
8455         int plylev, mvleft, mvtot, curscore, time;
8456         char mvname[MOVE_LEN];
8457         u64 nodes; // [DM]
8458         char plyext;
8459         int ignore = FALSE;
8460         int prefixHint = FALSE;
8461         mvname[0] = NULLCHAR;
8462
8463         switch (gameMode) {
8464           case MachinePlaysBlack:
8465           case IcsPlayingBlack:
8466             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8467             break;
8468           case MachinePlaysWhite:
8469           case IcsPlayingWhite:
8470             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8471             break;
8472           case AnalyzeMode:
8473           case AnalyzeFile:
8474             break;
8475           case IcsObserving: /* [DM] icsEngineAnalyze */
8476             if (!appData.icsEngineAnalyze) ignore = TRUE;
8477             break;
8478           case TwoMachinesPlay:
8479             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8480                 ignore = TRUE;
8481             }
8482             break;
8483           default:
8484             ignore = TRUE;
8485             break;
8486         }
8487
8488         if (!ignore) {
8489             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8490             buf1[0] = NULLCHAR;
8491             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8492                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8493
8494                 if (plyext != ' ' && plyext != '\t') {
8495                     time *= 100;
8496                 }
8497
8498                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8499                 if( cps->scoreIsAbsolute &&
8500                     ( gameMode == MachinePlaysBlack ||
8501                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8502                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8503                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8504                      !WhiteOnMove(currentMove)
8505                     ) )
8506                 {
8507                     curscore = -curscore;
8508                 }
8509
8510
8511                 tempStats.depth = plylev;
8512                 tempStats.nodes = nodes;
8513                 tempStats.time = time;
8514                 tempStats.score = curscore;
8515                 tempStats.got_only_move = 0;
8516
8517                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8518                         int ticklen;
8519
8520                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8521                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8522                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8523                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8524                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8525                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8526                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8527                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8528                 }
8529
8530                 /* Buffer overflow protection */
8531                 if (buf1[0] != NULLCHAR) {
8532                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8533                         && appData.debugMode) {
8534                         fprintf(debugFP,
8535                                 "PV is too long; using the first %u bytes.\n",
8536                                 (unsigned) sizeof(tempStats.movelist) - 1);
8537                     }
8538
8539                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8540                 } else {
8541                     sprintf(tempStats.movelist, " no PV\n");
8542                 }
8543
8544                 if (tempStats.seen_stat) {
8545                     tempStats.ok_to_send = 1;
8546                 }
8547
8548                 if (strchr(tempStats.movelist, '(') != NULL) {
8549                     tempStats.line_is_book = 1;
8550                     tempStats.nr_moves = 0;
8551                     tempStats.moves_left = 0;
8552                 } else {
8553                     tempStats.line_is_book = 0;
8554                 }
8555
8556                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8557                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8558
8559                 SendProgramStatsToFrontend( cps, &tempStats );
8560
8561                 /*
8562                     [AS] Protect the thinkOutput buffer from overflow... this
8563                     is only useful if buf1 hasn't overflowed first!
8564                 */
8565                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8566                          plylev,
8567                          (gameMode == TwoMachinesPlay ?
8568                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8569                          ((double) curscore) / 100.0,
8570                          prefixHint ? lastHint : "",
8571                          prefixHint ? " " : "" );
8572
8573                 if( buf1[0] != NULLCHAR ) {
8574                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8575
8576                     if( strlen(buf1) > max_len ) {
8577                         if( appData.debugMode) {
8578                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8579                         }
8580                         buf1[max_len+1] = '\0';
8581                     }
8582
8583                     strcat( thinkOutput, buf1 );
8584                 }
8585
8586                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8587                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8588                     DisplayMove(currentMove - 1);
8589                 }
8590                 return;
8591
8592             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8593                 /* crafty (9.25+) says "(only move) <move>"
8594                  * if there is only 1 legal move
8595                  */
8596                 sscanf(p, "(only move) %s", buf1);
8597                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8598                 sprintf(programStats.movelist, "%s (only move)", buf1);
8599                 programStats.depth = 1;
8600                 programStats.nr_moves = 1;
8601                 programStats.moves_left = 1;
8602                 programStats.nodes = 1;
8603                 programStats.time = 1;
8604                 programStats.got_only_move = 1;
8605
8606                 /* Not really, but we also use this member to
8607                    mean "line isn't going to change" (Crafty
8608                    isn't searching, so stats won't change) */
8609                 programStats.line_is_book = 1;
8610
8611                 SendProgramStatsToFrontend( cps, &programStats );
8612
8613                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8614                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8615                     DisplayMove(currentMove - 1);
8616                 }
8617                 return;
8618             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8619                               &time, &nodes, &plylev, &mvleft,
8620                               &mvtot, mvname) >= 5) {
8621                 /* The stat01: line is from Crafty (9.29+) in response
8622                    to the "." command */
8623                 programStats.seen_stat = 1;
8624                 cps->maybeThinking = TRUE;
8625
8626                 if (programStats.got_only_move || !appData.periodicUpdates)
8627                   return;
8628
8629                 programStats.depth = plylev;
8630                 programStats.time = time;
8631                 programStats.nodes = nodes;
8632                 programStats.moves_left = mvleft;
8633                 programStats.nr_moves = mvtot;
8634                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8635                 programStats.ok_to_send = 1;
8636                 programStats.movelist[0] = '\0';
8637
8638                 SendProgramStatsToFrontend( cps, &programStats );
8639
8640                 return;
8641
8642             } else if (strncmp(message,"++",2) == 0) {
8643                 /* Crafty 9.29+ outputs this */
8644                 programStats.got_fail = 2;
8645                 return;
8646
8647             } else if (strncmp(message,"--",2) == 0) {
8648                 /* Crafty 9.29+ outputs this */
8649                 programStats.got_fail = 1;
8650                 return;
8651
8652             } else if (thinkOutput[0] != NULLCHAR &&
8653                        strncmp(message, "    ", 4) == 0) {
8654                 unsigned message_len;
8655
8656                 p = message;
8657                 while (*p && *p == ' ') p++;
8658
8659                 message_len = strlen( p );
8660
8661                 /* [AS] Avoid buffer overflow */
8662                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8663                     strcat(thinkOutput, " ");
8664                     strcat(thinkOutput, p);
8665                 }
8666
8667                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8668                     strcat(programStats.movelist, " ");
8669                     strcat(programStats.movelist, p);
8670                 }
8671
8672                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8673                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8674                     DisplayMove(currentMove - 1);
8675                 }
8676                 return;
8677             }
8678         }
8679         else {
8680             buf1[0] = NULLCHAR;
8681
8682             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8683                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8684             {
8685                 ChessProgramStats cpstats;
8686
8687                 if (plyext != ' ' && plyext != '\t') {
8688                     time *= 100;
8689                 }
8690
8691                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8692                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8693                     curscore = -curscore;
8694                 }
8695
8696                 cpstats.depth = plylev;
8697                 cpstats.nodes = nodes;
8698                 cpstats.time = time;
8699                 cpstats.score = curscore;
8700                 cpstats.got_only_move = 0;
8701                 cpstats.movelist[0] = '\0';
8702
8703                 if (buf1[0] != NULLCHAR) {
8704                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8705                 }
8706
8707                 cpstats.ok_to_send = 0;
8708                 cpstats.line_is_book = 0;
8709                 cpstats.nr_moves = 0;
8710                 cpstats.moves_left = 0;
8711
8712                 SendProgramStatsToFrontend( cps, &cpstats );
8713             }
8714         }
8715     }
8716 }
8717
8718
8719 /* Parse a game score from the character string "game", and
8720    record it as the history of the current game.  The game
8721    score is NOT assumed to start from the standard position.
8722    The display is not updated in any way.
8723    */
8724 void
8725 ParseGameHistory(game)
8726      char *game;
8727 {
8728     ChessMove moveType;
8729     int fromX, fromY, toX, toY, boardIndex;
8730     char promoChar;
8731     char *p, *q;
8732     char buf[MSG_SIZ];
8733
8734     if (appData.debugMode)
8735       fprintf(debugFP, "Parsing game history: %s\n", game);
8736
8737     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8738     gameInfo.site = StrSave(appData.icsHost);
8739     gameInfo.date = PGNDate();
8740     gameInfo.round = StrSave("-");
8741
8742     /* Parse out names of players */
8743     while (*game == ' ') game++;
8744     p = buf;
8745     while (*game != ' ') *p++ = *game++;
8746     *p = NULLCHAR;
8747     gameInfo.white = StrSave(buf);
8748     while (*game == ' ') game++;
8749     p = buf;
8750     while (*game != ' ' && *game != '\n') *p++ = *game++;
8751     *p = NULLCHAR;
8752     gameInfo.black = StrSave(buf);
8753
8754     /* Parse moves */
8755     boardIndex = blackPlaysFirst ? 1 : 0;
8756     yynewstr(game);
8757     for (;;) {
8758         yyboardindex = boardIndex;
8759         moveType = (ChessMove) Myylex();
8760         switch (moveType) {
8761           case IllegalMove:             /* maybe suicide chess, etc. */
8762   if (appData.debugMode) {
8763     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8764     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8765     setbuf(debugFP, NULL);
8766   }
8767           case WhitePromotion:
8768           case BlackPromotion:
8769           case WhiteNonPromotion:
8770           case BlackNonPromotion:
8771           case NormalMove:
8772           case WhiteCapturesEnPassant:
8773           case BlackCapturesEnPassant:
8774           case WhiteKingSideCastle:
8775           case WhiteQueenSideCastle:
8776           case BlackKingSideCastle:
8777           case BlackQueenSideCastle:
8778           case WhiteKingSideCastleWild:
8779           case WhiteQueenSideCastleWild:
8780           case BlackKingSideCastleWild:
8781           case BlackQueenSideCastleWild:
8782           /* PUSH Fabien */
8783           case WhiteHSideCastleFR:
8784           case WhiteASideCastleFR:
8785           case BlackHSideCastleFR:
8786           case BlackASideCastleFR:
8787           /* POP Fabien */
8788             fromX = currentMoveString[0] - AAA;
8789             fromY = currentMoveString[1] - ONE;
8790             toX = currentMoveString[2] - AAA;
8791             toY = currentMoveString[3] - ONE;
8792             promoChar = currentMoveString[4];
8793             break;
8794           case WhiteDrop:
8795           case BlackDrop:
8796             fromX = moveType == WhiteDrop ?
8797               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8798             (int) CharToPiece(ToLower(currentMoveString[0]));
8799             fromY = DROP_RANK;
8800             toX = currentMoveString[2] - AAA;
8801             toY = currentMoveString[3] - ONE;
8802             promoChar = NULLCHAR;
8803             break;
8804           case AmbiguousMove:
8805             /* bug? */
8806             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8807   if (appData.debugMode) {
8808     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8809     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8810     setbuf(debugFP, NULL);
8811   }
8812             DisplayError(buf, 0);
8813             return;
8814           case ImpossibleMove:
8815             /* bug? */
8816             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8817   if (appData.debugMode) {
8818     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8819     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8820     setbuf(debugFP, NULL);
8821   }
8822             DisplayError(buf, 0);
8823             return;
8824           case EndOfFile:
8825             if (boardIndex < backwardMostMove) {
8826                 /* Oops, gap.  How did that happen? */
8827                 DisplayError(_("Gap in move list"), 0);
8828                 return;
8829             }
8830             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8831             if (boardIndex > forwardMostMove) {
8832                 forwardMostMove = boardIndex;
8833             }
8834             return;
8835           case ElapsedTime:
8836             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8837                 strcat(parseList[boardIndex-1], " ");
8838                 strcat(parseList[boardIndex-1], yy_text);
8839             }
8840             continue;
8841           case Comment:
8842           case PGNTag:
8843           case NAG:
8844           default:
8845             /* ignore */
8846             continue;
8847           case WhiteWins:
8848           case BlackWins:
8849           case GameIsDrawn:
8850           case GameUnfinished:
8851             if (gameMode == IcsExamining) {
8852                 if (boardIndex < backwardMostMove) {
8853                     /* Oops, gap.  How did that happen? */
8854                     return;
8855                 }
8856                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8857                 return;
8858             }
8859             gameInfo.result = moveType;
8860             p = strchr(yy_text, '{');
8861             if (p == NULL) p = strchr(yy_text, '(');
8862             if (p == NULL) {
8863                 p = yy_text;
8864                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8865             } else {
8866                 q = strchr(p, *p == '{' ? '}' : ')');
8867                 if (q != NULL) *q = NULLCHAR;
8868                 p++;
8869             }
8870             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8871             gameInfo.resultDetails = StrSave(p);
8872             continue;
8873         }
8874         if (boardIndex >= forwardMostMove &&
8875             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8876             backwardMostMove = blackPlaysFirst ? 1 : 0;
8877             return;
8878         }
8879         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8880                                  fromY, fromX, toY, toX, promoChar,
8881                                  parseList[boardIndex]);
8882         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8883         /* currentMoveString is set as a side-effect of yylex */
8884         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8885         strcat(moveList[boardIndex], "\n");
8886         boardIndex++;
8887         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8888         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8889           case MT_NONE:
8890           case MT_STALEMATE:
8891           default:
8892             break;
8893           case MT_CHECK:
8894             if(gameInfo.variant != VariantShogi)
8895                 strcat(parseList[boardIndex - 1], "+");
8896             break;
8897           case MT_CHECKMATE:
8898           case MT_STAINMATE:
8899             strcat(parseList[boardIndex - 1], "#");
8900             break;
8901         }
8902     }
8903 }
8904
8905
8906 /* Apply a move to the given board  */
8907 void
8908 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8909      int fromX, fromY, toX, toY;
8910      int promoChar;
8911      Board board;
8912 {
8913   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8914   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8915
8916     /* [HGM] compute & store e.p. status and castling rights for new position */
8917     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8918
8919       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8920       oldEP = (signed char)board[EP_STATUS];
8921       board[EP_STATUS] = EP_NONE;
8922
8923       if( board[toY][toX] != EmptySquare )
8924            board[EP_STATUS] = EP_CAPTURE;
8925
8926   if (fromY == DROP_RANK) {
8927         /* must be first */
8928         piece = board[toY][toX] = (ChessSquare) fromX;
8929   } else {
8930       int i;
8931
8932       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8933            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8934                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8935       } else
8936       if( board[fromY][fromX] == WhitePawn ) {
8937            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8938                board[EP_STATUS] = EP_PAWN_MOVE;
8939            if( toY-fromY==2) {
8940                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8941                         gameInfo.variant != VariantBerolina || toX < fromX)
8942                       board[EP_STATUS] = toX | berolina;
8943                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8944                         gameInfo.variant != VariantBerolina || toX > fromX)
8945                       board[EP_STATUS] = toX;
8946            }
8947       } else
8948       if( board[fromY][fromX] == BlackPawn ) {
8949            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8950                board[EP_STATUS] = EP_PAWN_MOVE;
8951            if( toY-fromY== -2) {
8952                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8953                         gameInfo.variant != VariantBerolina || toX < fromX)
8954                       board[EP_STATUS] = toX | berolina;
8955                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8956                         gameInfo.variant != VariantBerolina || toX > fromX)
8957                       board[EP_STATUS] = toX;
8958            }
8959        }
8960
8961        for(i=0; i<nrCastlingRights; i++) {
8962            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8963               board[CASTLING][i] == toX   && castlingRank[i] == toY
8964              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8965        }
8966
8967      if (fromX == toX && fromY == toY) return;
8968
8969      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8970      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8971      if(gameInfo.variant == VariantKnightmate)
8972          king += (int) WhiteUnicorn - (int) WhiteKing;
8973
8974     /* Code added by Tord: */
8975     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8976     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8977         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8978       board[fromY][fromX] = EmptySquare;
8979       board[toY][toX] = EmptySquare;
8980       if((toX > fromX) != (piece == WhiteRook)) {
8981         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8982       } else {
8983         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8984       }
8985     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8986                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8987       board[fromY][fromX] = EmptySquare;
8988       board[toY][toX] = EmptySquare;
8989       if((toX > fromX) != (piece == BlackRook)) {
8990         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8991       } else {
8992         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8993       }
8994     /* End of code added by Tord */
8995
8996     } else if (board[fromY][fromX] == king
8997         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8998         && toY == fromY && toX > fromX+1) {
8999         board[fromY][fromX] = EmptySquare;
9000         board[toY][toX] = king;
9001         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9002         board[fromY][BOARD_RGHT-1] = EmptySquare;
9003     } else if (board[fromY][fromX] == king
9004         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9005                && toY == fromY && toX < fromX-1) {
9006         board[fromY][fromX] = EmptySquare;
9007         board[toY][toX] = king;
9008         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9009         board[fromY][BOARD_LEFT] = EmptySquare;
9010     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9011                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9012                && toY >= BOARD_HEIGHT-promoRank
9013                ) {
9014         /* white pawn promotion */
9015         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9016         if (board[toY][toX] == EmptySquare) {
9017             board[toY][toX] = WhiteQueen;
9018         }
9019         if(gameInfo.variant==VariantBughouse ||
9020            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9021             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9022         board[fromY][fromX] = EmptySquare;
9023     } else if ((fromY == BOARD_HEIGHT-4)
9024                && (toX != fromX)
9025                && gameInfo.variant != VariantXiangqi
9026                && gameInfo.variant != VariantBerolina
9027                && (board[fromY][fromX] == WhitePawn)
9028                && (board[toY][toX] == EmptySquare)) {
9029         board[fromY][fromX] = EmptySquare;
9030         board[toY][toX] = WhitePawn;
9031         captured = board[toY - 1][toX];
9032         board[toY - 1][toX] = EmptySquare;
9033     } else if ((fromY == BOARD_HEIGHT-4)
9034                && (toX == fromX)
9035                && gameInfo.variant == VariantBerolina
9036                && (board[fromY][fromX] == WhitePawn)
9037                && (board[toY][toX] == EmptySquare)) {
9038         board[fromY][fromX] = EmptySquare;
9039         board[toY][toX] = WhitePawn;
9040         if(oldEP & EP_BEROLIN_A) {
9041                 captured = board[fromY][fromX-1];
9042                 board[fromY][fromX-1] = EmptySquare;
9043         }else{  captured = board[fromY][fromX+1];
9044                 board[fromY][fromX+1] = EmptySquare;
9045         }
9046     } else if (board[fromY][fromX] == king
9047         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9048                && toY == fromY && toX > fromX+1) {
9049         board[fromY][fromX] = EmptySquare;
9050         board[toY][toX] = king;
9051         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9052         board[fromY][BOARD_RGHT-1] = EmptySquare;
9053     } else if (board[fromY][fromX] == king
9054         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9055                && toY == fromY && toX < fromX-1) {
9056         board[fromY][fromX] = EmptySquare;
9057         board[toY][toX] = king;
9058         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9059         board[fromY][BOARD_LEFT] = EmptySquare;
9060     } else if (fromY == 7 && fromX == 3
9061                && board[fromY][fromX] == BlackKing
9062                && toY == 7 && toX == 5) {
9063         board[fromY][fromX] = EmptySquare;
9064         board[toY][toX] = BlackKing;
9065         board[fromY][7] = EmptySquare;
9066         board[toY][4] = BlackRook;
9067     } else if (fromY == 7 && fromX == 3
9068                && board[fromY][fromX] == BlackKing
9069                && toY == 7 && toX == 1) {
9070         board[fromY][fromX] = EmptySquare;
9071         board[toY][toX] = BlackKing;
9072         board[fromY][0] = EmptySquare;
9073         board[toY][2] = BlackRook;
9074     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9075                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9076                && toY < promoRank
9077                ) {
9078         /* black pawn promotion */
9079         board[toY][toX] = CharToPiece(ToLower(promoChar));
9080         if (board[toY][toX] == EmptySquare) {
9081             board[toY][toX] = BlackQueen;
9082         }
9083         if(gameInfo.variant==VariantBughouse ||
9084            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9085             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9086         board[fromY][fromX] = EmptySquare;
9087     } else if ((fromY == 3)
9088                && (toX != fromX)
9089                && gameInfo.variant != VariantXiangqi
9090                && gameInfo.variant != VariantBerolina
9091                && (board[fromY][fromX] == BlackPawn)
9092                && (board[toY][toX] == EmptySquare)) {
9093         board[fromY][fromX] = EmptySquare;
9094         board[toY][toX] = BlackPawn;
9095         captured = board[toY + 1][toX];
9096         board[toY + 1][toX] = EmptySquare;
9097     } else if ((fromY == 3)
9098                && (toX == fromX)
9099                && gameInfo.variant == VariantBerolina
9100                && (board[fromY][fromX] == BlackPawn)
9101                && (board[toY][toX] == EmptySquare)) {
9102         board[fromY][fromX] = EmptySquare;
9103         board[toY][toX] = BlackPawn;
9104         if(oldEP & EP_BEROLIN_A) {
9105                 captured = board[fromY][fromX-1];
9106                 board[fromY][fromX-1] = EmptySquare;
9107         }else{  captured = board[fromY][fromX+1];
9108                 board[fromY][fromX+1] = EmptySquare;
9109         }
9110     } else {
9111         board[toY][toX] = board[fromY][fromX];
9112         board[fromY][fromX] = EmptySquare;
9113     }
9114   }
9115
9116     if (gameInfo.holdingsWidth != 0) {
9117
9118       /* !!A lot more code needs to be written to support holdings  */
9119       /* [HGM] OK, so I have written it. Holdings are stored in the */
9120       /* penultimate board files, so they are automaticlly stored   */
9121       /* in the game history.                                       */
9122       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9123                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9124         /* Delete from holdings, by decreasing count */
9125         /* and erasing image if necessary            */
9126         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9127         if(p < (int) BlackPawn) { /* white drop */
9128              p -= (int)WhitePawn;
9129                  p = PieceToNumber((ChessSquare)p);
9130              if(p >= gameInfo.holdingsSize) p = 0;
9131              if(--board[p][BOARD_WIDTH-2] <= 0)
9132                   board[p][BOARD_WIDTH-1] = EmptySquare;
9133              if((int)board[p][BOARD_WIDTH-2] < 0)
9134                         board[p][BOARD_WIDTH-2] = 0;
9135         } else {                  /* black drop */
9136              p -= (int)BlackPawn;
9137                  p = PieceToNumber((ChessSquare)p);
9138              if(p >= gameInfo.holdingsSize) p = 0;
9139              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9140                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9141              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9142                         board[BOARD_HEIGHT-1-p][1] = 0;
9143         }
9144       }
9145       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9146           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9147         /* [HGM] holdings: Add to holdings, if holdings exist */
9148         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9149                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9150                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9151         }
9152         p = (int) captured;
9153         if (p >= (int) BlackPawn) {
9154           p -= (int)BlackPawn;
9155           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9156                   /* in Shogi restore piece to its original  first */
9157                   captured = (ChessSquare) (DEMOTED captured);
9158                   p = DEMOTED p;
9159           }
9160           p = PieceToNumber((ChessSquare)p);
9161           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9162           board[p][BOARD_WIDTH-2]++;
9163           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9164         } else {
9165           p -= (int)WhitePawn;
9166           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9167                   captured = (ChessSquare) (DEMOTED captured);
9168                   p = DEMOTED p;
9169           }
9170           p = PieceToNumber((ChessSquare)p);
9171           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9172           board[BOARD_HEIGHT-1-p][1]++;
9173           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9174         }
9175       }
9176     } else if (gameInfo.variant == VariantAtomic) {
9177       if (captured != EmptySquare) {
9178         int y, x;
9179         for (y = toY-1; y <= toY+1; y++) {
9180           for (x = toX-1; x <= toX+1; x++) {
9181             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9182                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9183               board[y][x] = EmptySquare;
9184             }
9185           }
9186         }
9187         board[toY][toX] = EmptySquare;
9188       }
9189     }
9190     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9191         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9192     } else
9193     if(promoChar == '+') {
9194         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9195         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9196     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9197         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9198     }
9199     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9200                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9201         // [HGM] superchess: take promotion piece out of holdings
9202         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9203         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9204             if(!--board[k][BOARD_WIDTH-2])
9205                 board[k][BOARD_WIDTH-1] = EmptySquare;
9206         } else {
9207             if(!--board[BOARD_HEIGHT-1-k][1])
9208                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9209         }
9210     }
9211
9212 }
9213
9214 /* Updates forwardMostMove */
9215 void
9216 MakeMove(fromX, fromY, toX, toY, promoChar)
9217      int fromX, fromY, toX, toY;
9218      int promoChar;
9219 {
9220 //    forwardMostMove++; // [HGM] bare: moved downstream
9221
9222     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9223         int timeLeft; static int lastLoadFlag=0; int king, piece;
9224         piece = boards[forwardMostMove][fromY][fromX];
9225         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9226         if(gameInfo.variant == VariantKnightmate)
9227             king += (int) WhiteUnicorn - (int) WhiteKing;
9228         if(forwardMostMove == 0) {
9229             if(blackPlaysFirst)
9230                 fprintf(serverMoves, "%s;", second.tidy);
9231             fprintf(serverMoves, "%s;", first.tidy);
9232             if(!blackPlaysFirst)
9233                 fprintf(serverMoves, "%s;", second.tidy);
9234         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9235         lastLoadFlag = loadFlag;
9236         // print base move
9237         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9238         // print castling suffix
9239         if( toY == fromY && piece == king ) {
9240             if(toX-fromX > 1)
9241                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9242             if(fromX-toX >1)
9243                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9244         }
9245         // e.p. suffix
9246         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9247              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9248              boards[forwardMostMove][toY][toX] == EmptySquare
9249              && fromX != toX && fromY != toY)
9250                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9251         // promotion suffix
9252         if(promoChar != NULLCHAR)
9253                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9254         if(!loadFlag) {
9255             fprintf(serverMoves, "/%d/%d",
9256                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9257             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9258             else                      timeLeft = blackTimeRemaining/1000;
9259             fprintf(serverMoves, "/%d", timeLeft);
9260         }
9261         fflush(serverMoves);
9262     }
9263
9264     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9265       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9266                         0, 1);
9267       return;
9268     }
9269     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9270     if (commentList[forwardMostMove+1] != NULL) {
9271         free(commentList[forwardMostMove+1]);
9272         commentList[forwardMostMove+1] = NULL;
9273     }
9274     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9275     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9276     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9277     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9278     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9279     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9280     gameInfo.result = GameUnfinished;
9281     if (gameInfo.resultDetails != NULL) {
9282         free(gameInfo.resultDetails);
9283         gameInfo.resultDetails = NULL;
9284     }
9285     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9286                               moveList[forwardMostMove - 1]);
9287     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9288                              PosFlags(forwardMostMove - 1),
9289                              fromY, fromX, toY, toX, promoChar,
9290                              parseList[forwardMostMove - 1]);
9291     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9292       case MT_NONE:
9293       case MT_STALEMATE:
9294       default:
9295         break;
9296       case MT_CHECK:
9297         if(gameInfo.variant != VariantShogi)
9298             strcat(parseList[forwardMostMove - 1], "+");
9299         break;
9300       case MT_CHECKMATE:
9301       case MT_STAINMATE:
9302         strcat(parseList[forwardMostMove - 1], "#");
9303         break;
9304     }
9305     if (appData.debugMode) {
9306         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9307     }
9308
9309 }
9310
9311 /* Updates currentMove if not pausing */
9312 void
9313 ShowMove(fromX, fromY, toX, toY)
9314 {
9315     int instant = (gameMode == PlayFromGameFile) ?
9316         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9317     if(appData.noGUI) return;
9318     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9319         if (!instant) {
9320             if (forwardMostMove == currentMove + 1) {
9321                 AnimateMove(boards[forwardMostMove - 1],
9322                             fromX, fromY, toX, toY);
9323             }
9324             if (appData.highlightLastMove) {
9325                 SetHighlights(fromX, fromY, toX, toY);
9326             }
9327         }
9328         currentMove = forwardMostMove;
9329     }
9330
9331     if (instant) return;
9332
9333     DisplayMove(currentMove - 1);
9334     DrawPosition(FALSE, boards[currentMove]);
9335     DisplayBothClocks();
9336     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9337     DisplayBook(currentMove);
9338 }
9339
9340 void SendEgtPath(ChessProgramState *cps)
9341 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9342         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9343
9344         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9345
9346         while(*p) {
9347             char c, *q = name+1, *r, *s;
9348
9349             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9350             while(*p && *p != ',') *q++ = *p++;
9351             *q++ = ':'; *q = 0;
9352             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9353                 strcmp(name, ",nalimov:") == 0 ) {
9354                 // take nalimov path from the menu-changeable option first, if it is defined
9355               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9356                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9357             } else
9358             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9359                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9360                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9361                 s = r = StrStr(s, ":") + 1; // beginning of path info
9362                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9363                 c = *r; *r = 0;             // temporarily null-terminate path info
9364                     *--q = 0;               // strip of trailig ':' from name
9365                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9366                 *r = c;
9367                 SendToProgram(buf,cps);     // send egtbpath command for this format
9368             }
9369             if(*p == ',') p++; // read away comma to position for next format name
9370         }
9371 }
9372
9373 void
9374 InitChessProgram(cps, setup)
9375      ChessProgramState *cps;
9376      int setup; /* [HGM] needed to setup FRC opening position */
9377 {
9378     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9379     if (appData.noChessProgram) return;
9380     hintRequested = FALSE;
9381     bookRequested = FALSE;
9382
9383     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9384     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9385     if(cps->memSize) { /* [HGM] memory */
9386       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9387         SendToProgram(buf, cps);
9388     }
9389     SendEgtPath(cps); /* [HGM] EGT */
9390     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9391       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9392         SendToProgram(buf, cps);
9393     }
9394
9395     SendToProgram(cps->initString, cps);
9396     if (gameInfo.variant != VariantNormal &&
9397         gameInfo.variant != VariantLoadable
9398         /* [HGM] also send variant if board size non-standard */
9399         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9400                                             ) {
9401       char *v = VariantName(gameInfo.variant);
9402       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9403         /* [HGM] in protocol 1 we have to assume all variants valid */
9404         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9405         DisplayFatalError(buf, 0, 1);
9406         return;
9407       }
9408
9409       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9410       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9411       if( gameInfo.variant == VariantXiangqi )
9412            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9413       if( gameInfo.variant == VariantShogi )
9414            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9415       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9416            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9417       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9418           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9419            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9420       if( gameInfo.variant == VariantCourier )
9421            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9422       if( gameInfo.variant == VariantSuper )
9423            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9424       if( gameInfo.variant == VariantGreat )
9425            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9426       if( gameInfo.variant == VariantSChess )
9427            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9428
9429       if(overruled) {
9430         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9431                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9432            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9433            if(StrStr(cps->variants, b) == NULL) {
9434                // specific sized variant not known, check if general sizing allowed
9435                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9436                    if(StrStr(cps->variants, "boardsize") == NULL) {
9437                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9438                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9439                        DisplayFatalError(buf, 0, 1);
9440                        return;
9441                    }
9442                    /* [HGM] here we really should compare with the maximum supported board size */
9443                }
9444            }
9445       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9446       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9447       SendToProgram(buf, cps);
9448     }
9449     currentlyInitializedVariant = gameInfo.variant;
9450
9451     /* [HGM] send opening position in FRC to first engine */
9452     if(setup) {
9453           SendToProgram("force\n", cps);
9454           SendBoard(cps, 0);
9455           /* engine is now in force mode! Set flag to wake it up after first move. */
9456           setboardSpoiledMachineBlack = 1;
9457     }
9458
9459     if (cps->sendICS) {
9460       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9461       SendToProgram(buf, cps);
9462     }
9463     cps->maybeThinking = FALSE;
9464     cps->offeredDraw = 0;
9465     if (!appData.icsActive) {
9466         SendTimeControl(cps, movesPerSession, timeControl,
9467                         timeIncrement, appData.searchDepth,
9468                         searchTime);
9469     }
9470     if (appData.showThinking
9471         // [HGM] thinking: four options require thinking output to be sent
9472         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9473                                 ) {
9474         SendToProgram("post\n", cps);
9475     }
9476     SendToProgram("hard\n", cps);
9477     if (!appData.ponderNextMove) {
9478         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9479            it without being sure what state we are in first.  "hard"
9480            is not a toggle, so that one is OK.
9481          */
9482         SendToProgram("easy\n", cps);
9483     }
9484     if (cps->usePing) {
9485       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9486       SendToProgram(buf, cps);
9487     }
9488     cps->initDone = TRUE;
9489 }
9490
9491
9492 void
9493 StartChessProgram(cps)
9494      ChessProgramState *cps;
9495 {
9496     char buf[MSG_SIZ];
9497     int err;
9498
9499     if (appData.noChessProgram) return;
9500     cps->initDone = FALSE;
9501
9502     if (strcmp(cps->host, "localhost") == 0) {
9503         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9504     } else if (*appData.remoteShell == NULLCHAR) {
9505         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9506     } else {
9507         if (*appData.remoteUser == NULLCHAR) {
9508           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9509                     cps->program);
9510         } else {
9511           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9512                     cps->host, appData.remoteUser, cps->program);
9513         }
9514         err = StartChildProcess(buf, "", &cps->pr);
9515     }
9516
9517     if (err != 0) {
9518       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9519         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9520         if(cps != &first) return;
9521         appData.noChessProgram = TRUE;
9522         ThawUI();
9523         SetNCPMode();
9524 //      DisplayFatalError(buf, err, 1);
9525 //      cps->pr = NoProc;
9526 //      cps->isr = NULL;
9527         return;
9528     }
9529
9530     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9531     if (cps->protocolVersion > 1) {
9532       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9533       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9534       cps->comboCnt = 0;  //                and values of combo boxes
9535       SendToProgram(buf, cps);
9536     } else {
9537       SendToProgram("xboard\n", cps);
9538     }
9539 }
9540
9541 void
9542 TwoMachinesEventIfReady P((void))
9543 {
9544   static int curMess = 0;
9545   if (first.lastPing != first.lastPong) {
9546     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9547     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9548     return;
9549   }
9550   if (second.lastPing != second.lastPong) {
9551     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9552     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9553     return;
9554   }
9555   DisplayMessage("", ""); curMess = 0;
9556   ThawUI();
9557   TwoMachinesEvent();
9558 }
9559
9560 char *
9561 MakeName(char *template)
9562 {
9563     time_t clock;
9564     struct tm *tm;
9565     static char buf[MSG_SIZ];
9566     char *p = buf;
9567     int i;
9568
9569     clock = time((time_t *)NULL);
9570     tm = localtime(&clock);
9571
9572     while(*p++ = *template++) if(p[-1] == '%') {
9573         switch(*template++) {
9574           case 0:   *p = 0; return buf;
9575           case 'Y': i = tm->tm_year+1900; break;
9576           case 'y': i = tm->tm_year-100; break;
9577           case 'M': i = tm->tm_mon+1; break;
9578           case 'd': i = tm->tm_mday; break;
9579           case 'h': i = tm->tm_hour; break;
9580           case 'm': i = tm->tm_min; break;
9581           case 's': i = tm->tm_sec; break;
9582           default:  i = 0;
9583         }
9584         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9585     }
9586     return buf;
9587 }
9588
9589 int
9590 CountPlayers(char *p)
9591 {
9592     int n = 0;
9593     while(p = strchr(p, '\n')) p++, n++; // count participants
9594     return n;
9595 }
9596
9597 FILE *
9598 WriteTourneyFile(char *results)
9599 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9600     FILE *f = fopen(appData.tourneyFile, "w");
9601     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9602         // create a file with tournament description
9603         fprintf(f, "-participants {%s}\n", appData.participants);
9604         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9605         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9606         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9607         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9608         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9609         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9610         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9611         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9612         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9613         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9614         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9615         if(searchTime > 0)
9616                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9617         else {
9618                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9619                 fprintf(f, "-tc %s\n", appData.timeControl);
9620                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9621         }
9622         fprintf(f, "-results \"%s\"\n", results);
9623     }
9624     return f;
9625 }
9626
9627 int
9628 CreateTourney(char *name)
9629 {
9630         FILE *f;
9631         if(name[0] == NULLCHAR) {
9632             if(appData.participants[0])
9633                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9634             return 0;
9635         }
9636         f = fopen(name, "r");
9637         if(f) { // file exists
9638             ASSIGN(appData.tourneyFile, name);
9639             ParseArgsFromFile(f); // parse it
9640         } else {
9641             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9642             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9643                 DisplayError(_("Not enough participants"), 0);
9644                 return 0;
9645             }
9646             ASSIGN(appData.tourneyFile, name);
9647             if((f = WriteTourneyFile("")) == NULL) return 0;
9648         }
9649         fclose(f);
9650         appData.noChessProgram = FALSE;
9651         appData.clockMode = TRUE;
9652         SetGNUMode();
9653         return 1;
9654 }
9655
9656 #define MAXENGINES 1000
9657 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9658
9659 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9660 {
9661     char buf[MSG_SIZ], *p, *q;
9662     int i=1;
9663     while(*names) {
9664         p = names; q = buf;
9665         while(*p && *p != '\n') *q++ = *p++;
9666         *q = 0;
9667         if(engineList[i]) free(engineList[i]);
9668         engineList[i] = strdup(buf);
9669         if(*p == '\n') p++;
9670         TidyProgramName(engineList[i], "localhost", buf);
9671         if(engineMnemonic[i]) free(engineMnemonic[i]);
9672         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9673             strcat(buf, " (");
9674             sscanf(q + 8, "%s", buf + strlen(buf));
9675             strcat(buf, ")");
9676         }
9677         engineMnemonic[i] = strdup(buf);
9678         names = p; i++;
9679       if(i > MAXENGINES - 2) break;
9680     }
9681     engineList[i] = NULL;
9682 }
9683
9684 // following implemented as macro to avoid type limitations
9685 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9686
9687 void SwapEngines(int n)
9688 {   // swap settings for first engine and other engine (so far only some selected options)
9689     int h;
9690     char *p;
9691     if(n == 0) return;
9692     SWAP(directory, p)
9693     SWAP(chessProgram, p)
9694     SWAP(isUCI, h)
9695     SWAP(hasOwnBookUCI, h)
9696     SWAP(protocolVersion, h)
9697     SWAP(reuse, h)
9698     SWAP(scoreIsAbsolute, h)
9699     SWAP(timeOdds, h)
9700     SWAP(logo, p)
9701     SWAP(pgnName, p)
9702 }
9703
9704 void
9705 SetPlayer(int player)
9706 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9707     int i;
9708     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9709     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9710     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9711     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9712     if(mnemonic[i]) {
9713         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9714         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9715         ParseArgsFromString(buf);
9716     }
9717     free(engineName);
9718 }
9719
9720 int
9721 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9722 {   // determine players from game number
9723     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9724
9725     if(appData.tourneyType == 0) {
9726         roundsPerCycle = (nPlayers - 1) | 1;
9727         pairingsPerRound = nPlayers / 2;
9728     } else if(appData.tourneyType > 0) {
9729         roundsPerCycle = nPlayers - appData.tourneyType;
9730         pairingsPerRound = appData.tourneyType;
9731     }
9732     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9733     gamesPerCycle = gamesPerRound * roundsPerCycle;
9734     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9735     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9736     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9737     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9738     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9739     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9740
9741     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9742     if(appData.roundSync) *syncInterval = gamesPerRound;
9743
9744     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9745
9746     if(appData.tourneyType == 0) {
9747         if(curPairing == (nPlayers-1)/2 ) {
9748             *whitePlayer = curRound;
9749             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9750         } else {
9751             *whitePlayer = curRound - pairingsPerRound + curPairing;
9752             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9753             *blackPlayer = curRound + pairingsPerRound - curPairing;
9754             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9755         }
9756     } else if(appData.tourneyType > 0) {
9757         *whitePlayer = curPairing;
9758         *blackPlayer = curRound + appData.tourneyType;
9759     }
9760
9761     // take care of white/black alternation per round. 
9762     // For cycles and games this is already taken care of by default, derived from matchGame!
9763     return curRound & 1;
9764 }
9765
9766 int
9767 NextTourneyGame(int nr, int *swapColors)
9768 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9769     char *p, *q;
9770     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9771     FILE *tf;
9772     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9773     tf = fopen(appData.tourneyFile, "r");
9774     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9775     ParseArgsFromFile(tf); fclose(tf);
9776     InitTimeControls(); // TC might be altered from tourney file
9777
9778     nPlayers = CountPlayers(appData.participants); // count participants
9779     if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9780         if(nr>=0 && !pairingReceived) {
9781             char buf[1<<16];
9782             if(pairing.pr == NoProc) StartChessProgram(&pairing);
9783             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9784             SendToProgram(buf, &pairing);
9785             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9786             SendToProgram(buf, &pairing);
9787             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9788         }
9789         pairingReceived = 0;                              // ... so we continue here 
9790         syncInterval = nPlayers/2; *swapColors = 0;
9791         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9792         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9793         matchGame = 1; roundNr = nr / syncInterval + 1;
9794     } else
9795     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9796
9797     if(syncInterval) {
9798         p = q = appData.results;
9799         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9800         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9801             DisplayMessage(_("Waiting for other game(s)"),"");
9802             waitingForGame = TRUE;
9803             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9804             return 0;
9805         }
9806         waitingForGame = FALSE;
9807     }
9808
9809     if(first.pr != NoProc) return 1; // engines already loaded
9810
9811     // redefine engines, engine dir, etc.
9812     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9813     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9814     SwapEngines(1);
9815     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9816     SwapEngines(1);         // and make that valid for second engine by swapping
9817     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9818     InitEngine(&second, 1);
9819     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9820     return 1;
9821 }
9822
9823 void
9824 NextMatchGame()
9825 {   // performs game initialization that does not invoke engines, and then tries to start the game
9826     int firstWhite, swapColors = 0;
9827     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9828     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9829     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9830     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9831     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9832     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9833     Reset(FALSE, first.pr != NoProc);
9834     appData.noChessProgram = FALSE;
9835     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9836     TwoMachinesEvent();
9837 }
9838
9839 void UserAdjudicationEvent( int result )
9840 {
9841     ChessMove gameResult = GameIsDrawn;
9842
9843     if( result > 0 ) {
9844         gameResult = WhiteWins;
9845     }
9846     else if( result < 0 ) {
9847         gameResult = BlackWins;
9848     }
9849
9850     if( gameMode == TwoMachinesPlay ) {
9851         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9852     }
9853 }
9854
9855
9856 // [HGM] save: calculate checksum of game to make games easily identifiable
9857 int StringCheckSum(char *s)
9858 {
9859         int i = 0;
9860         if(s==NULL) return 0;
9861         while(*s) i = i*259 + *s++;
9862         return i;
9863 }
9864
9865 int GameCheckSum()
9866 {
9867         int i, sum=0;
9868         for(i=backwardMostMove; i<forwardMostMove; i++) {
9869                 sum += pvInfoList[i].depth;
9870                 sum += StringCheckSum(parseList[i]);
9871                 sum += StringCheckSum(commentList[i]);
9872                 sum *= 261;
9873         }
9874         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9875         return sum + StringCheckSum(commentList[i]);
9876 } // end of save patch
9877
9878 void
9879 GameEnds(result, resultDetails, whosays)
9880      ChessMove result;
9881      char *resultDetails;
9882      int whosays;
9883 {
9884     GameMode nextGameMode;
9885     int isIcsGame;
9886     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9887
9888     if(endingGame) return; /* [HGM] crash: forbid recursion */
9889     endingGame = 1;
9890     if(twoBoards) { // [HGM] dual: switch back to one board
9891         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9892         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9893     }
9894     if (appData.debugMode) {
9895       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9896               result, resultDetails ? resultDetails : "(null)", whosays);
9897     }
9898
9899     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9900
9901     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9902         /* If we are playing on ICS, the server decides when the
9903            game is over, but the engine can offer to draw, claim
9904            a draw, or resign.
9905          */
9906 #if ZIPPY
9907         if (appData.zippyPlay && first.initDone) {
9908             if (result == GameIsDrawn) {
9909                 /* In case draw still needs to be claimed */
9910                 SendToICS(ics_prefix);
9911                 SendToICS("draw\n");
9912             } else if (StrCaseStr(resultDetails, "resign")) {
9913                 SendToICS(ics_prefix);
9914                 SendToICS("resign\n");
9915             }
9916         }
9917 #endif
9918         endingGame = 0; /* [HGM] crash */
9919         return;
9920     }
9921
9922     /* If we're loading the game from a file, stop */
9923     if (whosays == GE_FILE) {
9924       (void) StopLoadGameTimer();
9925       gameFileFP = NULL;
9926     }
9927
9928     /* Cancel draw offers */
9929     first.offeredDraw = second.offeredDraw = 0;
9930
9931     /* If this is an ICS game, only ICS can really say it's done;
9932        if not, anyone can. */
9933     isIcsGame = (gameMode == IcsPlayingWhite ||
9934                  gameMode == IcsPlayingBlack ||
9935                  gameMode == IcsObserving    ||
9936                  gameMode == IcsExamining);
9937
9938     if (!isIcsGame || whosays == GE_ICS) {
9939         /* OK -- not an ICS game, or ICS said it was done */
9940         StopClocks();
9941         if (!isIcsGame && !appData.noChessProgram)
9942           SetUserThinkingEnables();
9943
9944         /* [HGM] if a machine claims the game end we verify this claim */
9945         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9946             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9947                 char claimer;
9948                 ChessMove trueResult = (ChessMove) -1;
9949
9950                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9951                                             first.twoMachinesColor[0] :
9952                                             second.twoMachinesColor[0] ;
9953
9954                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9955                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9956                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9957                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9958                 } else
9959                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9960                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9961                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9962                 } else
9963                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9964                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9965                 }
9966
9967                 // now verify win claims, but not in drop games, as we don't understand those yet
9968                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9969                                                  || gameInfo.variant == VariantGreat) &&
9970                     (result == WhiteWins && claimer == 'w' ||
9971                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9972                       if (appData.debugMode) {
9973                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9974                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9975                       }
9976                       if(result != trueResult) {
9977                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9978                               result = claimer == 'w' ? BlackWins : WhiteWins;
9979                               resultDetails = buf;
9980                       }
9981                 } else
9982                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9983                     && (forwardMostMove <= backwardMostMove ||
9984                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9985                         (claimer=='b')==(forwardMostMove&1))
9986                                                                                   ) {
9987                       /* [HGM] verify: draws that were not flagged are false claims */
9988                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9989                       result = claimer == 'w' ? BlackWins : WhiteWins;
9990                       resultDetails = buf;
9991                 }
9992                 /* (Claiming a loss is accepted no questions asked!) */
9993             }
9994             /* [HGM] bare: don't allow bare King to win */
9995             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9996                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9997                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9998                && result != GameIsDrawn)
9999             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10000                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10001                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10002                         if(p >= 0 && p <= (int)WhiteKing) k++;
10003                 }
10004                 if (appData.debugMode) {
10005                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10006                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10007                 }
10008                 if(k <= 1) {
10009                         result = GameIsDrawn;
10010                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10011                         resultDetails = buf;
10012                 }
10013             }
10014         }
10015
10016
10017         if(serverMoves != NULL && !loadFlag) { char c = '=';
10018             if(result==WhiteWins) c = '+';
10019             if(result==BlackWins) c = '-';
10020             if(resultDetails != NULL)
10021                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10022         }
10023         if (resultDetails != NULL) {
10024             gameInfo.result = result;
10025             gameInfo.resultDetails = StrSave(resultDetails);
10026
10027             /* display last move only if game was not loaded from file */
10028             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10029                 DisplayMove(currentMove - 1);
10030
10031             if (forwardMostMove != 0) {
10032                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10033                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10034                                                                 ) {
10035                     if (*appData.saveGameFile != NULLCHAR) {
10036                         SaveGameToFile(appData.saveGameFile, TRUE);
10037                     } else if (appData.autoSaveGames) {
10038                         AutoSaveGame();
10039                     }
10040                     if (*appData.savePositionFile != NULLCHAR) {
10041                         SavePositionToFile(appData.savePositionFile);
10042                     }
10043                 }
10044             }
10045
10046             /* Tell program how game ended in case it is learning */
10047             /* [HGM] Moved this to after saving the PGN, just in case */
10048             /* engine died and we got here through time loss. In that */
10049             /* case we will get a fatal error writing the pipe, which */
10050             /* would otherwise lose us the PGN.                       */
10051             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10052             /* output during GameEnds should never be fatal anymore   */
10053             if (gameMode == MachinePlaysWhite ||
10054                 gameMode == MachinePlaysBlack ||
10055                 gameMode == TwoMachinesPlay ||
10056                 gameMode == IcsPlayingWhite ||
10057                 gameMode == IcsPlayingBlack ||
10058                 gameMode == BeginningOfGame) {
10059                 char buf[MSG_SIZ];
10060                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10061                         resultDetails);
10062                 if (first.pr != NoProc) {
10063                     SendToProgram(buf, &first);
10064                 }
10065                 if (second.pr != NoProc &&
10066                     gameMode == TwoMachinesPlay) {
10067                     SendToProgram(buf, &second);
10068                 }
10069             }
10070         }
10071
10072         if (appData.icsActive) {
10073             if (appData.quietPlay &&
10074                 (gameMode == IcsPlayingWhite ||
10075                  gameMode == IcsPlayingBlack)) {
10076                 SendToICS(ics_prefix);
10077                 SendToICS("set shout 1\n");
10078             }
10079             nextGameMode = IcsIdle;
10080             ics_user_moved = FALSE;
10081             /* clean up premove.  It's ugly when the game has ended and the
10082              * premove highlights are still on the board.
10083              */
10084             if (gotPremove) {
10085               gotPremove = FALSE;
10086               ClearPremoveHighlights();
10087               DrawPosition(FALSE, boards[currentMove]);
10088             }
10089             if (whosays == GE_ICS) {
10090                 switch (result) {
10091                 case WhiteWins:
10092                     if (gameMode == IcsPlayingWhite)
10093                         PlayIcsWinSound();
10094                     else if(gameMode == IcsPlayingBlack)
10095                         PlayIcsLossSound();
10096                     break;
10097                 case BlackWins:
10098                     if (gameMode == IcsPlayingBlack)
10099                         PlayIcsWinSound();
10100                     else if(gameMode == IcsPlayingWhite)
10101                         PlayIcsLossSound();
10102                     break;
10103                 case GameIsDrawn:
10104                     PlayIcsDrawSound();
10105                     break;
10106                 default:
10107                     PlayIcsUnfinishedSound();
10108                 }
10109             }
10110         } else if (gameMode == EditGame ||
10111                    gameMode == PlayFromGameFile ||
10112                    gameMode == AnalyzeMode ||
10113                    gameMode == AnalyzeFile) {
10114             nextGameMode = gameMode;
10115         } else {
10116             nextGameMode = EndOfGame;
10117         }
10118         pausing = FALSE;
10119         ModeHighlight();
10120     } else {
10121         nextGameMode = gameMode;
10122     }
10123
10124     if (appData.noChessProgram) {
10125         gameMode = nextGameMode;
10126         ModeHighlight();
10127         endingGame = 0; /* [HGM] crash */
10128         return;
10129     }
10130
10131     if (first.reuse) {
10132         /* Put first chess program into idle state */
10133         if (first.pr != NoProc &&
10134             (gameMode == MachinePlaysWhite ||
10135              gameMode == MachinePlaysBlack ||
10136              gameMode == TwoMachinesPlay ||
10137              gameMode == IcsPlayingWhite ||
10138              gameMode == IcsPlayingBlack ||
10139              gameMode == BeginningOfGame)) {
10140             SendToProgram("force\n", &first);
10141             if (first.usePing) {
10142               char buf[MSG_SIZ];
10143               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10144               SendToProgram(buf, &first);
10145             }
10146         }
10147     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10148         /* Kill off first chess program */
10149         if (first.isr != NULL)
10150           RemoveInputSource(first.isr);
10151         first.isr = NULL;
10152
10153         if (first.pr != NoProc) {
10154             ExitAnalyzeMode();
10155             DoSleep( appData.delayBeforeQuit );
10156             SendToProgram("quit\n", &first);
10157             DoSleep( appData.delayAfterQuit );
10158             DestroyChildProcess(first.pr, first.useSigterm);
10159         }
10160         first.pr = NoProc;
10161     }
10162     if (second.reuse) {
10163         /* Put second chess program into idle state */
10164         if (second.pr != NoProc &&
10165             gameMode == TwoMachinesPlay) {
10166             SendToProgram("force\n", &second);
10167             if (second.usePing) {
10168               char buf[MSG_SIZ];
10169               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10170               SendToProgram(buf, &second);
10171             }
10172         }
10173     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10174         /* Kill off second chess program */
10175         if (second.isr != NULL)
10176           RemoveInputSource(second.isr);
10177         second.isr = NULL;
10178
10179         if (second.pr != NoProc) {
10180             DoSleep( appData.delayBeforeQuit );
10181             SendToProgram("quit\n", &second);
10182             DoSleep( appData.delayAfterQuit );
10183             DestroyChildProcess(second.pr, second.useSigterm);
10184         }
10185         second.pr = NoProc;
10186     }
10187
10188     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10189         char resChar = '=';
10190         switch (result) {
10191         case WhiteWins:
10192           resChar = '+';
10193           if (first.twoMachinesColor[0] == 'w') {
10194             first.matchWins++;
10195           } else {
10196             second.matchWins++;
10197           }
10198           break;
10199         case BlackWins:
10200           resChar = '-';
10201           if (first.twoMachinesColor[0] == 'b') {
10202             first.matchWins++;
10203           } else {
10204             second.matchWins++;
10205           }
10206           break;
10207         case GameUnfinished:
10208           resChar = ' ';
10209         default:
10210           break;
10211         }
10212
10213         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10214         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10215             ReserveGame(nextGame, resChar); // sets nextGame
10216             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10217         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10218
10219         if (nextGame <= appData.matchGames && !abortMatch) {
10220             gameMode = nextGameMode;
10221             matchGame = nextGame; // this will be overruled in tourney mode!
10222             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10223             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10224             endingGame = 0; /* [HGM] crash */
10225             return;
10226         } else {
10227             gameMode = nextGameMode;
10228             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10229                      first.tidy, second.tidy,
10230                      first.matchWins, second.matchWins,
10231                      appData.matchGames - (first.matchWins + second.matchWins));
10232             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10233             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10234                 first.twoMachinesColor = "black\n";
10235                 second.twoMachinesColor = "white\n";
10236             } else {
10237                 first.twoMachinesColor = "white\n";
10238                 second.twoMachinesColor = "black\n";
10239             }
10240         }
10241     }
10242     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10243         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10244       ExitAnalyzeMode();
10245     gameMode = nextGameMode;
10246     ModeHighlight();
10247     endingGame = 0;  /* [HGM] crash */
10248     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10249         if(matchMode == TRUE) { // match through command line: exit with or without popup
10250             if(ranking) {
10251                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10252                 else ExitEvent(0);
10253             } else DisplayFatalError(buf, 0, 0);
10254         } else { // match through menu; just stop, with or without popup
10255             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10256             if(ranking){
10257                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10258             } else DisplayNote(buf);
10259       }
10260       if(ranking) free(ranking);
10261     }
10262 }
10263
10264 /* Assumes program was just initialized (initString sent).
10265    Leaves program in force mode. */
10266 void
10267 FeedMovesToProgram(cps, upto)
10268      ChessProgramState *cps;
10269      int upto;
10270 {
10271     int i;
10272
10273     if (appData.debugMode)
10274       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10275               startedFromSetupPosition ? "position and " : "",
10276               backwardMostMove, upto, cps->which);
10277     if(currentlyInitializedVariant != gameInfo.variant) {
10278       char buf[MSG_SIZ];
10279         // [HGM] variantswitch: make engine aware of new variant
10280         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10281                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10282         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10283         SendToProgram(buf, cps);
10284         currentlyInitializedVariant = gameInfo.variant;
10285     }
10286     SendToProgram("force\n", cps);
10287     if (startedFromSetupPosition) {
10288         SendBoard(cps, backwardMostMove);
10289     if (appData.debugMode) {
10290         fprintf(debugFP, "feedMoves\n");
10291     }
10292     }
10293     for (i = backwardMostMove; i < upto; i++) {
10294         SendMoveToProgram(i, cps);
10295     }
10296 }
10297
10298
10299 int
10300 ResurrectChessProgram()
10301 {
10302      /* The chess program may have exited.
10303         If so, restart it and feed it all the moves made so far. */
10304     static int doInit = 0;
10305
10306     if (appData.noChessProgram) return 1;
10307
10308     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10309         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10310         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10311         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10312     } else {
10313         if (first.pr != NoProc) return 1;
10314         StartChessProgram(&first);
10315     }
10316     InitChessProgram(&first, FALSE);
10317     FeedMovesToProgram(&first, currentMove);
10318
10319     if (!first.sendTime) {
10320         /* can't tell gnuchess what its clock should read,
10321            so we bow to its notion. */
10322         ResetClocks();
10323         timeRemaining[0][currentMove] = whiteTimeRemaining;
10324         timeRemaining[1][currentMove] = blackTimeRemaining;
10325     }
10326
10327     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10328                 appData.icsEngineAnalyze) && first.analysisSupport) {
10329       SendToProgram("analyze\n", &first);
10330       first.analyzing = TRUE;
10331     }
10332     return 1;
10333 }
10334
10335 /*
10336  * Button procedures
10337  */
10338 void
10339 Reset(redraw, init)
10340      int redraw, init;
10341 {
10342     int i;
10343
10344     if (appData.debugMode) {
10345         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10346                 redraw, init, gameMode);
10347     }
10348     CleanupTail(); // [HGM] vari: delete any stored variations
10349     pausing = pauseExamInvalid = FALSE;
10350     startedFromSetupPosition = blackPlaysFirst = FALSE;
10351     firstMove = TRUE;
10352     whiteFlag = blackFlag = FALSE;
10353     userOfferedDraw = FALSE;
10354     hintRequested = bookRequested = FALSE;
10355     first.maybeThinking = FALSE;
10356     second.maybeThinking = FALSE;
10357     first.bookSuspend = FALSE; // [HGM] book
10358     second.bookSuspend = FALSE;
10359     thinkOutput[0] = NULLCHAR;
10360     lastHint[0] = NULLCHAR;
10361     ClearGameInfo(&gameInfo);
10362     gameInfo.variant = StringToVariant(appData.variant);
10363     ics_user_moved = ics_clock_paused = FALSE;
10364     ics_getting_history = H_FALSE;
10365     ics_gamenum = -1;
10366     white_holding[0] = black_holding[0] = NULLCHAR;
10367     ClearProgramStats();
10368     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10369
10370     ResetFrontEnd();
10371     ClearHighlights();
10372     flipView = appData.flipView;
10373     ClearPremoveHighlights();
10374     gotPremove = FALSE;
10375     alarmSounded = FALSE;
10376
10377     GameEnds(EndOfFile, NULL, GE_PLAYER);
10378     if(appData.serverMovesName != NULL) {
10379         /* [HGM] prepare to make moves file for broadcasting */
10380         clock_t t = clock();
10381         if(serverMoves != NULL) fclose(serverMoves);
10382         serverMoves = fopen(appData.serverMovesName, "r");
10383         if(serverMoves != NULL) {
10384             fclose(serverMoves);
10385             /* delay 15 sec before overwriting, so all clients can see end */
10386             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10387         }
10388         serverMoves = fopen(appData.serverMovesName, "w");
10389     }
10390
10391     ExitAnalyzeMode();
10392     gameMode = BeginningOfGame;
10393     ModeHighlight();
10394     if(appData.icsActive) gameInfo.variant = VariantNormal;
10395     currentMove = forwardMostMove = backwardMostMove = 0;
10396     InitPosition(redraw);
10397     for (i = 0; i < MAX_MOVES; i++) {
10398         if (commentList[i] != NULL) {
10399             free(commentList[i]);
10400             commentList[i] = NULL;
10401         }
10402     }
10403     ResetClocks();
10404     timeRemaining[0][0] = whiteTimeRemaining;
10405     timeRemaining[1][0] = blackTimeRemaining;
10406
10407     if (first.pr == NULL) {
10408         StartChessProgram(&first);
10409     }
10410     if (init) {
10411             InitChessProgram(&first, startedFromSetupPosition);
10412     }
10413     DisplayTitle("");
10414     DisplayMessage("", "");
10415     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10416     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10417 }
10418
10419 void
10420 AutoPlayGameLoop()
10421 {
10422     for (;;) {
10423         if (!AutoPlayOneMove())
10424           return;
10425         if (matchMode || appData.timeDelay == 0)
10426           continue;
10427         if (appData.timeDelay < 0)
10428           return;
10429         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10430         break;
10431     }
10432 }
10433
10434
10435 int
10436 AutoPlayOneMove()
10437 {
10438     int fromX, fromY, toX, toY;
10439
10440     if (appData.debugMode) {
10441       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10442     }
10443
10444     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10445       return FALSE;
10446
10447     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10448       pvInfoList[currentMove].depth = programStats.depth;
10449       pvInfoList[currentMove].score = programStats.score;
10450       pvInfoList[currentMove].time  = 0;
10451       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10452     }
10453
10454     if (currentMove >= forwardMostMove) {
10455       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10456       gameMode = EditGame;
10457       ModeHighlight();
10458
10459       /* [AS] Clear current move marker at the end of a game */
10460       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10461
10462       return FALSE;
10463     }
10464
10465     toX = moveList[currentMove][2] - AAA;
10466     toY = moveList[currentMove][3] - ONE;
10467
10468     if (moveList[currentMove][1] == '@') {
10469         if (appData.highlightLastMove) {
10470             SetHighlights(-1, -1, toX, toY);
10471         }
10472     } else {
10473         fromX = moveList[currentMove][0] - AAA;
10474         fromY = moveList[currentMove][1] - ONE;
10475
10476         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10477
10478         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10479
10480         if (appData.highlightLastMove) {
10481             SetHighlights(fromX, fromY, toX, toY);
10482         }
10483     }
10484     DisplayMove(currentMove);
10485     SendMoveToProgram(currentMove++, &first);
10486     DisplayBothClocks();
10487     DrawPosition(FALSE, boards[currentMove]);
10488     // [HGM] PV info: always display, routine tests if empty
10489     DisplayComment(currentMove - 1, commentList[currentMove]);
10490     return TRUE;
10491 }
10492
10493
10494 int
10495 LoadGameOneMove(readAhead)
10496      ChessMove readAhead;
10497 {
10498     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10499     char promoChar = NULLCHAR;
10500     ChessMove moveType;
10501     char move[MSG_SIZ];
10502     char *p, *q;
10503
10504     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10505         gameMode != AnalyzeMode && gameMode != Training) {
10506         gameFileFP = NULL;
10507         return FALSE;
10508     }
10509
10510     yyboardindex = forwardMostMove;
10511     if (readAhead != EndOfFile) {
10512       moveType = readAhead;
10513     } else {
10514       if (gameFileFP == NULL)
10515           return FALSE;
10516       moveType = (ChessMove) Myylex();
10517     }
10518
10519     done = FALSE;
10520     switch (moveType) {
10521       case Comment:
10522         if (appData.debugMode)
10523           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10524         p = yy_text;
10525
10526         /* append the comment but don't display it */
10527         AppendComment(currentMove, p, FALSE);
10528         return TRUE;
10529
10530       case WhiteCapturesEnPassant:
10531       case BlackCapturesEnPassant:
10532       case WhitePromotion:
10533       case BlackPromotion:
10534       case WhiteNonPromotion:
10535       case BlackNonPromotion:
10536       case NormalMove:
10537       case WhiteKingSideCastle:
10538       case WhiteQueenSideCastle:
10539       case BlackKingSideCastle:
10540       case BlackQueenSideCastle:
10541       case WhiteKingSideCastleWild:
10542       case WhiteQueenSideCastleWild:
10543       case BlackKingSideCastleWild:
10544       case BlackQueenSideCastleWild:
10545       /* PUSH Fabien */
10546       case WhiteHSideCastleFR:
10547       case WhiteASideCastleFR:
10548       case BlackHSideCastleFR:
10549       case BlackASideCastleFR:
10550       /* POP Fabien */
10551         if (appData.debugMode)
10552           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10553         fromX = currentMoveString[0] - AAA;
10554         fromY = currentMoveString[1] - ONE;
10555         toX = currentMoveString[2] - AAA;
10556         toY = currentMoveString[3] - ONE;
10557         promoChar = currentMoveString[4];
10558         break;
10559
10560       case WhiteDrop:
10561       case BlackDrop:
10562         if (appData.debugMode)
10563           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10564         fromX = moveType == WhiteDrop ?
10565           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10566         (int) CharToPiece(ToLower(currentMoveString[0]));
10567         fromY = DROP_RANK;
10568         toX = currentMoveString[2] - AAA;
10569         toY = currentMoveString[3] - ONE;
10570         break;
10571
10572       case WhiteWins:
10573       case BlackWins:
10574       case GameIsDrawn:
10575       case GameUnfinished:
10576         if (appData.debugMode)
10577           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10578         p = strchr(yy_text, '{');
10579         if (p == NULL) p = strchr(yy_text, '(');
10580         if (p == NULL) {
10581             p = yy_text;
10582             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10583         } else {
10584             q = strchr(p, *p == '{' ? '}' : ')');
10585             if (q != NULL) *q = NULLCHAR;
10586             p++;
10587         }
10588         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10589         GameEnds(moveType, p, GE_FILE);
10590         done = TRUE;
10591         if (cmailMsgLoaded) {
10592             ClearHighlights();
10593             flipView = WhiteOnMove(currentMove);
10594             if (moveType == GameUnfinished) flipView = !flipView;
10595             if (appData.debugMode)
10596               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10597         }
10598         break;
10599
10600       case EndOfFile:
10601         if (appData.debugMode)
10602           fprintf(debugFP, "Parser hit end of file\n");
10603         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10604           case MT_NONE:
10605           case MT_CHECK:
10606             break;
10607           case MT_CHECKMATE:
10608           case MT_STAINMATE:
10609             if (WhiteOnMove(currentMove)) {
10610                 GameEnds(BlackWins, "Black mates", GE_FILE);
10611             } else {
10612                 GameEnds(WhiteWins, "White mates", GE_FILE);
10613             }
10614             break;
10615           case MT_STALEMATE:
10616             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10617             break;
10618         }
10619         done = TRUE;
10620         break;
10621
10622       case MoveNumberOne:
10623         if (lastLoadGameStart == GNUChessGame) {
10624             /* GNUChessGames have numbers, but they aren't move numbers */
10625             if (appData.debugMode)
10626               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10627                       yy_text, (int) moveType);
10628             return LoadGameOneMove(EndOfFile); /* tail recursion */
10629         }
10630         /* else fall thru */
10631
10632       case XBoardGame:
10633       case GNUChessGame:
10634       case PGNTag:
10635         /* Reached start of next game in file */
10636         if (appData.debugMode)
10637           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10638         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10639           case MT_NONE:
10640           case MT_CHECK:
10641             break;
10642           case MT_CHECKMATE:
10643           case MT_STAINMATE:
10644             if (WhiteOnMove(currentMove)) {
10645                 GameEnds(BlackWins, "Black mates", GE_FILE);
10646             } else {
10647                 GameEnds(WhiteWins, "White mates", GE_FILE);
10648             }
10649             break;
10650           case MT_STALEMATE:
10651             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10652             break;
10653         }
10654         done = TRUE;
10655         break;
10656
10657       case PositionDiagram:     /* should not happen; ignore */
10658       case ElapsedTime:         /* ignore */
10659       case NAG:                 /* ignore */
10660         if (appData.debugMode)
10661           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10662                   yy_text, (int) moveType);
10663         return LoadGameOneMove(EndOfFile); /* tail recursion */
10664
10665       case IllegalMove:
10666         if (appData.testLegality) {
10667             if (appData.debugMode)
10668               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10669             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10670                     (forwardMostMove / 2) + 1,
10671                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10672             DisplayError(move, 0);
10673             done = TRUE;
10674         } else {
10675             if (appData.debugMode)
10676               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10677                       yy_text, currentMoveString);
10678             fromX = currentMoveString[0] - AAA;
10679             fromY = currentMoveString[1] - ONE;
10680             toX = currentMoveString[2] - AAA;
10681             toY = currentMoveString[3] - ONE;
10682             promoChar = currentMoveString[4];
10683         }
10684         break;
10685
10686       case AmbiguousMove:
10687         if (appData.debugMode)
10688           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10689         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10690                 (forwardMostMove / 2) + 1,
10691                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10692         DisplayError(move, 0);
10693         done = TRUE;
10694         break;
10695
10696       default:
10697       case ImpossibleMove:
10698         if (appData.debugMode)
10699           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10700         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10701                 (forwardMostMove / 2) + 1,
10702                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10703         DisplayError(move, 0);
10704         done = TRUE;
10705         break;
10706     }
10707
10708     if (done) {
10709         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10710             DrawPosition(FALSE, boards[currentMove]);
10711             DisplayBothClocks();
10712             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10713               DisplayComment(currentMove - 1, commentList[currentMove]);
10714         }
10715         (void) StopLoadGameTimer();
10716         gameFileFP = NULL;
10717         cmailOldMove = forwardMostMove;
10718         return FALSE;
10719     } else {
10720         /* currentMoveString is set as a side-effect of yylex */
10721
10722         thinkOutput[0] = NULLCHAR;
10723         MakeMove(fromX, fromY, toX, toY, promoChar);
10724         currentMove = forwardMostMove;
10725         return TRUE;
10726     }
10727 }
10728
10729 /* Load the nth game from the given file */
10730 int
10731 LoadGameFromFile(filename, n, title, useList)
10732      char *filename;
10733      int n;
10734      char *title;
10735      /*Boolean*/ int useList;
10736 {
10737     FILE *f;
10738     char buf[MSG_SIZ];
10739
10740     if (strcmp(filename, "-") == 0) {
10741         f = stdin;
10742         title = "stdin";
10743     } else {
10744         f = fopen(filename, "rb");
10745         if (f == NULL) {
10746           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10747             DisplayError(buf, errno);
10748             return FALSE;
10749         }
10750     }
10751     if (fseek(f, 0, 0) == -1) {
10752         /* f is not seekable; probably a pipe */
10753         useList = FALSE;
10754     }
10755     if (useList && n == 0) {
10756         int error = GameListBuild(f);
10757         if (error) {
10758             DisplayError(_("Cannot build game list"), error);
10759         } else if (!ListEmpty(&gameList) &&
10760                    ((ListGame *) gameList.tailPred)->number > 1) {
10761             GameListPopUp(f, title);
10762             return TRUE;
10763         }
10764         GameListDestroy();
10765         n = 1;
10766     }
10767     if (n == 0) n = 1;
10768     return LoadGame(f, n, title, FALSE);
10769 }
10770
10771
10772 void
10773 MakeRegisteredMove()
10774 {
10775     int fromX, fromY, toX, toY;
10776     char promoChar;
10777     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10778         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10779           case CMAIL_MOVE:
10780           case CMAIL_DRAW:
10781             if (appData.debugMode)
10782               fprintf(debugFP, "Restoring %s for game %d\n",
10783                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10784
10785             thinkOutput[0] = NULLCHAR;
10786             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10787             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10788             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10789             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10790             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10791             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10792             MakeMove(fromX, fromY, toX, toY, promoChar);
10793             ShowMove(fromX, fromY, toX, toY);
10794
10795             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10796               case MT_NONE:
10797               case MT_CHECK:
10798                 break;
10799
10800               case MT_CHECKMATE:
10801               case MT_STAINMATE:
10802                 if (WhiteOnMove(currentMove)) {
10803                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10804                 } else {
10805                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10806                 }
10807                 break;
10808
10809               case MT_STALEMATE:
10810                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10811                 break;
10812             }
10813
10814             break;
10815
10816           case CMAIL_RESIGN:
10817             if (WhiteOnMove(currentMove)) {
10818                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10819             } else {
10820                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10821             }
10822             break;
10823
10824           case CMAIL_ACCEPT:
10825             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10826             break;
10827
10828           default:
10829             break;
10830         }
10831     }
10832
10833     return;
10834 }
10835
10836 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10837 int
10838 CmailLoadGame(f, gameNumber, title, useList)
10839      FILE *f;
10840      int gameNumber;
10841      char *title;
10842      int useList;
10843 {
10844     int retVal;
10845
10846     if (gameNumber > nCmailGames) {
10847         DisplayError(_("No more games in this message"), 0);
10848         return FALSE;
10849     }
10850     if (f == lastLoadGameFP) {
10851         int offset = gameNumber - lastLoadGameNumber;
10852         if (offset == 0) {
10853             cmailMsg[0] = NULLCHAR;
10854             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10855                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10856                 nCmailMovesRegistered--;
10857             }
10858             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10859             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10860                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10861             }
10862         } else {
10863             if (! RegisterMove()) return FALSE;
10864         }
10865     }
10866
10867     retVal = LoadGame(f, gameNumber, title, useList);
10868
10869     /* Make move registered during previous look at this game, if any */
10870     MakeRegisteredMove();
10871
10872     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10873         commentList[currentMove]
10874           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10875         DisplayComment(currentMove - 1, commentList[currentMove]);
10876     }
10877
10878     return retVal;
10879 }
10880
10881 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10882 int
10883 ReloadGame(offset)
10884      int offset;
10885 {
10886     int gameNumber = lastLoadGameNumber + offset;
10887     if (lastLoadGameFP == NULL) {
10888         DisplayError(_("No game has been loaded yet"), 0);
10889         return FALSE;
10890     }
10891     if (gameNumber <= 0) {
10892         DisplayError(_("Can't back up any further"), 0);
10893         return FALSE;
10894     }
10895     if (cmailMsgLoaded) {
10896         return CmailLoadGame(lastLoadGameFP, gameNumber,
10897                              lastLoadGameTitle, lastLoadGameUseList);
10898     } else {
10899         return LoadGame(lastLoadGameFP, gameNumber,
10900                         lastLoadGameTitle, lastLoadGameUseList);
10901     }
10902 }
10903
10904
10905
10906 /* Load the nth game from open file f */
10907 int
10908 LoadGame(f, gameNumber, title, useList)
10909      FILE *f;
10910      int gameNumber;
10911      char *title;
10912      int useList;
10913 {
10914     ChessMove cm;
10915     char buf[MSG_SIZ];
10916     int gn = gameNumber;
10917     ListGame *lg = NULL;
10918     int numPGNTags = 0;
10919     int err;
10920     GameMode oldGameMode;
10921     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10922
10923     if (appData.debugMode)
10924         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10925
10926     if (gameMode == Training )
10927         SetTrainingModeOff();
10928
10929     oldGameMode = gameMode;
10930     if (gameMode != BeginningOfGame) {
10931       Reset(FALSE, TRUE);
10932     }
10933
10934     gameFileFP = f;
10935     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10936         fclose(lastLoadGameFP);
10937     }
10938
10939     if (useList) {
10940         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10941
10942         if (lg) {
10943             fseek(f, lg->offset, 0);
10944             GameListHighlight(gameNumber);
10945             gn = 1;
10946         }
10947         else {
10948             DisplayError(_("Game number out of range"), 0);
10949             return FALSE;
10950         }
10951     } else {
10952         GameListDestroy();
10953         if (fseek(f, 0, 0) == -1) {
10954             if (f == lastLoadGameFP ?
10955                 gameNumber == lastLoadGameNumber + 1 :
10956                 gameNumber == 1) {
10957                 gn = 1;
10958             } else {
10959                 DisplayError(_("Can't seek on game file"), 0);
10960                 return FALSE;
10961             }
10962         }
10963     }
10964     lastLoadGameFP = f;
10965     lastLoadGameNumber = gameNumber;
10966     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10967     lastLoadGameUseList = useList;
10968
10969     yynewfile(f);
10970
10971     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10972       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10973                 lg->gameInfo.black);
10974             DisplayTitle(buf);
10975     } else if (*title != NULLCHAR) {
10976         if (gameNumber > 1) {
10977           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10978             DisplayTitle(buf);
10979         } else {
10980             DisplayTitle(title);
10981         }
10982     }
10983
10984     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10985         gameMode = PlayFromGameFile;
10986         ModeHighlight();
10987     }
10988
10989     currentMove = forwardMostMove = backwardMostMove = 0;
10990     CopyBoard(boards[0], initialPosition);
10991     StopClocks();
10992
10993     /*
10994      * Skip the first gn-1 games in the file.
10995      * Also skip over anything that precedes an identifiable
10996      * start of game marker, to avoid being confused by
10997      * garbage at the start of the file.  Currently
10998      * recognized start of game markers are the move number "1",
10999      * the pattern "gnuchess .* game", the pattern
11000      * "^[#;%] [^ ]* game file", and a PGN tag block.
11001      * A game that starts with one of the latter two patterns
11002      * will also have a move number 1, possibly
11003      * following a position diagram.
11004      * 5-4-02: Let's try being more lenient and allowing a game to
11005      * start with an unnumbered move.  Does that break anything?
11006      */
11007     cm = lastLoadGameStart = EndOfFile;
11008     while (gn > 0) {
11009         yyboardindex = forwardMostMove;
11010         cm = (ChessMove) Myylex();
11011         switch (cm) {
11012           case EndOfFile:
11013             if (cmailMsgLoaded) {
11014                 nCmailGames = CMAIL_MAX_GAMES - gn;
11015             } else {
11016                 Reset(TRUE, TRUE);
11017                 DisplayError(_("Game not found in file"), 0);
11018             }
11019             return FALSE;
11020
11021           case GNUChessGame:
11022           case XBoardGame:
11023             gn--;
11024             lastLoadGameStart = cm;
11025             break;
11026
11027           case MoveNumberOne:
11028             switch (lastLoadGameStart) {
11029               case GNUChessGame:
11030               case XBoardGame:
11031               case PGNTag:
11032                 break;
11033               case MoveNumberOne:
11034               case EndOfFile:
11035                 gn--;           /* count this game */
11036                 lastLoadGameStart = cm;
11037                 break;
11038               default:
11039                 /* impossible */
11040                 break;
11041             }
11042             break;
11043
11044           case PGNTag:
11045             switch (lastLoadGameStart) {
11046               case GNUChessGame:
11047               case PGNTag:
11048               case MoveNumberOne:
11049               case EndOfFile:
11050                 gn--;           /* count this game */
11051                 lastLoadGameStart = cm;
11052                 break;
11053               case XBoardGame:
11054                 lastLoadGameStart = cm; /* game counted already */
11055                 break;
11056               default:
11057                 /* impossible */
11058                 break;
11059             }
11060             if (gn > 0) {
11061                 do {
11062                     yyboardindex = forwardMostMove;
11063                     cm = (ChessMove) Myylex();
11064                 } while (cm == PGNTag || cm == Comment);
11065             }
11066             break;
11067
11068           case WhiteWins:
11069           case BlackWins:
11070           case GameIsDrawn:
11071             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11072                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11073                     != CMAIL_OLD_RESULT) {
11074                     nCmailResults ++ ;
11075                     cmailResult[  CMAIL_MAX_GAMES
11076                                 - gn - 1] = CMAIL_OLD_RESULT;
11077                 }
11078             }
11079             break;
11080
11081           case NormalMove:
11082             /* Only a NormalMove can be at the start of a game
11083              * without a position diagram. */
11084             if (lastLoadGameStart == EndOfFile ) {
11085               gn--;
11086               lastLoadGameStart = MoveNumberOne;
11087             }
11088             break;
11089
11090           default:
11091             break;
11092         }
11093     }
11094
11095     if (appData.debugMode)
11096       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11097
11098     if (cm == XBoardGame) {
11099         /* Skip any header junk before position diagram and/or move 1 */
11100         for (;;) {
11101             yyboardindex = forwardMostMove;
11102             cm = (ChessMove) Myylex();
11103
11104             if (cm == EndOfFile ||
11105                 cm == GNUChessGame || cm == XBoardGame) {
11106                 /* Empty game; pretend end-of-file and handle later */
11107                 cm = EndOfFile;
11108                 break;
11109             }
11110
11111             if (cm == MoveNumberOne || cm == PositionDiagram ||
11112                 cm == PGNTag || cm == Comment)
11113               break;
11114         }
11115     } else if (cm == GNUChessGame) {
11116         if (gameInfo.event != NULL) {
11117             free(gameInfo.event);
11118         }
11119         gameInfo.event = StrSave(yy_text);
11120     }
11121
11122     startedFromSetupPosition = FALSE;
11123     while (cm == PGNTag) {
11124         if (appData.debugMode)
11125           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11126         err = ParsePGNTag(yy_text, &gameInfo);
11127         if (!err) numPGNTags++;
11128
11129         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11130         if(gameInfo.variant != oldVariant) {
11131             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11132             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11133             InitPosition(TRUE);
11134             oldVariant = gameInfo.variant;
11135             if (appData.debugMode)
11136               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11137         }
11138
11139
11140         if (gameInfo.fen != NULL) {
11141           Board initial_position;
11142           startedFromSetupPosition = TRUE;
11143           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11144             Reset(TRUE, TRUE);
11145             DisplayError(_("Bad FEN position in file"), 0);
11146             return FALSE;
11147           }
11148           CopyBoard(boards[0], initial_position);
11149           if (blackPlaysFirst) {
11150             currentMove = forwardMostMove = backwardMostMove = 1;
11151             CopyBoard(boards[1], initial_position);
11152             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11153             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11154             timeRemaining[0][1] = whiteTimeRemaining;
11155             timeRemaining[1][1] = blackTimeRemaining;
11156             if (commentList[0] != NULL) {
11157               commentList[1] = commentList[0];
11158               commentList[0] = NULL;
11159             }
11160           } else {
11161             currentMove = forwardMostMove = backwardMostMove = 0;
11162           }
11163           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11164           {   int i;
11165               initialRulePlies = FENrulePlies;
11166               for( i=0; i< nrCastlingRights; i++ )
11167                   initialRights[i] = initial_position[CASTLING][i];
11168           }
11169           yyboardindex = forwardMostMove;
11170           free(gameInfo.fen);
11171           gameInfo.fen = NULL;
11172         }
11173
11174         yyboardindex = forwardMostMove;
11175         cm = (ChessMove) Myylex();
11176
11177         /* Handle comments interspersed among the tags */
11178         while (cm == Comment) {
11179             char *p;
11180             if (appData.debugMode)
11181               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11182             p = yy_text;
11183             AppendComment(currentMove, p, FALSE);
11184             yyboardindex = forwardMostMove;
11185             cm = (ChessMove) Myylex();
11186         }
11187     }
11188
11189     /* don't rely on existence of Event tag since if game was
11190      * pasted from clipboard the Event tag may not exist
11191      */
11192     if (numPGNTags > 0){
11193         char *tags;
11194         if (gameInfo.variant == VariantNormal) {
11195           VariantClass v = StringToVariant(gameInfo.event);
11196           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11197           if(v < VariantShogi) gameInfo.variant = v;
11198         }
11199         if (!matchMode) {
11200           if( appData.autoDisplayTags ) {
11201             tags = PGNTags(&gameInfo);
11202             TagsPopUp(tags, CmailMsg());
11203             free(tags);
11204           }
11205         }
11206     } else {
11207         /* Make something up, but don't display it now */
11208         SetGameInfo();
11209         TagsPopDown();
11210     }
11211
11212     if (cm == PositionDiagram) {
11213         int i, j;
11214         char *p;
11215         Board initial_position;
11216
11217         if (appData.debugMode)
11218           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11219
11220         if (!startedFromSetupPosition) {
11221             p = yy_text;
11222             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11223               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11224                 switch (*p) {
11225                   case '{':
11226                   case '[':
11227                   case '-':
11228                   case ' ':
11229                   case '\t':
11230                   case '\n':
11231                   case '\r':
11232                     break;
11233                   default:
11234                     initial_position[i][j++] = CharToPiece(*p);
11235                     break;
11236                 }
11237             while (*p == ' ' || *p == '\t' ||
11238                    *p == '\n' || *p == '\r') p++;
11239
11240             if (strncmp(p, "black", strlen("black"))==0)
11241               blackPlaysFirst = TRUE;
11242             else
11243               blackPlaysFirst = FALSE;
11244             startedFromSetupPosition = TRUE;
11245
11246             CopyBoard(boards[0], initial_position);
11247             if (blackPlaysFirst) {
11248                 currentMove = forwardMostMove = backwardMostMove = 1;
11249                 CopyBoard(boards[1], initial_position);
11250                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11251                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11252                 timeRemaining[0][1] = whiteTimeRemaining;
11253                 timeRemaining[1][1] = blackTimeRemaining;
11254                 if (commentList[0] != NULL) {
11255                     commentList[1] = commentList[0];
11256                     commentList[0] = NULL;
11257                 }
11258             } else {
11259                 currentMove = forwardMostMove = backwardMostMove = 0;
11260             }
11261         }
11262         yyboardindex = forwardMostMove;
11263         cm = (ChessMove) Myylex();
11264     }
11265
11266     if (first.pr == NoProc) {
11267         StartChessProgram(&first);
11268     }
11269     InitChessProgram(&first, FALSE);
11270     SendToProgram("force\n", &first);
11271     if (startedFromSetupPosition) {
11272         SendBoard(&first, forwardMostMove);
11273     if (appData.debugMode) {
11274         fprintf(debugFP, "Load Game\n");
11275     }
11276         DisplayBothClocks();
11277     }
11278
11279     /* [HGM] server: flag to write setup moves in broadcast file as one */
11280     loadFlag = appData.suppressLoadMoves;
11281
11282     while (cm == Comment) {
11283         char *p;
11284         if (appData.debugMode)
11285           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11286         p = yy_text;
11287         AppendComment(currentMove, p, FALSE);
11288         yyboardindex = forwardMostMove;
11289         cm = (ChessMove) Myylex();
11290     }
11291
11292     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11293         cm == WhiteWins || cm == BlackWins ||
11294         cm == GameIsDrawn || cm == GameUnfinished) {
11295         DisplayMessage("", _("No moves in game"));
11296         if (cmailMsgLoaded) {
11297             if (appData.debugMode)
11298               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11299             ClearHighlights();
11300             flipView = FALSE;
11301         }
11302         DrawPosition(FALSE, boards[currentMove]);
11303         DisplayBothClocks();
11304         gameMode = EditGame;
11305         ModeHighlight();
11306         gameFileFP = NULL;
11307         cmailOldMove = 0;
11308         return TRUE;
11309     }
11310
11311     // [HGM] PV info: routine tests if comment empty
11312     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11313         DisplayComment(currentMove - 1, commentList[currentMove]);
11314     }
11315     if (!matchMode && appData.timeDelay != 0)
11316       DrawPosition(FALSE, boards[currentMove]);
11317
11318     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11319       programStats.ok_to_send = 1;
11320     }
11321
11322     /* if the first token after the PGN tags is a move
11323      * and not move number 1, retrieve it from the parser
11324      */
11325     if (cm != MoveNumberOne)
11326         LoadGameOneMove(cm);
11327
11328     /* load the remaining moves from the file */
11329     while (LoadGameOneMove(EndOfFile)) {
11330       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11331       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11332     }
11333
11334     /* rewind to the start of the game */
11335     currentMove = backwardMostMove;
11336
11337     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11338
11339     if (oldGameMode == AnalyzeFile ||
11340         oldGameMode == AnalyzeMode) {
11341       AnalyzeFileEvent();
11342     }
11343
11344     if (matchMode || appData.timeDelay == 0) {
11345       ToEndEvent();
11346       gameMode = EditGame;
11347       ModeHighlight();
11348     } else if (appData.timeDelay > 0) {
11349       AutoPlayGameLoop();
11350     }
11351
11352     if (appData.debugMode)
11353         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11354
11355     loadFlag = 0; /* [HGM] true game starts */
11356     return TRUE;
11357 }
11358
11359 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11360 int
11361 ReloadPosition(offset)
11362      int offset;
11363 {
11364     int positionNumber = lastLoadPositionNumber + offset;
11365     if (lastLoadPositionFP == NULL) {
11366         DisplayError(_("No position has been loaded yet"), 0);
11367         return FALSE;
11368     }
11369     if (positionNumber <= 0) {
11370         DisplayError(_("Can't back up any further"), 0);
11371         return FALSE;
11372     }
11373     return LoadPosition(lastLoadPositionFP, positionNumber,
11374                         lastLoadPositionTitle);
11375 }
11376
11377 /* Load the nth position from the given file */
11378 int
11379 LoadPositionFromFile(filename, n, title)
11380      char *filename;
11381      int n;
11382      char *title;
11383 {
11384     FILE *f;
11385     char buf[MSG_SIZ];
11386
11387     if (strcmp(filename, "-") == 0) {
11388         return LoadPosition(stdin, n, "stdin");
11389     } else {
11390         f = fopen(filename, "rb");
11391         if (f == NULL) {
11392             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11393             DisplayError(buf, errno);
11394             return FALSE;
11395         } else {
11396             return LoadPosition(f, n, title);
11397         }
11398     }
11399 }
11400
11401 /* Load the nth position from the given open file, and close it */
11402 int
11403 LoadPosition(f, positionNumber, title)
11404      FILE *f;
11405      int positionNumber;
11406      char *title;
11407 {
11408     char *p, line[MSG_SIZ];
11409     Board initial_position;
11410     int i, j, fenMode, pn;
11411
11412     if (gameMode == Training )
11413         SetTrainingModeOff();
11414
11415     if (gameMode != BeginningOfGame) {
11416         Reset(FALSE, TRUE);
11417     }
11418     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11419         fclose(lastLoadPositionFP);
11420     }
11421     if (positionNumber == 0) positionNumber = 1;
11422     lastLoadPositionFP = f;
11423     lastLoadPositionNumber = positionNumber;
11424     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11425     if (first.pr == NoProc) {
11426       StartChessProgram(&first);
11427       InitChessProgram(&first, FALSE);
11428     }
11429     pn = positionNumber;
11430     if (positionNumber < 0) {
11431         /* Negative position number means to seek to that byte offset */
11432         if (fseek(f, -positionNumber, 0) == -1) {
11433             DisplayError(_("Can't seek on position file"), 0);
11434             return FALSE;
11435         };
11436         pn = 1;
11437     } else {
11438         if (fseek(f, 0, 0) == -1) {
11439             if (f == lastLoadPositionFP ?
11440                 positionNumber == lastLoadPositionNumber + 1 :
11441                 positionNumber == 1) {
11442                 pn = 1;
11443             } else {
11444                 DisplayError(_("Can't seek on position file"), 0);
11445                 return FALSE;
11446             }
11447         }
11448     }
11449     /* See if this file is FEN or old-style xboard */
11450     if (fgets(line, MSG_SIZ, f) == NULL) {
11451         DisplayError(_("Position not found in file"), 0);
11452         return FALSE;
11453     }
11454     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11455     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11456
11457     if (pn >= 2) {
11458         if (fenMode || line[0] == '#') pn--;
11459         while (pn > 0) {
11460             /* skip positions before number pn */
11461             if (fgets(line, MSG_SIZ, f) == NULL) {
11462                 Reset(TRUE, TRUE);
11463                 DisplayError(_("Position not found in file"), 0);
11464                 return FALSE;
11465             }
11466             if (fenMode || line[0] == '#') pn--;
11467         }
11468     }
11469
11470     if (fenMode) {
11471         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11472             DisplayError(_("Bad FEN position in file"), 0);
11473             return FALSE;
11474         }
11475     } else {
11476         (void) fgets(line, MSG_SIZ, f);
11477         (void) fgets(line, MSG_SIZ, f);
11478
11479         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11480             (void) fgets(line, MSG_SIZ, f);
11481             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11482                 if (*p == ' ')
11483                   continue;
11484                 initial_position[i][j++] = CharToPiece(*p);
11485             }
11486         }
11487
11488         blackPlaysFirst = FALSE;
11489         if (!feof(f)) {
11490             (void) fgets(line, MSG_SIZ, f);
11491             if (strncmp(line, "black", strlen("black"))==0)
11492               blackPlaysFirst = TRUE;
11493         }
11494     }
11495     startedFromSetupPosition = TRUE;
11496
11497     SendToProgram("force\n", &first);
11498     CopyBoard(boards[0], initial_position);
11499     if (blackPlaysFirst) {
11500         currentMove = forwardMostMove = backwardMostMove = 1;
11501         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11502         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11503         CopyBoard(boards[1], initial_position);
11504         DisplayMessage("", _("Black to play"));
11505     } else {
11506         currentMove = forwardMostMove = backwardMostMove = 0;
11507         DisplayMessage("", _("White to play"));
11508     }
11509     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11510     SendBoard(&first, forwardMostMove);
11511     if (appData.debugMode) {
11512 int i, j;
11513   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11514   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11515         fprintf(debugFP, "Load Position\n");
11516     }
11517
11518     if (positionNumber > 1) {
11519       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11520         DisplayTitle(line);
11521     } else {
11522         DisplayTitle(title);
11523     }
11524     gameMode = EditGame;
11525     ModeHighlight();
11526     ResetClocks();
11527     timeRemaining[0][1] = whiteTimeRemaining;
11528     timeRemaining[1][1] = blackTimeRemaining;
11529     DrawPosition(FALSE, boards[currentMove]);
11530
11531     return TRUE;
11532 }
11533
11534
11535 void
11536 CopyPlayerNameIntoFileName(dest, src)
11537      char **dest, *src;
11538 {
11539     while (*src != NULLCHAR && *src != ',') {
11540         if (*src == ' ') {
11541             *(*dest)++ = '_';
11542             src++;
11543         } else {
11544             *(*dest)++ = *src++;
11545         }
11546     }
11547 }
11548
11549 char *DefaultFileName(ext)
11550      char *ext;
11551 {
11552     static char def[MSG_SIZ];
11553     char *p;
11554
11555     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11556         p = def;
11557         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11558         *p++ = '-';
11559         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11560         *p++ = '.';
11561         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11562     } else {
11563         def[0] = NULLCHAR;
11564     }
11565     return def;
11566 }
11567
11568 /* Save the current game to the given file */
11569 int
11570 SaveGameToFile(filename, append)
11571      char *filename;
11572      int append;
11573 {
11574     FILE *f;
11575     char buf[MSG_SIZ];
11576     int result;
11577
11578     if (strcmp(filename, "-") == 0) {
11579         return SaveGame(stdout, 0, NULL);
11580     } else {
11581         f = fopen(filename, append ? "a" : "w");
11582         if (f == NULL) {
11583             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11584             DisplayError(buf, errno);
11585             return FALSE;
11586         } else {
11587             safeStrCpy(buf, lastMsg, MSG_SIZ);
11588             DisplayMessage(_("Waiting for access to save file"), "");
11589             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11590             DisplayMessage(_("Saving game"), "");
11591             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11592             result = SaveGame(f, 0, NULL);
11593             DisplayMessage(buf, "");
11594             return result;
11595         }
11596     }
11597 }
11598
11599 char *
11600 SavePart(str)
11601      char *str;
11602 {
11603     static char buf[MSG_SIZ];
11604     char *p;
11605
11606     p = strchr(str, ' ');
11607     if (p == NULL) return str;
11608     strncpy(buf, str, p - str);
11609     buf[p - str] = NULLCHAR;
11610     return buf;
11611 }
11612
11613 #define PGN_MAX_LINE 75
11614
11615 #define PGN_SIDE_WHITE  0
11616 #define PGN_SIDE_BLACK  1
11617
11618 /* [AS] */
11619 static int FindFirstMoveOutOfBook( int side )
11620 {
11621     int result = -1;
11622
11623     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11624         int index = backwardMostMove;
11625         int has_book_hit = 0;
11626
11627         if( (index % 2) != side ) {
11628             index++;
11629         }
11630
11631         while( index < forwardMostMove ) {
11632             /* Check to see if engine is in book */
11633             int depth = pvInfoList[index].depth;
11634             int score = pvInfoList[index].score;
11635             int in_book = 0;
11636
11637             if( depth <= 2 ) {
11638                 in_book = 1;
11639             }
11640             else if( score == 0 && depth == 63 ) {
11641                 in_book = 1; /* Zappa */
11642             }
11643             else if( score == 2 && depth == 99 ) {
11644                 in_book = 1; /* Abrok */
11645             }
11646
11647             has_book_hit += in_book;
11648
11649             if( ! in_book ) {
11650                 result = index;
11651
11652                 break;
11653             }
11654
11655             index += 2;
11656         }
11657     }
11658
11659     return result;
11660 }
11661
11662 /* [AS] */
11663 void GetOutOfBookInfo( char * buf )
11664 {
11665     int oob[2];
11666     int i;
11667     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11668
11669     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11670     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11671
11672     *buf = '\0';
11673
11674     if( oob[0] >= 0 || oob[1] >= 0 ) {
11675         for( i=0; i<2; i++ ) {
11676             int idx = oob[i];
11677
11678             if( idx >= 0 ) {
11679                 if( i > 0 && oob[0] >= 0 ) {
11680                     strcat( buf, "   " );
11681                 }
11682
11683                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11684                 sprintf( buf+strlen(buf), "%s%.2f",
11685                     pvInfoList[idx].score >= 0 ? "+" : "",
11686                     pvInfoList[idx].score / 100.0 );
11687             }
11688         }
11689     }
11690 }
11691
11692 /* Save game in PGN style and close the file */
11693 int
11694 SaveGamePGN(f)
11695      FILE *f;
11696 {
11697     int i, offset, linelen, newblock;
11698     time_t tm;
11699 //    char *movetext;
11700     char numtext[32];
11701     int movelen, numlen, blank;
11702     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11703
11704     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11705
11706     tm = time((time_t *) NULL);
11707
11708     PrintPGNTags(f, &gameInfo);
11709
11710     if (backwardMostMove > 0 || startedFromSetupPosition) {
11711         char *fen = PositionToFEN(backwardMostMove, NULL);
11712         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11713         fprintf(f, "\n{--------------\n");
11714         PrintPosition(f, backwardMostMove);
11715         fprintf(f, "--------------}\n");
11716         free(fen);
11717     }
11718     else {
11719         /* [AS] Out of book annotation */
11720         if( appData.saveOutOfBookInfo ) {
11721             char buf[64];
11722
11723             GetOutOfBookInfo( buf );
11724
11725             if( buf[0] != '\0' ) {
11726                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11727             }
11728         }
11729
11730         fprintf(f, "\n");
11731     }
11732
11733     i = backwardMostMove;
11734     linelen = 0;
11735     newblock = TRUE;
11736
11737     while (i < forwardMostMove) {
11738         /* Print comments preceding this move */
11739         if (commentList[i] != NULL) {
11740             if (linelen > 0) fprintf(f, "\n");
11741             fprintf(f, "%s", commentList[i]);
11742             linelen = 0;
11743             newblock = TRUE;
11744         }
11745
11746         /* Format move number */
11747         if ((i % 2) == 0)
11748           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11749         else
11750           if (newblock)
11751             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11752           else
11753             numtext[0] = NULLCHAR;
11754
11755         numlen = strlen(numtext);
11756         newblock = FALSE;
11757
11758         /* Print move number */
11759         blank = linelen > 0 && numlen > 0;
11760         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11761             fprintf(f, "\n");
11762             linelen = 0;
11763             blank = 0;
11764         }
11765         if (blank) {
11766             fprintf(f, " ");
11767             linelen++;
11768         }
11769         fprintf(f, "%s", numtext);
11770         linelen += numlen;
11771
11772         /* Get move */
11773         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11774         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11775
11776         /* Print move */
11777         blank = linelen > 0 && movelen > 0;
11778         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11779             fprintf(f, "\n");
11780             linelen = 0;
11781             blank = 0;
11782         }
11783         if (blank) {
11784             fprintf(f, " ");
11785             linelen++;
11786         }
11787         fprintf(f, "%s", move_buffer);
11788         linelen += movelen;
11789
11790         /* [AS] Add PV info if present */
11791         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11792             /* [HGM] add time */
11793             char buf[MSG_SIZ]; int seconds;
11794
11795             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11796
11797             if( seconds <= 0)
11798               buf[0] = 0;
11799             else
11800               if( seconds < 30 )
11801                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11802               else
11803                 {
11804                   seconds = (seconds + 4)/10; // round to full seconds
11805                   if( seconds < 60 )
11806                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11807                   else
11808                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11809                 }
11810
11811             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11812                       pvInfoList[i].score >= 0 ? "+" : "",
11813                       pvInfoList[i].score / 100.0,
11814                       pvInfoList[i].depth,
11815                       buf );
11816
11817             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11818
11819             /* Print score/depth */
11820             blank = linelen > 0 && movelen > 0;
11821             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11822                 fprintf(f, "\n");
11823                 linelen = 0;
11824                 blank = 0;
11825             }
11826             if (blank) {
11827                 fprintf(f, " ");
11828                 linelen++;
11829             }
11830             fprintf(f, "%s", move_buffer);
11831             linelen += movelen;
11832         }
11833
11834         i++;
11835     }
11836
11837     /* Start a new line */
11838     if (linelen > 0) fprintf(f, "\n");
11839
11840     /* Print comments after last move */
11841     if (commentList[i] != NULL) {
11842         fprintf(f, "%s\n", commentList[i]);
11843     }
11844
11845     /* Print result */
11846     if (gameInfo.resultDetails != NULL &&
11847         gameInfo.resultDetails[0] != NULLCHAR) {
11848         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11849                 PGNResult(gameInfo.result));
11850     } else {
11851         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11852     }
11853
11854     fclose(f);
11855     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11856     return TRUE;
11857 }
11858
11859 /* Save game in old style and close the file */
11860 int
11861 SaveGameOldStyle(f)
11862      FILE *f;
11863 {
11864     int i, offset;
11865     time_t tm;
11866
11867     tm = time((time_t *) NULL);
11868
11869     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11870     PrintOpponents(f);
11871
11872     if (backwardMostMove > 0 || startedFromSetupPosition) {
11873         fprintf(f, "\n[--------------\n");
11874         PrintPosition(f, backwardMostMove);
11875         fprintf(f, "--------------]\n");
11876     } else {
11877         fprintf(f, "\n");
11878     }
11879
11880     i = backwardMostMove;
11881     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11882
11883     while (i < forwardMostMove) {
11884         if (commentList[i] != NULL) {
11885             fprintf(f, "[%s]\n", commentList[i]);
11886         }
11887
11888         if ((i % 2) == 1) {
11889             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11890             i++;
11891         } else {
11892             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11893             i++;
11894             if (commentList[i] != NULL) {
11895                 fprintf(f, "\n");
11896                 continue;
11897             }
11898             if (i >= forwardMostMove) {
11899                 fprintf(f, "\n");
11900                 break;
11901             }
11902             fprintf(f, "%s\n", parseList[i]);
11903             i++;
11904         }
11905     }
11906
11907     if (commentList[i] != NULL) {
11908         fprintf(f, "[%s]\n", commentList[i]);
11909     }
11910
11911     /* This isn't really the old style, but it's close enough */
11912     if (gameInfo.resultDetails != NULL &&
11913         gameInfo.resultDetails[0] != NULLCHAR) {
11914         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11915                 gameInfo.resultDetails);
11916     } else {
11917         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11918     }
11919
11920     fclose(f);
11921     return TRUE;
11922 }
11923
11924 /* Save the current game to open file f and close the file */
11925 int
11926 SaveGame(f, dummy, dummy2)
11927      FILE *f;
11928      int dummy;
11929      char *dummy2;
11930 {
11931     if (gameMode == EditPosition) EditPositionDone(TRUE);
11932     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11933     if (appData.oldSaveStyle)
11934       return SaveGameOldStyle(f);
11935     else
11936       return SaveGamePGN(f);
11937 }
11938
11939 /* Save the current position to the given file */
11940 int
11941 SavePositionToFile(filename)
11942      char *filename;
11943 {
11944     FILE *f;
11945     char buf[MSG_SIZ];
11946
11947     if (strcmp(filename, "-") == 0) {
11948         return SavePosition(stdout, 0, NULL);
11949     } else {
11950         f = fopen(filename, "a");
11951         if (f == NULL) {
11952             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11953             DisplayError(buf, errno);
11954             return FALSE;
11955         } else {
11956             safeStrCpy(buf, lastMsg, MSG_SIZ);
11957             DisplayMessage(_("Waiting for access to save file"), "");
11958             flock(fileno(f), LOCK_EX); // [HGM] lock
11959             DisplayMessage(_("Saving position"), "");
11960             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11961             SavePosition(f, 0, NULL);
11962             DisplayMessage(buf, "");
11963             return TRUE;
11964         }
11965     }
11966 }
11967
11968 /* Save the current position to the given open file and close the file */
11969 int
11970 SavePosition(f, dummy, dummy2)
11971      FILE *f;
11972      int dummy;
11973      char *dummy2;
11974 {
11975     time_t tm;
11976     char *fen;
11977
11978     if (gameMode == EditPosition) EditPositionDone(TRUE);
11979     if (appData.oldSaveStyle) {
11980         tm = time((time_t *) NULL);
11981
11982         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11983         PrintOpponents(f);
11984         fprintf(f, "[--------------\n");
11985         PrintPosition(f, currentMove);
11986         fprintf(f, "--------------]\n");
11987     } else {
11988         fen = PositionToFEN(currentMove, NULL);
11989         fprintf(f, "%s\n", fen);
11990         free(fen);
11991     }
11992     fclose(f);
11993     return TRUE;
11994 }
11995
11996 void
11997 ReloadCmailMsgEvent(unregister)
11998      int unregister;
11999 {
12000 #if !WIN32
12001     static char *inFilename = NULL;
12002     static char *outFilename;
12003     int i;
12004     struct stat inbuf, outbuf;
12005     int status;
12006
12007     /* Any registered moves are unregistered if unregister is set, */
12008     /* i.e. invoked by the signal handler */
12009     if (unregister) {
12010         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12011             cmailMoveRegistered[i] = FALSE;
12012             if (cmailCommentList[i] != NULL) {
12013                 free(cmailCommentList[i]);
12014                 cmailCommentList[i] = NULL;
12015             }
12016         }
12017         nCmailMovesRegistered = 0;
12018     }
12019
12020     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12021         cmailResult[i] = CMAIL_NOT_RESULT;
12022     }
12023     nCmailResults = 0;
12024
12025     if (inFilename == NULL) {
12026         /* Because the filenames are static they only get malloced once  */
12027         /* and they never get freed                                      */
12028         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12029         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12030
12031         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12032         sprintf(outFilename, "%s.out", appData.cmailGameName);
12033     }
12034
12035     status = stat(outFilename, &outbuf);
12036     if (status < 0) {
12037         cmailMailedMove = FALSE;
12038     } else {
12039         status = stat(inFilename, &inbuf);
12040         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12041     }
12042
12043     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12044        counts the games, notes how each one terminated, etc.
12045
12046        It would be nice to remove this kludge and instead gather all
12047        the information while building the game list.  (And to keep it
12048        in the game list nodes instead of having a bunch of fixed-size
12049        parallel arrays.)  Note this will require getting each game's
12050        termination from the PGN tags, as the game list builder does
12051        not process the game moves.  --mann
12052        */
12053     cmailMsgLoaded = TRUE;
12054     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12055
12056     /* Load first game in the file or popup game menu */
12057     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12058
12059 #endif /* !WIN32 */
12060     return;
12061 }
12062
12063 int
12064 RegisterMove()
12065 {
12066     FILE *f;
12067     char string[MSG_SIZ];
12068
12069     if (   cmailMailedMove
12070         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12071         return TRUE;            /* Allow free viewing  */
12072     }
12073
12074     /* Unregister move to ensure that we don't leave RegisterMove        */
12075     /* with the move registered when the conditions for registering no   */
12076     /* longer hold                                                       */
12077     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12078         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12079         nCmailMovesRegistered --;
12080
12081         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12082           {
12083               free(cmailCommentList[lastLoadGameNumber - 1]);
12084               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12085           }
12086     }
12087
12088     if (cmailOldMove == -1) {
12089         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12090         return FALSE;
12091     }
12092
12093     if (currentMove > cmailOldMove + 1) {
12094         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12095         return FALSE;
12096     }
12097
12098     if (currentMove < cmailOldMove) {
12099         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12100         return FALSE;
12101     }
12102
12103     if (forwardMostMove > currentMove) {
12104         /* Silently truncate extra moves */
12105         TruncateGame();
12106     }
12107
12108     if (   (currentMove == cmailOldMove + 1)
12109         || (   (currentMove == cmailOldMove)
12110             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12111                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12112         if (gameInfo.result != GameUnfinished) {
12113             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12114         }
12115
12116         if (commentList[currentMove] != NULL) {
12117             cmailCommentList[lastLoadGameNumber - 1]
12118               = StrSave(commentList[currentMove]);
12119         }
12120         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12121
12122         if (appData.debugMode)
12123           fprintf(debugFP, "Saving %s for game %d\n",
12124                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12125
12126         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12127
12128         f = fopen(string, "w");
12129         if (appData.oldSaveStyle) {
12130             SaveGameOldStyle(f); /* also closes the file */
12131
12132             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12133             f = fopen(string, "w");
12134             SavePosition(f, 0, NULL); /* also closes the file */
12135         } else {
12136             fprintf(f, "{--------------\n");
12137             PrintPosition(f, currentMove);
12138             fprintf(f, "--------------}\n\n");
12139
12140             SaveGame(f, 0, NULL); /* also closes the file*/
12141         }
12142
12143         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12144         nCmailMovesRegistered ++;
12145     } else if (nCmailGames == 1) {
12146         DisplayError(_("You have not made a move yet"), 0);
12147         return FALSE;
12148     }
12149
12150     return TRUE;
12151 }
12152
12153 void
12154 MailMoveEvent()
12155 {
12156 #if !WIN32
12157     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12158     FILE *commandOutput;
12159     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12160     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12161     int nBuffers;
12162     int i;
12163     int archived;
12164     char *arcDir;
12165
12166     if (! cmailMsgLoaded) {
12167         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12168         return;
12169     }
12170
12171     if (nCmailGames == nCmailResults) {
12172         DisplayError(_("No unfinished games"), 0);
12173         return;
12174     }
12175
12176 #if CMAIL_PROHIBIT_REMAIL
12177     if (cmailMailedMove) {
12178       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);
12179         DisplayError(msg, 0);
12180         return;
12181     }
12182 #endif
12183
12184     if (! (cmailMailedMove || RegisterMove())) return;
12185
12186     if (   cmailMailedMove
12187         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12188       snprintf(string, MSG_SIZ, partCommandString,
12189                appData.debugMode ? " -v" : "", appData.cmailGameName);
12190         commandOutput = popen(string, "r");
12191
12192         if (commandOutput == NULL) {
12193             DisplayError(_("Failed to invoke cmail"), 0);
12194         } else {
12195             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12196                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12197             }
12198             if (nBuffers > 1) {
12199                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12200                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12201                 nBytes = MSG_SIZ - 1;
12202             } else {
12203                 (void) memcpy(msg, buffer, nBytes);
12204             }
12205             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12206
12207             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12208                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12209
12210                 archived = TRUE;
12211                 for (i = 0; i < nCmailGames; i ++) {
12212                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12213                         archived = FALSE;
12214                     }
12215                 }
12216                 if (   archived
12217                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12218                         != NULL)) {
12219                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12220                            arcDir,
12221                            appData.cmailGameName,
12222                            gameInfo.date);
12223                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12224                     cmailMsgLoaded = FALSE;
12225                 }
12226             }
12227
12228             DisplayInformation(msg);
12229             pclose(commandOutput);
12230         }
12231     } else {
12232         if ((*cmailMsg) != '\0') {
12233             DisplayInformation(cmailMsg);
12234         }
12235     }
12236
12237     return;
12238 #endif /* !WIN32 */
12239 }
12240
12241 char *
12242 CmailMsg()
12243 {
12244 #if WIN32
12245     return NULL;
12246 #else
12247     int  prependComma = 0;
12248     char number[5];
12249     char string[MSG_SIZ];       /* Space for game-list */
12250     int  i;
12251
12252     if (!cmailMsgLoaded) return "";
12253
12254     if (cmailMailedMove) {
12255       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12256     } else {
12257         /* Create a list of games left */
12258       snprintf(string, MSG_SIZ, "[");
12259         for (i = 0; i < nCmailGames; i ++) {
12260             if (! (   cmailMoveRegistered[i]
12261                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12262                 if (prependComma) {
12263                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12264                 } else {
12265                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12266                     prependComma = 1;
12267                 }
12268
12269                 strcat(string, number);
12270             }
12271         }
12272         strcat(string, "]");
12273
12274         if (nCmailMovesRegistered + nCmailResults == 0) {
12275             switch (nCmailGames) {
12276               case 1:
12277                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12278                 break;
12279
12280               case 2:
12281                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12282                 break;
12283
12284               default:
12285                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12286                          nCmailGames);
12287                 break;
12288             }
12289         } else {
12290             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12291               case 1:
12292                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12293                          string);
12294                 break;
12295
12296               case 0:
12297                 if (nCmailResults == nCmailGames) {
12298                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12299                 } else {
12300                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12301                 }
12302                 break;
12303
12304               default:
12305                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12306                          string);
12307             }
12308         }
12309     }
12310     return cmailMsg;
12311 #endif /* WIN32 */
12312 }
12313
12314 void
12315 ResetGameEvent()
12316 {
12317     if (gameMode == Training)
12318       SetTrainingModeOff();
12319
12320     Reset(TRUE, TRUE);
12321     cmailMsgLoaded = FALSE;
12322     if (appData.icsActive) {
12323       SendToICS(ics_prefix);
12324       SendToICS("refresh\n");
12325     }
12326 }
12327
12328 void
12329 ExitEvent(status)
12330      int status;
12331 {
12332     exiting++;
12333     if (exiting > 2) {
12334       /* Give up on clean exit */
12335       exit(status);
12336     }
12337     if (exiting > 1) {
12338       /* Keep trying for clean exit */
12339       return;
12340     }
12341
12342     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12343
12344     if (telnetISR != NULL) {
12345       RemoveInputSource(telnetISR);
12346     }
12347     if (icsPR != NoProc) {
12348       DestroyChildProcess(icsPR, TRUE);
12349     }
12350
12351     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12352     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12353
12354     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12355     /* make sure this other one finishes before killing it!                  */
12356     if(endingGame) { int count = 0;
12357         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12358         while(endingGame && count++ < 10) DoSleep(1);
12359         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12360     }
12361
12362     /* Kill off chess programs */
12363     if (first.pr != NoProc) {
12364         ExitAnalyzeMode();
12365
12366         DoSleep( appData.delayBeforeQuit );
12367         SendToProgram("quit\n", &first);
12368         DoSleep( appData.delayAfterQuit );
12369         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12370     }
12371     if (second.pr != NoProc) {
12372         DoSleep( appData.delayBeforeQuit );
12373         SendToProgram("quit\n", &second);
12374         DoSleep( appData.delayAfterQuit );
12375         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12376     }
12377     if (first.isr != NULL) {
12378         RemoveInputSource(first.isr);
12379     }
12380     if (second.isr != NULL) {
12381         RemoveInputSource(second.isr);
12382     }
12383
12384     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12385     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12386
12387     ShutDownFrontEnd();
12388     exit(status);
12389 }
12390
12391 void
12392 PauseEvent()
12393 {
12394     if (appData.debugMode)
12395         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12396     if (pausing) {
12397         pausing = FALSE;
12398         ModeHighlight();
12399         if (gameMode == MachinePlaysWhite ||
12400             gameMode == MachinePlaysBlack) {
12401             StartClocks();
12402         } else {
12403             DisplayBothClocks();
12404         }
12405         if (gameMode == PlayFromGameFile) {
12406             if (appData.timeDelay >= 0)
12407                 AutoPlayGameLoop();
12408         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12409             Reset(FALSE, TRUE);
12410             SendToICS(ics_prefix);
12411             SendToICS("refresh\n");
12412         } else if (currentMove < forwardMostMove) {
12413             ForwardInner(forwardMostMove);
12414         }
12415         pauseExamInvalid = FALSE;
12416     } else {
12417         switch (gameMode) {
12418           default:
12419             return;
12420           case IcsExamining:
12421             pauseExamForwardMostMove = forwardMostMove;
12422             pauseExamInvalid = FALSE;
12423             /* fall through */
12424           case IcsObserving:
12425           case IcsPlayingWhite:
12426           case IcsPlayingBlack:
12427             pausing = TRUE;
12428             ModeHighlight();
12429             return;
12430           case PlayFromGameFile:
12431             (void) StopLoadGameTimer();
12432             pausing = TRUE;
12433             ModeHighlight();
12434             break;
12435           case BeginningOfGame:
12436             if (appData.icsActive) return;
12437             /* else fall through */
12438           case MachinePlaysWhite:
12439           case MachinePlaysBlack:
12440           case TwoMachinesPlay:
12441             if (forwardMostMove == 0)
12442               return;           /* don't pause if no one has moved */
12443             if ((gameMode == MachinePlaysWhite &&
12444                  !WhiteOnMove(forwardMostMove)) ||
12445                 (gameMode == MachinePlaysBlack &&
12446                  WhiteOnMove(forwardMostMove))) {
12447                 StopClocks();
12448             }
12449             pausing = TRUE;
12450             ModeHighlight();
12451             break;
12452         }
12453     }
12454 }
12455
12456 void
12457 EditCommentEvent()
12458 {
12459     char title[MSG_SIZ];
12460
12461     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12462       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12463     } else {
12464       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12465                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12466                parseList[currentMove - 1]);
12467     }
12468
12469     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12470 }
12471
12472
12473 void
12474 EditTagsEvent()
12475 {
12476     char *tags = PGNTags(&gameInfo);
12477     bookUp = FALSE;
12478     EditTagsPopUp(tags, NULL);
12479     free(tags);
12480 }
12481
12482 void
12483 AnalyzeModeEvent()
12484 {
12485     if (appData.noChessProgram || gameMode == AnalyzeMode)
12486       return;
12487
12488     if (gameMode != AnalyzeFile) {
12489         if (!appData.icsEngineAnalyze) {
12490                EditGameEvent();
12491                if (gameMode != EditGame) return;
12492         }
12493         ResurrectChessProgram();
12494         SendToProgram("analyze\n", &first);
12495         first.analyzing = TRUE;
12496         /*first.maybeThinking = TRUE;*/
12497         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12498         EngineOutputPopUp();
12499     }
12500     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12501     pausing = FALSE;
12502     ModeHighlight();
12503     SetGameInfo();
12504
12505     StartAnalysisClock();
12506     GetTimeMark(&lastNodeCountTime);
12507     lastNodeCount = 0;
12508 }
12509
12510 void
12511 AnalyzeFileEvent()
12512 {
12513     if (appData.noChessProgram || gameMode == AnalyzeFile)
12514       return;
12515
12516     if (gameMode != AnalyzeMode) {
12517         EditGameEvent();
12518         if (gameMode != EditGame) return;
12519         ResurrectChessProgram();
12520         SendToProgram("analyze\n", &first);
12521         first.analyzing = TRUE;
12522         /*first.maybeThinking = TRUE;*/
12523         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12524         EngineOutputPopUp();
12525     }
12526     gameMode = AnalyzeFile;
12527     pausing = FALSE;
12528     ModeHighlight();
12529     SetGameInfo();
12530
12531     StartAnalysisClock();
12532     GetTimeMark(&lastNodeCountTime);
12533     lastNodeCount = 0;
12534 }
12535
12536 void
12537 MachineWhiteEvent()
12538 {
12539     char buf[MSG_SIZ];
12540     char *bookHit = NULL;
12541
12542     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12543       return;
12544
12545
12546     if (gameMode == PlayFromGameFile ||
12547         gameMode == TwoMachinesPlay  ||
12548         gameMode == Training         ||
12549         gameMode == AnalyzeMode      ||
12550         gameMode == EndOfGame)
12551         EditGameEvent();
12552
12553     if (gameMode == EditPosition)
12554         EditPositionDone(TRUE);
12555
12556     if (!WhiteOnMove(currentMove)) {
12557         DisplayError(_("It is not White's turn"), 0);
12558         return;
12559     }
12560
12561     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12562       ExitAnalyzeMode();
12563
12564     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12565         gameMode == AnalyzeFile)
12566         TruncateGame();
12567
12568     ResurrectChessProgram();    /* in case it isn't running */
12569     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12570         gameMode = MachinePlaysWhite;
12571         ResetClocks();
12572     } else
12573     gameMode = MachinePlaysWhite;
12574     pausing = FALSE;
12575     ModeHighlight();
12576     SetGameInfo();
12577     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12578     DisplayTitle(buf);
12579     if (first.sendName) {
12580       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12581       SendToProgram(buf, &first);
12582     }
12583     if (first.sendTime) {
12584       if (first.useColors) {
12585         SendToProgram("black\n", &first); /*gnu kludge*/
12586       }
12587       SendTimeRemaining(&first, TRUE);
12588     }
12589     if (first.useColors) {
12590       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12591     }
12592     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12593     SetMachineThinkingEnables();
12594     first.maybeThinking = TRUE;
12595     StartClocks();
12596     firstMove = FALSE;
12597
12598     if (appData.autoFlipView && !flipView) {
12599       flipView = !flipView;
12600       DrawPosition(FALSE, NULL);
12601       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12602     }
12603
12604     if(bookHit) { // [HGM] book: simulate book reply
12605         static char bookMove[MSG_SIZ]; // a bit generous?
12606
12607         programStats.nodes = programStats.depth = programStats.time =
12608         programStats.score = programStats.got_only_move = 0;
12609         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12610
12611         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12612         strcat(bookMove, bookHit);
12613         HandleMachineMove(bookMove, &first);
12614     }
12615 }
12616
12617 void
12618 MachineBlackEvent()
12619 {
12620   char buf[MSG_SIZ];
12621   char *bookHit = NULL;
12622
12623     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12624         return;
12625
12626
12627     if (gameMode == PlayFromGameFile ||
12628         gameMode == TwoMachinesPlay  ||
12629         gameMode == Training         ||
12630         gameMode == AnalyzeMode      ||
12631         gameMode == EndOfGame)
12632         EditGameEvent();
12633
12634     if (gameMode == EditPosition)
12635         EditPositionDone(TRUE);
12636
12637     if (WhiteOnMove(currentMove)) {
12638         DisplayError(_("It is not Black's turn"), 0);
12639         return;
12640     }
12641
12642     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12643       ExitAnalyzeMode();
12644
12645     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12646         gameMode == AnalyzeFile)
12647         TruncateGame();
12648
12649     ResurrectChessProgram();    /* in case it isn't running */
12650     gameMode = MachinePlaysBlack;
12651     pausing = FALSE;
12652     ModeHighlight();
12653     SetGameInfo();
12654     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12655     DisplayTitle(buf);
12656     if (first.sendName) {
12657       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12658       SendToProgram(buf, &first);
12659     }
12660     if (first.sendTime) {
12661       if (first.useColors) {
12662         SendToProgram("white\n", &first); /*gnu kludge*/
12663       }
12664       SendTimeRemaining(&first, FALSE);
12665     }
12666     if (first.useColors) {
12667       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12668     }
12669     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12670     SetMachineThinkingEnables();
12671     first.maybeThinking = TRUE;
12672     StartClocks();
12673
12674     if (appData.autoFlipView && flipView) {
12675       flipView = !flipView;
12676       DrawPosition(FALSE, NULL);
12677       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12678     }
12679     if(bookHit) { // [HGM] book: simulate book reply
12680         static char bookMove[MSG_SIZ]; // a bit generous?
12681
12682         programStats.nodes = programStats.depth = programStats.time =
12683         programStats.score = programStats.got_only_move = 0;
12684         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12685
12686         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12687         strcat(bookMove, bookHit);
12688         HandleMachineMove(bookMove, &first);
12689     }
12690 }
12691
12692
12693 void
12694 DisplayTwoMachinesTitle()
12695 {
12696     char buf[MSG_SIZ];
12697     if (appData.matchGames > 0) {
12698         if(appData.tourneyFile[0]) {
12699           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12700                    gameInfo.white, gameInfo.black,
12701                    nextGame+1, appData.matchGames+1,
12702                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12703         } else 
12704         if (first.twoMachinesColor[0] == 'w') {
12705           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12706                    gameInfo.white, gameInfo.black,
12707                    first.matchWins, second.matchWins,
12708                    matchGame - 1 - (first.matchWins + second.matchWins));
12709         } else {
12710           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12711                    gameInfo.white, gameInfo.black,
12712                    second.matchWins, first.matchWins,
12713                    matchGame - 1 - (first.matchWins + second.matchWins));
12714         }
12715     } else {
12716       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12717     }
12718     DisplayTitle(buf);
12719 }
12720
12721 void
12722 SettingsMenuIfReady()
12723 {
12724   if (second.lastPing != second.lastPong) {
12725     DisplayMessage("", _("Waiting for second chess program"));
12726     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12727     return;
12728   }
12729   ThawUI();
12730   DisplayMessage("", "");
12731   SettingsPopUp(&second);
12732 }
12733
12734 int
12735 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12736 {
12737     char buf[MSG_SIZ];
12738     if (cps->pr == NULL) {
12739         StartChessProgram(cps);
12740         if (cps->protocolVersion == 1) {
12741           retry();
12742         } else {
12743           /* kludge: allow timeout for initial "feature" command */
12744           FreezeUI();
12745           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12746           DisplayMessage("", buf);
12747           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12748         }
12749         return 1;
12750     }
12751     return 0;
12752 }
12753
12754 void
12755 TwoMachinesEvent P((void))
12756 {
12757     int i;
12758     char buf[MSG_SIZ];
12759     ChessProgramState *onmove;
12760     char *bookHit = NULL;
12761     static int stalling = 0;
12762     TimeMark now;
12763     long wait;
12764
12765     if (appData.noChessProgram) return;
12766
12767     switch (gameMode) {
12768       case TwoMachinesPlay:
12769         return;
12770       case MachinePlaysWhite:
12771       case MachinePlaysBlack:
12772         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12773             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12774             return;
12775         }
12776         /* fall through */
12777       case BeginningOfGame:
12778       case PlayFromGameFile:
12779       case EndOfGame:
12780         EditGameEvent();
12781         if (gameMode != EditGame) return;
12782         break;
12783       case EditPosition:
12784         EditPositionDone(TRUE);
12785         break;
12786       case AnalyzeMode:
12787       case AnalyzeFile:
12788         ExitAnalyzeMode();
12789         break;
12790       case EditGame:
12791       default:
12792         break;
12793     }
12794
12795 //    forwardMostMove = currentMove;
12796     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12797
12798     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12799
12800     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12801     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12802       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12803       return;
12804     }
12805     if(!stalling) {
12806       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12807       SendToProgram("force\n", &second);
12808       stalling = 1;
12809       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12810       return;
12811     }
12812     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12813     if(appData.matchPause>10000 || appData.matchPause<10)
12814                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12815     wait = SubtractTimeMarks(&now, &pauseStart);
12816     if(wait < appData.matchPause) {
12817         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12818         return;
12819     }
12820     stalling = 0;
12821     DisplayMessage("", "");
12822     if (startedFromSetupPosition) {
12823         SendBoard(&second, backwardMostMove);
12824     if (appData.debugMode) {
12825         fprintf(debugFP, "Two Machines\n");
12826     }
12827     }
12828     for (i = backwardMostMove; i < forwardMostMove; i++) {
12829         SendMoveToProgram(i, &second);
12830     }
12831
12832     gameMode = TwoMachinesPlay;
12833     pausing = FALSE;
12834     ModeHighlight();
12835     SetGameInfo();
12836     DisplayTwoMachinesTitle();
12837     firstMove = TRUE;
12838     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12839         onmove = &first;
12840     } else {
12841         onmove = &second;
12842     }
12843     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12844     SendToProgram(first.computerString, &first);
12845     if (first.sendName) {
12846       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12847       SendToProgram(buf, &first);
12848     }
12849     SendToProgram(second.computerString, &second);
12850     if (second.sendName) {
12851       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12852       SendToProgram(buf, &second);
12853     }
12854
12855     ResetClocks();
12856     if (!first.sendTime || !second.sendTime) {
12857         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12858         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12859     }
12860     if (onmove->sendTime) {
12861       if (onmove->useColors) {
12862         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12863       }
12864       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12865     }
12866     if (onmove->useColors) {
12867       SendToProgram(onmove->twoMachinesColor, onmove);
12868     }
12869     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12870 //    SendToProgram("go\n", onmove);
12871     onmove->maybeThinking = TRUE;
12872     SetMachineThinkingEnables();
12873
12874     StartClocks();
12875
12876     if(bookHit) { // [HGM] book: simulate book reply
12877         static char bookMove[MSG_SIZ]; // a bit generous?
12878
12879         programStats.nodes = programStats.depth = programStats.time =
12880         programStats.score = programStats.got_only_move = 0;
12881         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12882
12883         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12884         strcat(bookMove, bookHit);
12885         savedMessage = bookMove; // args for deferred call
12886         savedState = onmove;
12887         ScheduleDelayedEvent(DeferredBookMove, 1);
12888     }
12889 }
12890
12891 void
12892 TrainingEvent()
12893 {
12894     if (gameMode == Training) {
12895       SetTrainingModeOff();
12896       gameMode = PlayFromGameFile;
12897       DisplayMessage("", _("Training mode off"));
12898     } else {
12899       gameMode = Training;
12900       animateTraining = appData.animate;
12901
12902       /* make sure we are not already at the end of the game */
12903       if (currentMove < forwardMostMove) {
12904         SetTrainingModeOn();
12905         DisplayMessage("", _("Training mode on"));
12906       } else {
12907         gameMode = PlayFromGameFile;
12908         DisplayError(_("Already at end of game"), 0);
12909       }
12910     }
12911     ModeHighlight();
12912 }
12913
12914 void
12915 IcsClientEvent()
12916 {
12917     if (!appData.icsActive) return;
12918     switch (gameMode) {
12919       case IcsPlayingWhite:
12920       case IcsPlayingBlack:
12921       case IcsObserving:
12922       case IcsIdle:
12923       case BeginningOfGame:
12924       case IcsExamining:
12925         return;
12926
12927       case EditGame:
12928         break;
12929
12930       case EditPosition:
12931         EditPositionDone(TRUE);
12932         break;
12933
12934       case AnalyzeMode:
12935       case AnalyzeFile:
12936         ExitAnalyzeMode();
12937         break;
12938
12939       default:
12940         EditGameEvent();
12941         break;
12942     }
12943
12944     gameMode = IcsIdle;
12945     ModeHighlight();
12946     return;
12947 }
12948
12949
12950 void
12951 EditGameEvent()
12952 {
12953     int i;
12954
12955     switch (gameMode) {
12956       case Training:
12957         SetTrainingModeOff();
12958         break;
12959       case MachinePlaysWhite:
12960       case MachinePlaysBlack:
12961       case BeginningOfGame:
12962         SendToProgram("force\n", &first);
12963         SetUserThinkingEnables();
12964         break;
12965       case PlayFromGameFile:
12966         (void) StopLoadGameTimer();
12967         if (gameFileFP != NULL) {
12968             gameFileFP = NULL;
12969         }
12970         break;
12971       case EditPosition:
12972         EditPositionDone(TRUE);
12973         break;
12974       case AnalyzeMode:
12975       case AnalyzeFile:
12976         ExitAnalyzeMode();
12977         SendToProgram("force\n", &first);
12978         break;
12979       case TwoMachinesPlay:
12980         GameEnds(EndOfFile, NULL, GE_PLAYER);
12981         ResurrectChessProgram();
12982         SetUserThinkingEnables();
12983         break;
12984       case EndOfGame:
12985         ResurrectChessProgram();
12986         break;
12987       case IcsPlayingBlack:
12988       case IcsPlayingWhite:
12989         DisplayError(_("Warning: You are still playing a game"), 0);
12990         break;
12991       case IcsObserving:
12992         DisplayError(_("Warning: You are still observing a game"), 0);
12993         break;
12994       case IcsExamining:
12995         DisplayError(_("Warning: You are still examining a game"), 0);
12996         break;
12997       case IcsIdle:
12998         break;
12999       case EditGame:
13000       default:
13001         return;
13002     }
13003
13004     pausing = FALSE;
13005     StopClocks();
13006     first.offeredDraw = second.offeredDraw = 0;
13007
13008     if (gameMode == PlayFromGameFile) {
13009         whiteTimeRemaining = timeRemaining[0][currentMove];
13010         blackTimeRemaining = timeRemaining[1][currentMove];
13011         DisplayTitle("");
13012     }
13013
13014     if (gameMode == MachinePlaysWhite ||
13015         gameMode == MachinePlaysBlack ||
13016         gameMode == TwoMachinesPlay ||
13017         gameMode == EndOfGame) {
13018         i = forwardMostMove;
13019         while (i > currentMove) {
13020             SendToProgram("undo\n", &first);
13021             i--;
13022         }
13023         whiteTimeRemaining = timeRemaining[0][currentMove];
13024         blackTimeRemaining = timeRemaining[1][currentMove];
13025         DisplayBothClocks();
13026         if (whiteFlag || blackFlag) {
13027             whiteFlag = blackFlag = 0;
13028         }
13029         DisplayTitle("");
13030     }
13031
13032     gameMode = EditGame;
13033     ModeHighlight();
13034     SetGameInfo();
13035 }
13036
13037
13038 void
13039 EditPositionEvent()
13040 {
13041     if (gameMode == EditPosition) {
13042         EditGameEvent();
13043         return;
13044     }
13045
13046     EditGameEvent();
13047     if (gameMode != EditGame) return;
13048
13049     gameMode = EditPosition;
13050     ModeHighlight();
13051     SetGameInfo();
13052     if (currentMove > 0)
13053       CopyBoard(boards[0], boards[currentMove]);
13054
13055     blackPlaysFirst = !WhiteOnMove(currentMove);
13056     ResetClocks();
13057     currentMove = forwardMostMove = backwardMostMove = 0;
13058     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13059     DisplayMove(-1);
13060 }
13061
13062 void
13063 ExitAnalyzeMode()
13064 {
13065     /* [DM] icsEngineAnalyze - possible call from other functions */
13066     if (appData.icsEngineAnalyze) {
13067         appData.icsEngineAnalyze = FALSE;
13068
13069         DisplayMessage("",_("Close ICS engine analyze..."));
13070     }
13071     if (first.analysisSupport && first.analyzing) {
13072       SendToProgram("exit\n", &first);
13073       first.analyzing = FALSE;
13074     }
13075     thinkOutput[0] = NULLCHAR;
13076 }
13077
13078 void
13079 EditPositionDone(Boolean fakeRights)
13080 {
13081     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13082
13083     startedFromSetupPosition = TRUE;
13084     InitChessProgram(&first, FALSE);
13085     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13086       boards[0][EP_STATUS] = EP_NONE;
13087       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13088     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13089         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13090         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13091       } else boards[0][CASTLING][2] = NoRights;
13092     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13093         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13094         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13095       } else boards[0][CASTLING][5] = NoRights;
13096     }
13097     SendToProgram("force\n", &first);
13098     if (blackPlaysFirst) {
13099         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13100         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13101         currentMove = forwardMostMove = backwardMostMove = 1;
13102         CopyBoard(boards[1], boards[0]);
13103     } else {
13104         currentMove = forwardMostMove = backwardMostMove = 0;
13105     }
13106     SendBoard(&first, forwardMostMove);
13107     if (appData.debugMode) {
13108         fprintf(debugFP, "EditPosDone\n");
13109     }
13110     DisplayTitle("");
13111     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13112     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13113     gameMode = EditGame;
13114     ModeHighlight();
13115     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13116     ClearHighlights(); /* [AS] */
13117 }
13118
13119 /* Pause for `ms' milliseconds */
13120 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13121 void
13122 TimeDelay(ms)
13123      long ms;
13124 {
13125     TimeMark m1, m2;
13126
13127     GetTimeMark(&m1);
13128     do {
13129         GetTimeMark(&m2);
13130     } while (SubtractTimeMarks(&m2, &m1) < ms);
13131 }
13132
13133 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13134 void
13135 SendMultiLineToICS(buf)
13136      char *buf;
13137 {
13138     char temp[MSG_SIZ+1], *p;
13139     int len;
13140
13141     len = strlen(buf);
13142     if (len > MSG_SIZ)
13143       len = MSG_SIZ;
13144
13145     strncpy(temp, buf, len);
13146     temp[len] = 0;
13147
13148     p = temp;
13149     while (*p) {
13150         if (*p == '\n' || *p == '\r')
13151           *p = ' ';
13152         ++p;
13153     }
13154
13155     strcat(temp, "\n");
13156     SendToICS(temp);
13157     SendToPlayer(temp, strlen(temp));
13158 }
13159
13160 void
13161 SetWhiteToPlayEvent()
13162 {
13163     if (gameMode == EditPosition) {
13164         blackPlaysFirst = FALSE;
13165         DisplayBothClocks();    /* works because currentMove is 0 */
13166     } else if (gameMode == IcsExamining) {
13167         SendToICS(ics_prefix);
13168         SendToICS("tomove white\n");
13169     }
13170 }
13171
13172 void
13173 SetBlackToPlayEvent()
13174 {
13175     if (gameMode == EditPosition) {
13176         blackPlaysFirst = TRUE;
13177         currentMove = 1;        /* kludge */
13178         DisplayBothClocks();
13179         currentMove = 0;
13180     } else if (gameMode == IcsExamining) {
13181         SendToICS(ics_prefix);
13182         SendToICS("tomove black\n");
13183     }
13184 }
13185
13186 void
13187 EditPositionMenuEvent(selection, x, y)
13188      ChessSquare selection;
13189      int x, y;
13190 {
13191     char buf[MSG_SIZ];
13192     ChessSquare piece = boards[0][y][x];
13193
13194     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13195
13196     switch (selection) {
13197       case ClearBoard:
13198         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13199             SendToICS(ics_prefix);
13200             SendToICS("bsetup clear\n");
13201         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13202             SendToICS(ics_prefix);
13203             SendToICS("clearboard\n");
13204         } else {
13205             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13206                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13207                 for (y = 0; y < BOARD_HEIGHT; y++) {
13208                     if (gameMode == IcsExamining) {
13209                         if (boards[currentMove][y][x] != EmptySquare) {
13210                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13211                                     AAA + x, ONE + y);
13212                             SendToICS(buf);
13213                         }
13214                     } else {
13215                         boards[0][y][x] = p;
13216                     }
13217                 }
13218             }
13219         }
13220         if (gameMode == EditPosition) {
13221             DrawPosition(FALSE, boards[0]);
13222         }
13223         break;
13224
13225       case WhitePlay:
13226         SetWhiteToPlayEvent();
13227         break;
13228
13229       case BlackPlay:
13230         SetBlackToPlayEvent();
13231         break;
13232
13233       case EmptySquare:
13234         if (gameMode == IcsExamining) {
13235             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13236             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13237             SendToICS(buf);
13238         } else {
13239             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13240                 if(x == BOARD_LEFT-2) {
13241                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13242                     boards[0][y][1] = 0;
13243                 } else
13244                 if(x == BOARD_RGHT+1) {
13245                     if(y >= gameInfo.holdingsSize) break;
13246                     boards[0][y][BOARD_WIDTH-2] = 0;
13247                 } else break;
13248             }
13249             boards[0][y][x] = EmptySquare;
13250             DrawPosition(FALSE, boards[0]);
13251         }
13252         break;
13253
13254       case PromotePiece:
13255         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13256            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13257             selection = (ChessSquare) (PROMOTED piece);
13258         } else if(piece == EmptySquare) selection = WhiteSilver;
13259         else selection = (ChessSquare)((int)piece - 1);
13260         goto defaultlabel;
13261
13262       case DemotePiece:
13263         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13264            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13265             selection = (ChessSquare) (DEMOTED piece);
13266         } else if(piece == EmptySquare) selection = BlackSilver;
13267         else selection = (ChessSquare)((int)piece + 1);
13268         goto defaultlabel;
13269
13270       case WhiteQueen:
13271       case BlackQueen:
13272         if(gameInfo.variant == VariantShatranj ||
13273            gameInfo.variant == VariantXiangqi  ||
13274            gameInfo.variant == VariantCourier  ||
13275            gameInfo.variant == VariantMakruk     )
13276             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13277         goto defaultlabel;
13278
13279       case WhiteKing:
13280       case BlackKing:
13281         if(gameInfo.variant == VariantXiangqi)
13282             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13283         if(gameInfo.variant == VariantKnightmate)
13284             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13285       default:
13286         defaultlabel:
13287         if (gameMode == IcsExamining) {
13288             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13289             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13290                      PieceToChar(selection), AAA + x, ONE + y);
13291             SendToICS(buf);
13292         } else {
13293             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13294                 int n;
13295                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13296                     n = PieceToNumber(selection - BlackPawn);
13297                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13298                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13299                     boards[0][BOARD_HEIGHT-1-n][1]++;
13300                 } else
13301                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13302                     n = PieceToNumber(selection);
13303                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13304                     boards[0][n][BOARD_WIDTH-1] = selection;
13305                     boards[0][n][BOARD_WIDTH-2]++;
13306                 }
13307             } else
13308             boards[0][y][x] = selection;
13309             DrawPosition(TRUE, boards[0]);
13310         }
13311         break;
13312     }
13313 }
13314
13315
13316 void
13317 DropMenuEvent(selection, x, y)
13318      ChessSquare selection;
13319      int x, y;
13320 {
13321     ChessMove moveType;
13322
13323     switch (gameMode) {
13324       case IcsPlayingWhite:
13325       case MachinePlaysBlack:
13326         if (!WhiteOnMove(currentMove)) {
13327             DisplayMoveError(_("It is Black's turn"));
13328             return;
13329         }
13330         moveType = WhiteDrop;
13331         break;
13332       case IcsPlayingBlack:
13333       case MachinePlaysWhite:
13334         if (WhiteOnMove(currentMove)) {
13335             DisplayMoveError(_("It is White's turn"));
13336             return;
13337         }
13338         moveType = BlackDrop;
13339         break;
13340       case EditGame:
13341         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13342         break;
13343       default:
13344         return;
13345     }
13346
13347     if (moveType == BlackDrop && selection < BlackPawn) {
13348       selection = (ChessSquare) ((int) selection
13349                                  + (int) BlackPawn - (int) WhitePawn);
13350     }
13351     if (boards[currentMove][y][x] != EmptySquare) {
13352         DisplayMoveError(_("That square is occupied"));
13353         return;
13354     }
13355
13356     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13357 }
13358
13359 void
13360 AcceptEvent()
13361 {
13362     /* Accept a pending offer of any kind from opponent */
13363
13364     if (appData.icsActive) {
13365         SendToICS(ics_prefix);
13366         SendToICS("accept\n");
13367     } else if (cmailMsgLoaded) {
13368         if (currentMove == cmailOldMove &&
13369             commentList[cmailOldMove] != NULL &&
13370             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13371                    "Black offers a draw" : "White offers a draw")) {
13372             TruncateGame();
13373             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13374             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13375         } else {
13376             DisplayError(_("There is no pending offer on this move"), 0);
13377             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13378         }
13379     } else {
13380         /* Not used for offers from chess program */
13381     }
13382 }
13383
13384 void
13385 DeclineEvent()
13386 {
13387     /* Decline a pending offer of any kind from opponent */
13388
13389     if (appData.icsActive) {
13390         SendToICS(ics_prefix);
13391         SendToICS("decline\n");
13392     } else if (cmailMsgLoaded) {
13393         if (currentMove == cmailOldMove &&
13394             commentList[cmailOldMove] != NULL &&
13395             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13396                    "Black offers a draw" : "White offers a draw")) {
13397 #ifdef NOTDEF
13398             AppendComment(cmailOldMove, "Draw declined", TRUE);
13399             DisplayComment(cmailOldMove - 1, "Draw declined");
13400 #endif /*NOTDEF*/
13401         } else {
13402             DisplayError(_("There is no pending offer on this move"), 0);
13403         }
13404     } else {
13405         /* Not used for offers from chess program */
13406     }
13407 }
13408
13409 void
13410 RematchEvent()
13411 {
13412     /* Issue ICS rematch command */
13413     if (appData.icsActive) {
13414         SendToICS(ics_prefix);
13415         SendToICS("rematch\n");
13416     }
13417 }
13418
13419 void
13420 CallFlagEvent()
13421 {
13422     /* Call your opponent's flag (claim a win on time) */
13423     if (appData.icsActive) {
13424         SendToICS(ics_prefix);
13425         SendToICS("flag\n");
13426     } else {
13427         switch (gameMode) {
13428           default:
13429             return;
13430           case MachinePlaysWhite:
13431             if (whiteFlag) {
13432                 if (blackFlag)
13433                   GameEnds(GameIsDrawn, "Both players ran out of time",
13434                            GE_PLAYER);
13435                 else
13436                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13437             } else {
13438                 DisplayError(_("Your opponent is not out of time"), 0);
13439             }
13440             break;
13441           case MachinePlaysBlack:
13442             if (blackFlag) {
13443                 if (whiteFlag)
13444                   GameEnds(GameIsDrawn, "Both players ran out of time",
13445                            GE_PLAYER);
13446                 else
13447                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13448             } else {
13449                 DisplayError(_("Your opponent is not out of time"), 0);
13450             }
13451             break;
13452         }
13453     }
13454 }
13455
13456 void
13457 ClockClick(int which)
13458 {       // [HGM] code moved to back-end from winboard.c
13459         if(which) { // black clock
13460           if (gameMode == EditPosition || gameMode == IcsExamining) {
13461             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13462             SetBlackToPlayEvent();
13463           } else if (gameMode == EditGame || shiftKey) {
13464             AdjustClock(which, -1);
13465           } else if (gameMode == IcsPlayingWhite ||
13466                      gameMode == MachinePlaysBlack) {
13467             CallFlagEvent();
13468           }
13469         } else { // white clock
13470           if (gameMode == EditPosition || gameMode == IcsExamining) {
13471             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13472             SetWhiteToPlayEvent();
13473           } else if (gameMode == EditGame || shiftKey) {
13474             AdjustClock(which, -1);
13475           } else if (gameMode == IcsPlayingBlack ||
13476                    gameMode == MachinePlaysWhite) {
13477             CallFlagEvent();
13478           }
13479         }
13480 }
13481
13482 void
13483 DrawEvent()
13484 {
13485     /* Offer draw or accept pending draw offer from opponent */
13486
13487     if (appData.icsActive) {
13488         /* Note: tournament rules require draw offers to be
13489            made after you make your move but before you punch
13490            your clock.  Currently ICS doesn't let you do that;
13491            instead, you immediately punch your clock after making
13492            a move, but you can offer a draw at any time. */
13493
13494         SendToICS(ics_prefix);
13495         SendToICS("draw\n");
13496         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13497     } else if (cmailMsgLoaded) {
13498         if (currentMove == cmailOldMove &&
13499             commentList[cmailOldMove] != NULL &&
13500             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13501                    "Black offers a draw" : "White offers a draw")) {
13502             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13503             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13504         } else if (currentMove == cmailOldMove + 1) {
13505             char *offer = WhiteOnMove(cmailOldMove) ?
13506               "White offers a draw" : "Black offers a draw";
13507             AppendComment(currentMove, offer, TRUE);
13508             DisplayComment(currentMove - 1, offer);
13509             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13510         } else {
13511             DisplayError(_("You must make your move before offering a draw"), 0);
13512             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13513         }
13514     } else if (first.offeredDraw) {
13515         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13516     } else {
13517         if (first.sendDrawOffers) {
13518             SendToProgram("draw\n", &first);
13519             userOfferedDraw = TRUE;
13520         }
13521     }
13522 }
13523
13524 void
13525 AdjournEvent()
13526 {
13527     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13528
13529     if (appData.icsActive) {
13530         SendToICS(ics_prefix);
13531         SendToICS("adjourn\n");
13532     } else {
13533         /* Currently GNU Chess doesn't offer or accept Adjourns */
13534     }
13535 }
13536
13537
13538 void
13539 AbortEvent()
13540 {
13541     /* Offer Abort or accept pending Abort offer from opponent */
13542
13543     if (appData.icsActive) {
13544         SendToICS(ics_prefix);
13545         SendToICS("abort\n");
13546     } else {
13547         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13548     }
13549 }
13550
13551 void
13552 ResignEvent()
13553 {
13554     /* Resign.  You can do this even if it's not your turn. */
13555
13556     if (appData.icsActive) {
13557         SendToICS(ics_prefix);
13558         SendToICS("resign\n");
13559     } else {
13560         switch (gameMode) {
13561           case MachinePlaysWhite:
13562             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13563             break;
13564           case MachinePlaysBlack:
13565             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13566             break;
13567           case EditGame:
13568             if (cmailMsgLoaded) {
13569                 TruncateGame();
13570                 if (WhiteOnMove(cmailOldMove)) {
13571                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13572                 } else {
13573                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13574                 }
13575                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13576             }
13577             break;
13578           default:
13579             break;
13580         }
13581     }
13582 }
13583
13584
13585 void
13586 StopObservingEvent()
13587 {
13588     /* Stop observing current games */
13589     SendToICS(ics_prefix);
13590     SendToICS("unobserve\n");
13591 }
13592
13593 void
13594 StopExaminingEvent()
13595 {
13596     /* Stop observing current game */
13597     SendToICS(ics_prefix);
13598     SendToICS("unexamine\n");
13599 }
13600
13601 void
13602 ForwardInner(target)
13603      int target;
13604 {
13605     int limit;
13606
13607     if (appData.debugMode)
13608         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13609                 target, currentMove, forwardMostMove);
13610
13611     if (gameMode == EditPosition)
13612       return;
13613
13614     if (gameMode == PlayFromGameFile && !pausing)
13615       PauseEvent();
13616
13617     if (gameMode == IcsExamining && pausing)
13618       limit = pauseExamForwardMostMove;
13619     else
13620       limit = forwardMostMove;
13621
13622     if (target > limit) target = limit;
13623
13624     if (target > 0 && moveList[target - 1][0]) {
13625         int fromX, fromY, toX, toY;
13626         toX = moveList[target - 1][2] - AAA;
13627         toY = moveList[target - 1][3] - ONE;
13628         if (moveList[target - 1][1] == '@') {
13629             if (appData.highlightLastMove) {
13630                 SetHighlights(-1, -1, toX, toY);
13631             }
13632         } else {
13633             fromX = moveList[target - 1][0] - AAA;
13634             fromY = moveList[target - 1][1] - ONE;
13635             if (target == currentMove + 1) {
13636                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13637             }
13638             if (appData.highlightLastMove) {
13639                 SetHighlights(fromX, fromY, toX, toY);
13640             }
13641         }
13642     }
13643     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13644         gameMode == Training || gameMode == PlayFromGameFile ||
13645         gameMode == AnalyzeFile) {
13646         while (currentMove < target) {
13647             SendMoveToProgram(currentMove++, &first);
13648         }
13649     } else {
13650         currentMove = target;
13651     }
13652
13653     if (gameMode == EditGame || gameMode == EndOfGame) {
13654         whiteTimeRemaining = timeRemaining[0][currentMove];
13655         blackTimeRemaining = timeRemaining[1][currentMove];
13656     }
13657     DisplayBothClocks();
13658     DisplayMove(currentMove - 1);
13659     DrawPosition(FALSE, boards[currentMove]);
13660     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13661     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13662         DisplayComment(currentMove - 1, commentList[currentMove]);
13663     }
13664     DisplayBook(currentMove);
13665 }
13666
13667
13668 void
13669 ForwardEvent()
13670 {
13671     if (gameMode == IcsExamining && !pausing) {
13672         SendToICS(ics_prefix);
13673         SendToICS("forward\n");
13674     } else {
13675         ForwardInner(currentMove + 1);
13676     }
13677 }
13678
13679 void
13680 ToEndEvent()
13681 {
13682     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13683         /* to optimze, we temporarily turn off analysis mode while we feed
13684          * the remaining moves to the engine. Otherwise we get analysis output
13685          * after each move.
13686          */
13687         if (first.analysisSupport) {
13688           SendToProgram("exit\nforce\n", &first);
13689           first.analyzing = FALSE;
13690         }
13691     }
13692
13693     if (gameMode == IcsExamining && !pausing) {
13694         SendToICS(ics_prefix);
13695         SendToICS("forward 999999\n");
13696     } else {
13697         ForwardInner(forwardMostMove);
13698     }
13699
13700     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13701         /* we have fed all the moves, so reactivate analysis mode */
13702         SendToProgram("analyze\n", &first);
13703         first.analyzing = TRUE;
13704         /*first.maybeThinking = TRUE;*/
13705         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13706     }
13707 }
13708
13709 void
13710 BackwardInner(target)
13711      int target;
13712 {
13713     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13714
13715     if (appData.debugMode)
13716         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13717                 target, currentMove, forwardMostMove);
13718
13719     if (gameMode == EditPosition) return;
13720     if (currentMove <= backwardMostMove) {
13721         ClearHighlights();
13722         DrawPosition(full_redraw, boards[currentMove]);
13723         return;
13724     }
13725     if (gameMode == PlayFromGameFile && !pausing)
13726       PauseEvent();
13727
13728     if (moveList[target][0]) {
13729         int fromX, fromY, toX, toY;
13730         toX = moveList[target][2] - AAA;
13731         toY = moveList[target][3] - ONE;
13732         if (moveList[target][1] == '@') {
13733             if (appData.highlightLastMove) {
13734                 SetHighlights(-1, -1, toX, toY);
13735             }
13736         } else {
13737             fromX = moveList[target][0] - AAA;
13738             fromY = moveList[target][1] - ONE;
13739             if (target == currentMove - 1) {
13740                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13741             }
13742             if (appData.highlightLastMove) {
13743                 SetHighlights(fromX, fromY, toX, toY);
13744             }
13745         }
13746     }
13747     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13748         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13749         while (currentMove > target) {
13750             SendToProgram("undo\n", &first);
13751             currentMove--;
13752         }
13753     } else {
13754         currentMove = target;
13755     }
13756
13757     if (gameMode == EditGame || gameMode == EndOfGame) {
13758         whiteTimeRemaining = timeRemaining[0][currentMove];
13759         blackTimeRemaining = timeRemaining[1][currentMove];
13760     }
13761     DisplayBothClocks();
13762     DisplayMove(currentMove - 1);
13763     DrawPosition(full_redraw, boards[currentMove]);
13764     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13765     // [HGM] PV info: routine tests if comment empty
13766     DisplayComment(currentMove - 1, commentList[currentMove]);
13767     DisplayBook(currentMove);
13768 }
13769
13770 void
13771 BackwardEvent()
13772 {
13773     if (gameMode == IcsExamining && !pausing) {
13774         SendToICS(ics_prefix);
13775         SendToICS("backward\n");
13776     } else {
13777         BackwardInner(currentMove - 1);
13778     }
13779 }
13780
13781 void
13782 ToStartEvent()
13783 {
13784     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13785         /* to optimize, we temporarily turn off analysis mode while we undo
13786          * all the moves. Otherwise we get analysis output after each undo.
13787          */
13788         if (first.analysisSupport) {
13789           SendToProgram("exit\nforce\n", &first);
13790           first.analyzing = FALSE;
13791         }
13792     }
13793
13794     if (gameMode == IcsExamining && !pausing) {
13795         SendToICS(ics_prefix);
13796         SendToICS("backward 999999\n");
13797     } else {
13798         BackwardInner(backwardMostMove);
13799     }
13800
13801     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13802         /* we have fed all the moves, so reactivate analysis mode */
13803         SendToProgram("analyze\n", &first);
13804         first.analyzing = TRUE;
13805         /*first.maybeThinking = TRUE;*/
13806         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13807     }
13808 }
13809
13810 void
13811 ToNrEvent(int to)
13812 {
13813   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13814   if (to >= forwardMostMove) to = forwardMostMove;
13815   if (to <= backwardMostMove) to = backwardMostMove;
13816   if (to < currentMove) {
13817     BackwardInner(to);
13818   } else {
13819     ForwardInner(to);
13820   }
13821 }
13822
13823 void
13824 RevertEvent(Boolean annotate)
13825 {
13826     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13827         return;
13828     }
13829     if (gameMode != IcsExamining) {
13830         DisplayError(_("You are not examining a game"), 0);
13831         return;
13832     }
13833     if (pausing) {
13834         DisplayError(_("You can't revert while pausing"), 0);
13835         return;
13836     }
13837     SendToICS(ics_prefix);
13838     SendToICS("revert\n");
13839 }
13840
13841 void
13842 RetractMoveEvent()
13843 {
13844     switch (gameMode) {
13845       case MachinePlaysWhite:
13846       case MachinePlaysBlack:
13847         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13848             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13849             return;
13850         }
13851         if (forwardMostMove < 2) return;
13852         currentMove = forwardMostMove = forwardMostMove - 2;
13853         whiteTimeRemaining = timeRemaining[0][currentMove];
13854         blackTimeRemaining = timeRemaining[1][currentMove];
13855         DisplayBothClocks();
13856         DisplayMove(currentMove - 1);
13857         ClearHighlights();/*!! could figure this out*/
13858         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13859         SendToProgram("remove\n", &first);
13860         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13861         break;
13862
13863       case BeginningOfGame:
13864       default:
13865         break;
13866
13867       case IcsPlayingWhite:
13868       case IcsPlayingBlack:
13869         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13870             SendToICS(ics_prefix);
13871             SendToICS("takeback 2\n");
13872         } else {
13873             SendToICS(ics_prefix);
13874             SendToICS("takeback 1\n");
13875         }
13876         break;
13877     }
13878 }
13879
13880 void
13881 MoveNowEvent()
13882 {
13883     ChessProgramState *cps;
13884
13885     switch (gameMode) {
13886       case MachinePlaysWhite:
13887         if (!WhiteOnMove(forwardMostMove)) {
13888             DisplayError(_("It is your turn"), 0);
13889             return;
13890         }
13891         cps = &first;
13892         break;
13893       case MachinePlaysBlack:
13894         if (WhiteOnMove(forwardMostMove)) {
13895             DisplayError(_("It is your turn"), 0);
13896             return;
13897         }
13898         cps = &first;
13899         break;
13900       case TwoMachinesPlay:
13901         if (WhiteOnMove(forwardMostMove) ==
13902             (first.twoMachinesColor[0] == 'w')) {
13903             cps = &first;
13904         } else {
13905             cps = &second;
13906         }
13907         break;
13908       case BeginningOfGame:
13909       default:
13910         return;
13911     }
13912     SendToProgram("?\n", cps);
13913 }
13914
13915 void
13916 TruncateGameEvent()
13917 {
13918     EditGameEvent();
13919     if (gameMode != EditGame) return;
13920     TruncateGame();
13921 }
13922
13923 void
13924 TruncateGame()
13925 {
13926     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13927     if (forwardMostMove > currentMove) {
13928         if (gameInfo.resultDetails != NULL) {
13929             free(gameInfo.resultDetails);
13930             gameInfo.resultDetails = NULL;
13931             gameInfo.result = GameUnfinished;
13932         }
13933         forwardMostMove = currentMove;
13934         HistorySet(parseList, backwardMostMove, forwardMostMove,
13935                    currentMove-1);
13936     }
13937 }
13938
13939 void
13940 HintEvent()
13941 {
13942     if (appData.noChessProgram) return;
13943     switch (gameMode) {
13944       case MachinePlaysWhite:
13945         if (WhiteOnMove(forwardMostMove)) {
13946             DisplayError(_("Wait until your turn"), 0);
13947             return;
13948         }
13949         break;
13950       case BeginningOfGame:
13951       case MachinePlaysBlack:
13952         if (!WhiteOnMove(forwardMostMove)) {
13953             DisplayError(_("Wait until your turn"), 0);
13954             return;
13955         }
13956         break;
13957       default:
13958         DisplayError(_("No hint available"), 0);
13959         return;
13960     }
13961     SendToProgram("hint\n", &first);
13962     hintRequested = TRUE;
13963 }
13964
13965 void
13966 BookEvent()
13967 {
13968     if (appData.noChessProgram) return;
13969     switch (gameMode) {
13970       case MachinePlaysWhite:
13971         if (WhiteOnMove(forwardMostMove)) {
13972             DisplayError(_("Wait until your turn"), 0);
13973             return;
13974         }
13975         break;
13976       case BeginningOfGame:
13977       case MachinePlaysBlack:
13978         if (!WhiteOnMove(forwardMostMove)) {
13979             DisplayError(_("Wait until your turn"), 0);
13980             return;
13981         }
13982         break;
13983       case EditPosition:
13984         EditPositionDone(TRUE);
13985         break;
13986       case TwoMachinesPlay:
13987         return;
13988       default:
13989         break;
13990     }
13991     SendToProgram("bk\n", &first);
13992     bookOutput[0] = NULLCHAR;
13993     bookRequested = TRUE;
13994 }
13995
13996 void
13997 AboutGameEvent()
13998 {
13999     char *tags = PGNTags(&gameInfo);
14000     TagsPopUp(tags, CmailMsg());
14001     free(tags);
14002 }
14003
14004 /* end button procedures */
14005
14006 void
14007 PrintPosition(fp, move)
14008      FILE *fp;
14009      int move;
14010 {
14011     int i, j;
14012
14013     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14014         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14015             char c = PieceToChar(boards[move][i][j]);
14016             fputc(c == 'x' ? '.' : c, fp);
14017             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14018         }
14019     }
14020     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14021       fprintf(fp, "white to play\n");
14022     else
14023       fprintf(fp, "black to play\n");
14024 }
14025
14026 void
14027 PrintOpponents(fp)
14028      FILE *fp;
14029 {
14030     if (gameInfo.white != NULL) {
14031         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14032     } else {
14033         fprintf(fp, "\n");
14034     }
14035 }
14036
14037 /* Find last component of program's own name, using some heuristics */
14038 void
14039 TidyProgramName(prog, host, buf)
14040      char *prog, *host, buf[MSG_SIZ];
14041 {
14042     char *p, *q;
14043     int local = (strcmp(host, "localhost") == 0);
14044     while (!local && (p = strchr(prog, ';')) != NULL) {
14045         p++;
14046         while (*p == ' ') p++;
14047         prog = p;
14048     }
14049     if (*prog == '"' || *prog == '\'') {
14050         q = strchr(prog + 1, *prog);
14051     } else {
14052         q = strchr(prog, ' ');
14053     }
14054     if (q == NULL) q = prog + strlen(prog);
14055     p = q;
14056     while (p >= prog && *p != '/' && *p != '\\') p--;
14057     p++;
14058     if(p == prog && *p == '"') p++;
14059     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14060     memcpy(buf, p, q - p);
14061     buf[q - p] = NULLCHAR;
14062     if (!local) {
14063         strcat(buf, "@");
14064         strcat(buf, host);
14065     }
14066 }
14067
14068 char *
14069 TimeControlTagValue()
14070 {
14071     char buf[MSG_SIZ];
14072     if (!appData.clockMode) {
14073       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14074     } else if (movesPerSession > 0) {
14075       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14076     } else if (timeIncrement == 0) {
14077       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14078     } else {
14079       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14080     }
14081     return StrSave(buf);
14082 }
14083
14084 void
14085 SetGameInfo()
14086 {
14087     /* This routine is used only for certain modes */
14088     VariantClass v = gameInfo.variant;
14089     ChessMove r = GameUnfinished;
14090     char *p = NULL;
14091
14092     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14093         r = gameInfo.result;
14094         p = gameInfo.resultDetails;
14095         gameInfo.resultDetails = NULL;
14096     }
14097     ClearGameInfo(&gameInfo);
14098     gameInfo.variant = v;
14099
14100     switch (gameMode) {
14101       case MachinePlaysWhite:
14102         gameInfo.event = StrSave( appData.pgnEventHeader );
14103         gameInfo.site = StrSave(HostName());
14104         gameInfo.date = PGNDate();
14105         gameInfo.round = StrSave("-");
14106         gameInfo.white = StrSave(first.tidy);
14107         gameInfo.black = StrSave(UserName());
14108         gameInfo.timeControl = TimeControlTagValue();
14109         break;
14110
14111       case MachinePlaysBlack:
14112         gameInfo.event = StrSave( appData.pgnEventHeader );
14113         gameInfo.site = StrSave(HostName());
14114         gameInfo.date = PGNDate();
14115         gameInfo.round = StrSave("-");
14116         gameInfo.white = StrSave(UserName());
14117         gameInfo.black = StrSave(first.tidy);
14118         gameInfo.timeControl = TimeControlTagValue();
14119         break;
14120
14121       case TwoMachinesPlay:
14122         gameInfo.event = StrSave( appData.pgnEventHeader );
14123         gameInfo.site = StrSave(HostName());
14124         gameInfo.date = PGNDate();
14125         if (roundNr > 0) {
14126             char buf[MSG_SIZ];
14127             snprintf(buf, MSG_SIZ, "%d", roundNr);
14128             gameInfo.round = StrSave(buf);
14129         } else {
14130             gameInfo.round = StrSave("-");
14131         }
14132         if (first.twoMachinesColor[0] == 'w') {
14133             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14134             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14135         } else {
14136             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14137             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14138         }
14139         gameInfo.timeControl = TimeControlTagValue();
14140         break;
14141
14142       case EditGame:
14143         gameInfo.event = StrSave("Edited game");
14144         gameInfo.site = StrSave(HostName());
14145         gameInfo.date = PGNDate();
14146         gameInfo.round = StrSave("-");
14147         gameInfo.white = StrSave("-");
14148         gameInfo.black = StrSave("-");
14149         gameInfo.result = r;
14150         gameInfo.resultDetails = p;
14151         break;
14152
14153       case EditPosition:
14154         gameInfo.event = StrSave("Edited position");
14155         gameInfo.site = StrSave(HostName());
14156         gameInfo.date = PGNDate();
14157         gameInfo.round = StrSave("-");
14158         gameInfo.white = StrSave("-");
14159         gameInfo.black = StrSave("-");
14160         break;
14161
14162       case IcsPlayingWhite:
14163       case IcsPlayingBlack:
14164       case IcsObserving:
14165       case IcsExamining:
14166         break;
14167
14168       case PlayFromGameFile:
14169         gameInfo.event = StrSave("Game from non-PGN file");
14170         gameInfo.site = StrSave(HostName());
14171         gameInfo.date = PGNDate();
14172         gameInfo.round = StrSave("-");
14173         gameInfo.white = StrSave("?");
14174         gameInfo.black = StrSave("?");
14175         break;
14176
14177       default:
14178         break;
14179     }
14180 }
14181
14182 void
14183 ReplaceComment(index, text)
14184      int index;
14185      char *text;
14186 {
14187     int len;
14188     char *p;
14189     float score;
14190
14191     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14192        pvInfoList[index-1].depth == len &&
14193        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14194        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14195     while (*text == '\n') text++;
14196     len = strlen(text);
14197     while (len > 0 && text[len - 1] == '\n') len--;
14198
14199     if (commentList[index] != NULL)
14200       free(commentList[index]);
14201
14202     if (len == 0) {
14203         commentList[index] = NULL;
14204         return;
14205     }
14206   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14207       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14208       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14209     commentList[index] = (char *) malloc(len + 2);
14210     strncpy(commentList[index], text, len);
14211     commentList[index][len] = '\n';
14212     commentList[index][len + 1] = NULLCHAR;
14213   } else {
14214     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14215     char *p;
14216     commentList[index] = (char *) malloc(len + 7);
14217     safeStrCpy(commentList[index], "{\n", 3);
14218     safeStrCpy(commentList[index]+2, text, len+1);
14219     commentList[index][len+2] = NULLCHAR;
14220     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14221     strcat(commentList[index], "\n}\n");
14222   }
14223 }
14224
14225 void
14226 CrushCRs(text)
14227      char *text;
14228 {
14229   char *p = text;
14230   char *q = text;
14231   char ch;
14232
14233   do {
14234     ch = *p++;
14235     if (ch == '\r') continue;
14236     *q++ = ch;
14237   } while (ch != '\0');
14238 }
14239
14240 void
14241 AppendComment(index, text, addBraces)
14242      int index;
14243      char *text;
14244      Boolean addBraces; // [HGM] braces: tells if we should add {}
14245 {
14246     int oldlen, len;
14247     char *old;
14248
14249 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14250     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14251
14252     CrushCRs(text);
14253     while (*text == '\n') text++;
14254     len = strlen(text);
14255     while (len > 0 && text[len - 1] == '\n') len--;
14256
14257     if (len == 0) return;
14258
14259     if (commentList[index] != NULL) {
14260         old = commentList[index];
14261         oldlen = strlen(old);
14262         while(commentList[index][oldlen-1] ==  '\n')
14263           commentList[index][--oldlen] = NULLCHAR;
14264         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14265         safeStrCpy(commentList[index], old, oldlen + len + 6);
14266         free(old);
14267         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14268         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14269           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14270           while (*text == '\n') { text++; len--; }
14271           commentList[index][--oldlen] = NULLCHAR;
14272       }
14273         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14274         else          strcat(commentList[index], "\n");
14275         strcat(commentList[index], text);
14276         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14277         else          strcat(commentList[index], "\n");
14278     } else {
14279         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14280         if(addBraces)
14281           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14282         else commentList[index][0] = NULLCHAR;
14283         strcat(commentList[index], text);
14284         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14285         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14286     }
14287 }
14288
14289 static char * FindStr( char * text, char * sub_text )
14290 {
14291     char * result = strstr( text, sub_text );
14292
14293     if( result != NULL ) {
14294         result += strlen( sub_text );
14295     }
14296
14297     return result;
14298 }
14299
14300 /* [AS] Try to extract PV info from PGN comment */
14301 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14302 char *GetInfoFromComment( int index, char * text )
14303 {
14304     char * sep = text, *p;
14305
14306     if( text != NULL && index > 0 ) {
14307         int score = 0;
14308         int depth = 0;
14309         int time = -1, sec = 0, deci;
14310         char * s_eval = FindStr( text, "[%eval " );
14311         char * s_emt = FindStr( text, "[%emt " );
14312
14313         if( s_eval != NULL || s_emt != NULL ) {
14314             /* New style */
14315             char delim;
14316
14317             if( s_eval != NULL ) {
14318                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14319                     return text;
14320                 }
14321
14322                 if( delim != ']' ) {
14323                     return text;
14324                 }
14325             }
14326
14327             if( s_emt != NULL ) {
14328             }
14329                 return text;
14330         }
14331         else {
14332             /* We expect something like: [+|-]nnn.nn/dd */
14333             int score_lo = 0;
14334
14335             if(*text != '{') return text; // [HGM] braces: must be normal comment
14336
14337             sep = strchr( text, '/' );
14338             if( sep == NULL || sep < (text+4) ) {
14339                 return text;
14340             }
14341
14342             p = text;
14343             if(p[1] == '(') { // comment starts with PV
14344                p = strchr(p, ')'); // locate end of PV
14345                if(p == NULL || sep < p+5) return text;
14346                // at this point we have something like "{(.*) +0.23/6 ..."
14347                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14348                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14349                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14350             }
14351             time = -1; sec = -1; deci = -1;
14352             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14353                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14354                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14355                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14356                 return text;
14357             }
14358
14359             if( score_lo < 0 || score_lo >= 100 ) {
14360                 return text;
14361             }
14362
14363             if(sec >= 0) time = 600*time + 10*sec; else
14364             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14365
14366             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14367
14368             /* [HGM] PV time: now locate end of PV info */
14369             while( *++sep >= '0' && *sep <= '9'); // strip depth
14370             if(time >= 0)
14371             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14372             if(sec >= 0)
14373             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14374             if(deci >= 0)
14375             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14376             while(*sep == ' ') sep++;
14377         }
14378
14379         if( depth <= 0 ) {
14380             return text;
14381         }
14382
14383         if( time < 0 ) {
14384             time = -1;
14385         }
14386
14387         pvInfoList[index-1].depth = depth;
14388         pvInfoList[index-1].score = score;
14389         pvInfoList[index-1].time  = 10*time; // centi-sec
14390         if(*sep == '}') *sep = 0; else *--sep = '{';
14391         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14392     }
14393     return sep;
14394 }
14395
14396 void
14397 SendToProgram(message, cps)
14398      char *message;
14399      ChessProgramState *cps;
14400 {
14401     int count, outCount, error;
14402     char buf[MSG_SIZ];
14403
14404     if (cps->pr == NULL) return;
14405     Attention(cps);
14406
14407     if (appData.debugMode) {
14408         TimeMark now;
14409         GetTimeMark(&now);
14410         fprintf(debugFP, "%ld >%-6s: %s",
14411                 SubtractTimeMarks(&now, &programStartTime),
14412                 cps->which, message);
14413     }
14414
14415     count = strlen(message);
14416     outCount = OutputToProcess(cps->pr, message, count, &error);
14417     if (outCount < count && !exiting
14418                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14419       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14420       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14421         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14422             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14423                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14424                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14425                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14426             } else {
14427                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14428                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14429                 gameInfo.result = res;
14430             }
14431             gameInfo.resultDetails = StrSave(buf);
14432         }
14433         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14434         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14435     }
14436 }
14437
14438 void
14439 ReceiveFromProgram(isr, closure, message, count, error)
14440      InputSourceRef isr;
14441      VOIDSTAR closure;
14442      char *message;
14443      int count;
14444      int error;
14445 {
14446     char *end_str;
14447     char buf[MSG_SIZ];
14448     ChessProgramState *cps = (ChessProgramState *)closure;
14449
14450     if (isr != cps->isr) return; /* Killed intentionally */
14451     if (count <= 0) {
14452         if (count == 0) {
14453             RemoveInputSource(cps->isr);
14454             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14455             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14456                     _(cps->which), cps->program);
14457         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14458                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14459                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14460                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14461                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14462                 } else {
14463                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14464                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14465                     gameInfo.result = res;
14466                 }
14467                 gameInfo.resultDetails = StrSave(buf);
14468             }
14469             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14470             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14471         } else {
14472             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14473                     _(cps->which), cps->program);
14474             RemoveInputSource(cps->isr);
14475
14476             /* [AS] Program is misbehaving badly... kill it */
14477             if( count == -2 ) {
14478                 DestroyChildProcess( cps->pr, 9 );
14479                 cps->pr = NoProc;
14480             }
14481
14482             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14483         }
14484         return;
14485     }
14486
14487     if ((end_str = strchr(message, '\r')) != NULL)
14488       *end_str = NULLCHAR;
14489     if ((end_str = strchr(message, '\n')) != NULL)
14490       *end_str = NULLCHAR;
14491
14492     if (appData.debugMode) {
14493         TimeMark now; int print = 1;
14494         char *quote = ""; char c; int i;
14495
14496         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14497                 char start = message[0];
14498                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14499                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14500                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14501                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14502                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14503                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14504                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14505                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14506                    sscanf(message, "hint: %c", &c)!=1 && 
14507                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14508                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14509                     print = (appData.engineComments >= 2);
14510                 }
14511                 message[0] = start; // restore original message
14512         }
14513         if(print) {
14514                 GetTimeMark(&now);
14515                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14516                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14517                         quote,
14518                         message);
14519         }
14520     }
14521
14522     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14523     if (appData.icsEngineAnalyze) {
14524         if (strstr(message, "whisper") != NULL ||
14525              strstr(message, "kibitz") != NULL ||
14526             strstr(message, "tellics") != NULL) return;
14527     }
14528
14529     HandleMachineMove(message, cps);
14530 }
14531
14532
14533 void
14534 SendTimeControl(cps, mps, tc, inc, sd, st)
14535      ChessProgramState *cps;
14536      int mps, inc, sd, st;
14537      long tc;
14538 {
14539     char buf[MSG_SIZ];
14540     int seconds;
14541
14542     if( timeControl_2 > 0 ) {
14543         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14544             tc = timeControl_2;
14545         }
14546     }
14547     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14548     inc /= cps->timeOdds;
14549     st  /= cps->timeOdds;
14550
14551     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14552
14553     if (st > 0) {
14554       /* Set exact time per move, normally using st command */
14555       if (cps->stKludge) {
14556         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14557         seconds = st % 60;
14558         if (seconds == 0) {
14559           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14560         } else {
14561           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14562         }
14563       } else {
14564         snprintf(buf, MSG_SIZ, "st %d\n", st);
14565       }
14566     } else {
14567       /* Set conventional or incremental time control, using level command */
14568       if (seconds == 0) {
14569         /* Note old gnuchess bug -- minutes:seconds used to not work.
14570            Fixed in later versions, but still avoid :seconds
14571            when seconds is 0. */
14572         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14573       } else {
14574         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14575                  seconds, inc/1000.);
14576       }
14577     }
14578     SendToProgram(buf, cps);
14579
14580     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14581     /* Orthogonally, limit search to given depth */
14582     if (sd > 0) {
14583       if (cps->sdKludge) {
14584         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14585       } else {
14586         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14587       }
14588       SendToProgram(buf, cps);
14589     }
14590
14591     if(cps->nps >= 0) { /* [HGM] nps */
14592         if(cps->supportsNPS == FALSE)
14593           cps->nps = -1; // don't use if engine explicitly says not supported!
14594         else {
14595           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14596           SendToProgram(buf, cps);
14597         }
14598     }
14599 }
14600
14601 ChessProgramState *WhitePlayer()
14602 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14603 {
14604     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14605        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14606         return &second;
14607     return &first;
14608 }
14609
14610 void
14611 SendTimeRemaining(cps, machineWhite)
14612      ChessProgramState *cps;
14613      int /*boolean*/ machineWhite;
14614 {
14615     char message[MSG_SIZ];
14616     long time, otime;
14617
14618     /* Note: this routine must be called when the clocks are stopped
14619        or when they have *just* been set or switched; otherwise
14620        it will be off by the time since the current tick started.
14621     */
14622     if (machineWhite) {
14623         time = whiteTimeRemaining / 10;
14624         otime = blackTimeRemaining / 10;
14625     } else {
14626         time = blackTimeRemaining / 10;
14627         otime = whiteTimeRemaining / 10;
14628     }
14629     /* [HGM] translate opponent's time by time-odds factor */
14630     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14631     if (appData.debugMode) {
14632         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14633     }
14634
14635     if (time <= 0) time = 1;
14636     if (otime <= 0) otime = 1;
14637
14638     snprintf(message, MSG_SIZ, "time %ld\n", time);
14639     SendToProgram(message, cps);
14640
14641     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14642     SendToProgram(message, cps);
14643 }
14644
14645 int
14646 BoolFeature(p, name, loc, cps)
14647      char **p;
14648      char *name;
14649      int *loc;
14650      ChessProgramState *cps;
14651 {
14652   char buf[MSG_SIZ];
14653   int len = strlen(name);
14654   int val;
14655
14656   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14657     (*p) += len + 1;
14658     sscanf(*p, "%d", &val);
14659     *loc = (val != 0);
14660     while (**p && **p != ' ')
14661       (*p)++;
14662     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14663     SendToProgram(buf, cps);
14664     return TRUE;
14665   }
14666   return FALSE;
14667 }
14668
14669 int
14670 IntFeature(p, name, loc, cps)
14671      char **p;
14672      char *name;
14673      int *loc;
14674      ChessProgramState *cps;
14675 {
14676   char buf[MSG_SIZ];
14677   int len = strlen(name);
14678   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14679     (*p) += len + 1;
14680     sscanf(*p, "%d", loc);
14681     while (**p && **p != ' ') (*p)++;
14682     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14683     SendToProgram(buf, cps);
14684     return TRUE;
14685   }
14686   return FALSE;
14687 }
14688
14689 int
14690 StringFeature(p, name, loc, cps)
14691      char **p;
14692      char *name;
14693      char loc[];
14694      ChessProgramState *cps;
14695 {
14696   char buf[MSG_SIZ];
14697   int len = strlen(name);
14698   if (strncmp((*p), name, len) == 0
14699       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14700     (*p) += len + 2;
14701     sscanf(*p, "%[^\"]", loc);
14702     while (**p && **p != '\"') (*p)++;
14703     if (**p == '\"') (*p)++;
14704     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14705     SendToProgram(buf, cps);
14706     return TRUE;
14707   }
14708   return FALSE;
14709 }
14710
14711 int
14712 ParseOption(Option *opt, ChessProgramState *cps)
14713 // [HGM] options: process the string that defines an engine option, and determine
14714 // name, type, default value, and allowed value range
14715 {
14716         char *p, *q, buf[MSG_SIZ];
14717         int n, min = (-1)<<31, max = 1<<31, def;
14718
14719         if(p = strstr(opt->name, " -spin ")) {
14720             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14721             if(max < min) max = min; // enforce consistency
14722             if(def < min) def = min;
14723             if(def > max) def = max;
14724             opt->value = def;
14725             opt->min = min;
14726             opt->max = max;
14727             opt->type = Spin;
14728         } else if((p = strstr(opt->name, " -slider "))) {
14729             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14730             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14731             if(max < min) max = min; // enforce consistency
14732             if(def < min) def = min;
14733             if(def > max) def = max;
14734             opt->value = def;
14735             opt->min = min;
14736             opt->max = max;
14737             opt->type = Spin; // Slider;
14738         } else if((p = strstr(opt->name, " -string "))) {
14739             opt->textValue = p+9;
14740             opt->type = TextBox;
14741         } else if((p = strstr(opt->name, " -file "))) {
14742             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14743             opt->textValue = p+7;
14744             opt->type = FileName; // FileName;
14745         } else if((p = strstr(opt->name, " -path "))) {
14746             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14747             opt->textValue = p+7;
14748             opt->type = PathName; // PathName;
14749         } else if(p = strstr(opt->name, " -check ")) {
14750             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14751             opt->value = (def != 0);
14752             opt->type = CheckBox;
14753         } else if(p = strstr(opt->name, " -combo ")) {
14754             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14755             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14756             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14757             opt->value = n = 0;
14758             while(q = StrStr(q, " /// ")) {
14759                 n++; *q = 0;    // count choices, and null-terminate each of them
14760                 q += 5;
14761                 if(*q == '*') { // remember default, which is marked with * prefix
14762                     q++;
14763                     opt->value = n;
14764                 }
14765                 cps->comboList[cps->comboCnt++] = q;
14766             }
14767             cps->comboList[cps->comboCnt++] = NULL;
14768             opt->max = n + 1;
14769             opt->type = ComboBox;
14770         } else if(p = strstr(opt->name, " -button")) {
14771             opt->type = Button;
14772         } else if(p = strstr(opt->name, " -save")) {
14773             opt->type = SaveButton;
14774         } else return FALSE;
14775         *p = 0; // terminate option name
14776         // now look if the command-line options define a setting for this engine option.
14777         if(cps->optionSettings && cps->optionSettings[0])
14778             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14779         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14780           snprintf(buf, MSG_SIZ, "option %s", p);
14781                 if(p = strstr(buf, ",")) *p = 0;
14782                 if(q = strchr(buf, '=')) switch(opt->type) {
14783                     case ComboBox:
14784                         for(n=0; n<opt->max; n++)
14785                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14786                         break;
14787                     case TextBox:
14788                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14789                         break;
14790                     case Spin:
14791                     case CheckBox:
14792                         opt->value = atoi(q+1);
14793                     default:
14794                         break;
14795                 }
14796                 strcat(buf, "\n");
14797                 SendToProgram(buf, cps);
14798         }
14799         return TRUE;
14800 }
14801
14802 void
14803 FeatureDone(cps, val)
14804      ChessProgramState* cps;
14805      int val;
14806 {
14807   DelayedEventCallback cb = GetDelayedEvent();
14808   if ((cb == InitBackEnd3 && cps == &first) ||
14809       (cb == SettingsMenuIfReady && cps == &second) ||
14810       (cb == LoadEngine) ||
14811       (cb == TwoMachinesEventIfReady)) {
14812     CancelDelayedEvent();
14813     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14814   }
14815   cps->initDone = val;
14816 }
14817
14818 /* Parse feature command from engine */
14819 void
14820 ParseFeatures(args, cps)
14821      char* args;
14822      ChessProgramState *cps;
14823 {
14824   char *p = args;
14825   char *q;
14826   int val;
14827   char buf[MSG_SIZ];
14828
14829   for (;;) {
14830     while (*p == ' ') p++;
14831     if (*p == NULLCHAR) return;
14832
14833     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14834     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14835     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14836     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14837     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14838     if (BoolFeature(&p, "reuse", &val, cps)) {
14839       /* Engine can disable reuse, but can't enable it if user said no */
14840       if (!val) cps->reuse = FALSE;
14841       continue;
14842     }
14843     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14844     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14845       if (gameMode == TwoMachinesPlay) {
14846         DisplayTwoMachinesTitle();
14847       } else {
14848         DisplayTitle("");
14849       }
14850       continue;
14851     }
14852     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14853     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14854     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14855     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14856     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14857     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14858     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14859     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14860     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14861     if (IntFeature(&p, "done", &val, cps)) {
14862       FeatureDone(cps, val);
14863       continue;
14864     }
14865     /* Added by Tord: */
14866     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14867     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14868     /* End of additions by Tord */
14869
14870     /* [HGM] added features: */
14871     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14872     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14873     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14874     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14875     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14876     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14877     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14878         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14879           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14880             SendToProgram(buf, cps);
14881             continue;
14882         }
14883         if(cps->nrOptions >= MAX_OPTIONS) {
14884             cps->nrOptions--;
14885             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14886             DisplayError(buf, 0);
14887         }
14888         continue;
14889     }
14890     /* End of additions by HGM */
14891
14892     /* unknown feature: complain and skip */
14893     q = p;
14894     while (*q && *q != '=') q++;
14895     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14896     SendToProgram(buf, cps);
14897     p = q;
14898     if (*p == '=') {
14899       p++;
14900       if (*p == '\"') {
14901         p++;
14902         while (*p && *p != '\"') p++;
14903         if (*p == '\"') p++;
14904       } else {
14905         while (*p && *p != ' ') p++;
14906       }
14907     }
14908   }
14909
14910 }
14911
14912 void
14913 PeriodicUpdatesEvent(newState)
14914      int newState;
14915 {
14916     if (newState == appData.periodicUpdates)
14917       return;
14918
14919     appData.periodicUpdates=newState;
14920
14921     /* Display type changes, so update it now */
14922 //    DisplayAnalysis();
14923
14924     /* Get the ball rolling again... */
14925     if (newState) {
14926         AnalysisPeriodicEvent(1);
14927         StartAnalysisClock();
14928     }
14929 }
14930
14931 void
14932 PonderNextMoveEvent(newState)
14933      int newState;
14934 {
14935     if (newState == appData.ponderNextMove) return;
14936     if (gameMode == EditPosition) EditPositionDone(TRUE);
14937     if (newState) {
14938         SendToProgram("hard\n", &first);
14939         if (gameMode == TwoMachinesPlay) {
14940             SendToProgram("hard\n", &second);
14941         }
14942     } else {
14943         SendToProgram("easy\n", &first);
14944         thinkOutput[0] = NULLCHAR;
14945         if (gameMode == TwoMachinesPlay) {
14946             SendToProgram("easy\n", &second);
14947         }
14948     }
14949     appData.ponderNextMove = newState;
14950 }
14951
14952 void
14953 NewSettingEvent(option, feature, command, value)
14954      char *command;
14955      int option, value, *feature;
14956 {
14957     char buf[MSG_SIZ];
14958
14959     if (gameMode == EditPosition) EditPositionDone(TRUE);
14960     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14961     if(feature == NULL || *feature) SendToProgram(buf, &first);
14962     if (gameMode == TwoMachinesPlay) {
14963         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14964     }
14965 }
14966
14967 void
14968 ShowThinkingEvent()
14969 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14970 {
14971     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14972     int newState = appData.showThinking
14973         // [HGM] thinking: other features now need thinking output as well
14974         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14975
14976     if (oldState == newState) return;
14977     oldState = newState;
14978     if (gameMode == EditPosition) EditPositionDone(TRUE);
14979     if (oldState) {
14980         SendToProgram("post\n", &first);
14981         if (gameMode == TwoMachinesPlay) {
14982             SendToProgram("post\n", &second);
14983         }
14984     } else {
14985         SendToProgram("nopost\n", &first);
14986         thinkOutput[0] = NULLCHAR;
14987         if (gameMode == TwoMachinesPlay) {
14988             SendToProgram("nopost\n", &second);
14989         }
14990     }
14991 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14992 }
14993
14994 void
14995 AskQuestionEvent(title, question, replyPrefix, which)
14996      char *title; char *question; char *replyPrefix; char *which;
14997 {
14998   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14999   if (pr == NoProc) return;
15000   AskQuestion(title, question, replyPrefix, pr);
15001 }
15002
15003 void
15004 TypeInEvent(char firstChar)
15005 {
15006     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15007         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15008         gameMode == AnalyzeMode || gameMode == EditGame || \r
15009         gameMode == EditPosition || gameMode == IcsExamining ||\r
15010         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15011         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15012                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15013                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15014         gameMode == Training) PopUpMoveDialog(firstChar);
15015 }
15016
15017 void
15018 TypeInDoneEvent(char *move)
15019 {
15020         Board board;
15021         int n, fromX, fromY, toX, toY;
15022         char promoChar;
15023         ChessMove moveType;\r
15024
15025         // [HGM] FENedit\r
15026         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15027                 EditPositionPasteFEN(move);\r
15028                 return;\r
15029         }\r
15030         // [HGM] movenum: allow move number to be typed in any mode\r
15031         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15032           ToNrEvent(2*n-1);\r
15033           return;\r
15034         }\r
15035
15036       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15037         gameMode != Training) {\r
15038         DisplayMoveError(_("Displayed move is not current"));\r
15039       } else {\r
15040         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15041           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15042         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15043         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15044           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15045           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15046         } else {\r
15047           DisplayMoveError(_("Could not parse move"));\r
15048         }
15049       }\r
15050 }\r
15051
15052 void
15053 DisplayMove(moveNumber)
15054      int moveNumber;
15055 {
15056     char message[MSG_SIZ];
15057     char res[MSG_SIZ];
15058     char cpThinkOutput[MSG_SIZ];
15059
15060     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15061
15062     if (moveNumber == forwardMostMove - 1 ||
15063         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15064
15065         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15066
15067         if (strchr(cpThinkOutput, '\n')) {
15068             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15069         }
15070     } else {
15071         *cpThinkOutput = NULLCHAR;
15072     }
15073
15074     /* [AS] Hide thinking from human user */
15075     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15076         *cpThinkOutput = NULLCHAR;
15077         if( thinkOutput[0] != NULLCHAR ) {
15078             int i;
15079
15080             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15081                 cpThinkOutput[i] = '.';
15082             }
15083             cpThinkOutput[i] = NULLCHAR;
15084             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15085         }
15086     }
15087
15088     if (moveNumber == forwardMostMove - 1 &&
15089         gameInfo.resultDetails != NULL) {
15090         if (gameInfo.resultDetails[0] == NULLCHAR) {
15091           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15092         } else {
15093           snprintf(res, MSG_SIZ, " {%s} %s",
15094                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15095         }
15096     } else {
15097         res[0] = NULLCHAR;
15098     }
15099
15100     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15101         DisplayMessage(res, cpThinkOutput);
15102     } else {
15103       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15104                 WhiteOnMove(moveNumber) ? " " : ".. ",
15105                 parseList[moveNumber], res);
15106         DisplayMessage(message, cpThinkOutput);
15107     }
15108 }
15109
15110 void
15111 DisplayComment(moveNumber, text)
15112      int moveNumber;
15113      char *text;
15114 {
15115     char title[MSG_SIZ];
15116     char buf[8000]; // comment can be long!
15117     int score, depth;
15118
15119     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15120       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15121     } else {
15122       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15123               WhiteOnMove(moveNumber) ? " " : ".. ",
15124               parseList[moveNumber]);
15125     }
15126     // [HGM] PV info: display PV info together with (or as) comment
15127     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15128       if(text == NULL) text = "";
15129       score = pvInfoList[moveNumber].score;
15130       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15131               depth, (pvInfoList[moveNumber].time+50)/100, text);
15132       text = buf;
15133     }
15134     if (text != NULL && (appData.autoDisplayComment || commentUp))
15135         CommentPopUp(title, text);
15136 }
15137
15138 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15139  * might be busy thinking or pondering.  It can be omitted if your
15140  * gnuchess is configured to stop thinking immediately on any user
15141  * input.  However, that gnuchess feature depends on the FIONREAD
15142  * ioctl, which does not work properly on some flavors of Unix.
15143  */
15144 void
15145 Attention(cps)
15146      ChessProgramState *cps;
15147 {
15148 #if ATTENTION
15149     if (!cps->useSigint) return;
15150     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15151     switch (gameMode) {
15152       case MachinePlaysWhite:
15153       case MachinePlaysBlack:
15154       case TwoMachinesPlay:
15155       case IcsPlayingWhite:
15156       case IcsPlayingBlack:
15157       case AnalyzeMode:
15158       case AnalyzeFile:
15159         /* Skip if we know it isn't thinking */
15160         if (!cps->maybeThinking) return;
15161         if (appData.debugMode)
15162           fprintf(debugFP, "Interrupting %s\n", cps->which);
15163         InterruptChildProcess(cps->pr);
15164         cps->maybeThinking = FALSE;
15165         break;
15166       default:
15167         break;
15168     }
15169 #endif /*ATTENTION*/
15170 }
15171
15172 int
15173 CheckFlags()
15174 {
15175     if (whiteTimeRemaining <= 0) {
15176         if (!whiteFlag) {
15177             whiteFlag = TRUE;
15178             if (appData.icsActive) {
15179                 if (appData.autoCallFlag &&
15180                     gameMode == IcsPlayingBlack && !blackFlag) {
15181                   SendToICS(ics_prefix);
15182                   SendToICS("flag\n");
15183                 }
15184             } else {
15185                 if (blackFlag) {
15186                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15187                 } else {
15188                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15189                     if (appData.autoCallFlag) {
15190                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15191                         return TRUE;
15192                     }
15193                 }
15194             }
15195         }
15196     }
15197     if (blackTimeRemaining <= 0) {
15198         if (!blackFlag) {
15199             blackFlag = TRUE;
15200             if (appData.icsActive) {
15201                 if (appData.autoCallFlag &&
15202                     gameMode == IcsPlayingWhite && !whiteFlag) {
15203                   SendToICS(ics_prefix);
15204                   SendToICS("flag\n");
15205                 }
15206             } else {
15207                 if (whiteFlag) {
15208                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15209                 } else {
15210                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15211                     if (appData.autoCallFlag) {
15212                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15213                         return TRUE;
15214                     }
15215                 }
15216             }
15217         }
15218     }
15219     return FALSE;
15220 }
15221
15222 void
15223 CheckTimeControl()
15224 {
15225     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15226         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15227
15228     /*
15229      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15230      */
15231     if ( !WhiteOnMove(forwardMostMove) ) {
15232         /* White made time control */
15233         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15234         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15235         /* [HGM] time odds: correct new time quota for time odds! */
15236                                             / WhitePlayer()->timeOdds;
15237         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15238     } else {
15239         lastBlack -= blackTimeRemaining;
15240         /* Black made time control */
15241         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15242                                             / WhitePlayer()->other->timeOdds;
15243         lastWhite = whiteTimeRemaining;
15244     }
15245 }
15246
15247 void
15248 DisplayBothClocks()
15249 {
15250     int wom = gameMode == EditPosition ?
15251       !blackPlaysFirst : WhiteOnMove(currentMove);
15252     DisplayWhiteClock(whiteTimeRemaining, wom);
15253     DisplayBlackClock(blackTimeRemaining, !wom);
15254 }
15255
15256
15257 /* Timekeeping seems to be a portability nightmare.  I think everyone
15258    has ftime(), but I'm really not sure, so I'm including some ifdefs
15259    to use other calls if you don't.  Clocks will be less accurate if
15260    you have neither ftime nor gettimeofday.
15261 */
15262
15263 /* VS 2008 requires the #include outside of the function */
15264 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15265 #include <sys/timeb.h>
15266 #endif
15267
15268 /* Get the current time as a TimeMark */
15269 void
15270 GetTimeMark(tm)
15271      TimeMark *tm;
15272 {
15273 #if HAVE_GETTIMEOFDAY
15274
15275     struct timeval timeVal;
15276     struct timezone timeZone;
15277
15278     gettimeofday(&timeVal, &timeZone);
15279     tm->sec = (long) timeVal.tv_sec;
15280     tm->ms = (int) (timeVal.tv_usec / 1000L);
15281
15282 #else /*!HAVE_GETTIMEOFDAY*/
15283 #if HAVE_FTIME
15284
15285 // include <sys/timeb.h> / moved to just above start of function
15286     struct timeb timeB;
15287
15288     ftime(&timeB);
15289     tm->sec = (long) timeB.time;
15290     tm->ms = (int) timeB.millitm;
15291
15292 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15293     tm->sec = (long) time(NULL);
15294     tm->ms = 0;
15295 #endif
15296 #endif
15297 }
15298
15299 /* Return the difference in milliseconds between two
15300    time marks.  We assume the difference will fit in a long!
15301 */
15302 long
15303 SubtractTimeMarks(tm2, tm1)
15304      TimeMark *tm2, *tm1;
15305 {
15306     return 1000L*(tm2->sec - tm1->sec) +
15307            (long) (tm2->ms - tm1->ms);
15308 }
15309
15310
15311 /*
15312  * Code to manage the game clocks.
15313  *
15314  * In tournament play, black starts the clock and then white makes a move.
15315  * We give the human user a slight advantage if he is playing white---the
15316  * clocks don't run until he makes his first move, so it takes zero time.
15317  * Also, we don't account for network lag, so we could get out of sync
15318  * with GNU Chess's clock -- but then, referees are always right.
15319  */
15320
15321 static TimeMark tickStartTM;
15322 static long intendedTickLength;
15323
15324 long
15325 NextTickLength(timeRemaining)
15326      long timeRemaining;
15327 {
15328     long nominalTickLength, nextTickLength;
15329
15330     if (timeRemaining > 0L && timeRemaining <= 10000L)
15331       nominalTickLength = 100L;
15332     else
15333       nominalTickLength = 1000L;
15334     nextTickLength = timeRemaining % nominalTickLength;
15335     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15336
15337     return nextTickLength;
15338 }
15339
15340 /* Adjust clock one minute up or down */
15341 void
15342 AdjustClock(Boolean which, int dir)
15343 {
15344     if(which) blackTimeRemaining += 60000*dir;
15345     else      whiteTimeRemaining += 60000*dir;
15346     DisplayBothClocks();
15347 }
15348
15349 /* Stop clocks and reset to a fresh time control */
15350 void
15351 ResetClocks()
15352 {
15353     (void) StopClockTimer();
15354     if (appData.icsActive) {
15355         whiteTimeRemaining = blackTimeRemaining = 0;
15356     } else if (searchTime) {
15357         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15358         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15359     } else { /* [HGM] correct new time quote for time odds */
15360         whiteTC = blackTC = fullTimeControlString;
15361         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15362         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15363     }
15364     if (whiteFlag || blackFlag) {
15365         DisplayTitle("");
15366         whiteFlag = blackFlag = FALSE;
15367     }
15368     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15369     DisplayBothClocks();
15370 }
15371
15372 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15373
15374 /* Decrement running clock by amount of time that has passed */
15375 void
15376 DecrementClocks()
15377 {
15378     long timeRemaining;
15379     long lastTickLength, fudge;
15380     TimeMark now;
15381
15382     if (!appData.clockMode) return;
15383     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15384
15385     GetTimeMark(&now);
15386
15387     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15388
15389     /* Fudge if we woke up a little too soon */
15390     fudge = intendedTickLength - lastTickLength;
15391     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15392
15393     if (WhiteOnMove(forwardMostMove)) {
15394         if(whiteNPS >= 0) lastTickLength = 0;
15395         timeRemaining = whiteTimeRemaining -= lastTickLength;
15396         if(timeRemaining < 0 && !appData.icsActive) {
15397             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15398             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15399                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15400                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15401             }
15402         }
15403         DisplayWhiteClock(whiteTimeRemaining - fudge,
15404                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15405     } else {
15406         if(blackNPS >= 0) lastTickLength = 0;
15407         timeRemaining = blackTimeRemaining -= lastTickLength;
15408         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15409             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15410             if(suddenDeath) {
15411                 blackStartMove = forwardMostMove;
15412                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15413             }
15414         }
15415         DisplayBlackClock(blackTimeRemaining - fudge,
15416                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15417     }
15418     if (CheckFlags()) return;
15419
15420     tickStartTM = now;
15421     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15422     StartClockTimer(intendedTickLength);
15423
15424     /* if the time remaining has fallen below the alarm threshold, sound the
15425      * alarm. if the alarm has sounded and (due to a takeback or time control
15426      * with increment) the time remaining has increased to a level above the
15427      * threshold, reset the alarm so it can sound again.
15428      */
15429
15430     if (appData.icsActive && appData.icsAlarm) {
15431
15432         /* make sure we are dealing with the user's clock */
15433         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15434                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15435            )) return;
15436
15437         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15438             alarmSounded = FALSE;
15439         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15440             PlayAlarmSound();
15441             alarmSounded = TRUE;
15442         }
15443     }
15444 }
15445
15446
15447 /* A player has just moved, so stop the previously running
15448    clock and (if in clock mode) start the other one.
15449    We redisplay both clocks in case we're in ICS mode, because
15450    ICS gives us an update to both clocks after every move.
15451    Note that this routine is called *after* forwardMostMove
15452    is updated, so the last fractional tick must be subtracted
15453    from the color that is *not* on move now.
15454 */
15455 void
15456 SwitchClocks(int newMoveNr)
15457 {
15458     long lastTickLength;
15459     TimeMark now;
15460     int flagged = FALSE;
15461
15462     GetTimeMark(&now);
15463
15464     if (StopClockTimer() && appData.clockMode) {
15465         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15466         if (!WhiteOnMove(forwardMostMove)) {
15467             if(blackNPS >= 0) lastTickLength = 0;
15468             blackTimeRemaining -= lastTickLength;
15469            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15470 //         if(pvInfoList[forwardMostMove].time == -1)
15471                  pvInfoList[forwardMostMove].time =               // use GUI time
15472                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15473         } else {
15474            if(whiteNPS >= 0) lastTickLength = 0;
15475            whiteTimeRemaining -= lastTickLength;
15476            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15477 //         if(pvInfoList[forwardMostMove].time == -1)
15478                  pvInfoList[forwardMostMove].time =
15479                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15480         }
15481         flagged = CheckFlags();
15482     }
15483     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15484     CheckTimeControl();
15485
15486     if (flagged || !appData.clockMode) return;
15487
15488     switch (gameMode) {
15489       case MachinePlaysBlack:
15490       case MachinePlaysWhite:
15491       case BeginningOfGame:
15492         if (pausing) return;
15493         break;
15494
15495       case EditGame:
15496       case PlayFromGameFile:
15497       case IcsExamining:
15498         return;
15499
15500       default:
15501         break;
15502     }
15503
15504     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15505         if(WhiteOnMove(forwardMostMove))
15506              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15507         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15508     }
15509
15510     tickStartTM = now;
15511     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15512       whiteTimeRemaining : blackTimeRemaining);
15513     StartClockTimer(intendedTickLength);
15514 }
15515
15516
15517 /* Stop both clocks */
15518 void
15519 StopClocks()
15520 {
15521     long lastTickLength;
15522     TimeMark now;
15523
15524     if (!StopClockTimer()) return;
15525     if (!appData.clockMode) return;
15526
15527     GetTimeMark(&now);
15528
15529     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15530     if (WhiteOnMove(forwardMostMove)) {
15531         if(whiteNPS >= 0) lastTickLength = 0;
15532         whiteTimeRemaining -= lastTickLength;
15533         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15534     } else {
15535         if(blackNPS >= 0) lastTickLength = 0;
15536         blackTimeRemaining -= lastTickLength;
15537         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15538     }
15539     CheckFlags();
15540 }
15541
15542 /* Start clock of player on move.  Time may have been reset, so
15543    if clock is already running, stop and restart it. */
15544 void
15545 StartClocks()
15546 {
15547     (void) StopClockTimer(); /* in case it was running already */
15548     DisplayBothClocks();
15549     if (CheckFlags()) return;
15550
15551     if (!appData.clockMode) return;
15552     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15553
15554     GetTimeMark(&tickStartTM);
15555     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15556       whiteTimeRemaining : blackTimeRemaining);
15557
15558    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15559     whiteNPS = blackNPS = -1;
15560     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15561        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15562         whiteNPS = first.nps;
15563     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15564        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15565         blackNPS = first.nps;
15566     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15567         whiteNPS = second.nps;
15568     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15569         blackNPS = second.nps;
15570     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15571
15572     StartClockTimer(intendedTickLength);
15573 }
15574
15575 char *
15576 TimeString(ms)
15577      long ms;
15578 {
15579     long second, minute, hour, day;
15580     char *sign = "";
15581     static char buf[32];
15582
15583     if (ms > 0 && ms <= 9900) {
15584       /* convert milliseconds to tenths, rounding up */
15585       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15586
15587       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15588       return buf;
15589     }
15590
15591     /* convert milliseconds to seconds, rounding up */
15592     /* use floating point to avoid strangeness of integer division
15593        with negative dividends on many machines */
15594     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15595
15596     if (second < 0) {
15597         sign = "-";
15598         second = -second;
15599     }
15600
15601     day = second / (60 * 60 * 24);
15602     second = second % (60 * 60 * 24);
15603     hour = second / (60 * 60);
15604     second = second % (60 * 60);
15605     minute = second / 60;
15606     second = second % 60;
15607
15608     if (day > 0)
15609       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15610               sign, day, hour, minute, second);
15611     else if (hour > 0)
15612       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15613     else
15614       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15615
15616     return buf;
15617 }
15618
15619
15620 /*
15621  * This is necessary because some C libraries aren't ANSI C compliant yet.
15622  */
15623 char *
15624 StrStr(string, match)
15625      char *string, *match;
15626 {
15627     int i, length;
15628
15629     length = strlen(match);
15630
15631     for (i = strlen(string) - length; i >= 0; i--, string++)
15632       if (!strncmp(match, string, length))
15633         return string;
15634
15635     return NULL;
15636 }
15637
15638 char *
15639 StrCaseStr(string, match)
15640      char *string, *match;
15641 {
15642     int i, j, length;
15643
15644     length = strlen(match);
15645
15646     for (i = strlen(string) - length; i >= 0; i--, string++) {
15647         for (j = 0; j < length; j++) {
15648             if (ToLower(match[j]) != ToLower(string[j]))
15649               break;
15650         }
15651         if (j == length) return string;
15652     }
15653
15654     return NULL;
15655 }
15656
15657 #ifndef _amigados
15658 int
15659 StrCaseCmp(s1, s2)
15660      char *s1, *s2;
15661 {
15662     char c1, c2;
15663
15664     for (;;) {
15665         c1 = ToLower(*s1++);
15666         c2 = ToLower(*s2++);
15667         if (c1 > c2) return 1;
15668         if (c1 < c2) return -1;
15669         if (c1 == NULLCHAR) return 0;
15670     }
15671 }
15672
15673
15674 int
15675 ToLower(c)
15676      int c;
15677 {
15678     return isupper(c) ? tolower(c) : c;
15679 }
15680
15681
15682 int
15683 ToUpper(c)
15684      int c;
15685 {
15686     return islower(c) ? toupper(c) : c;
15687 }
15688 #endif /* !_amigados    */
15689
15690 char *
15691 StrSave(s)
15692      char *s;
15693 {
15694   char *ret;
15695
15696   if ((ret = (char *) malloc(strlen(s) + 1)))
15697     {
15698       safeStrCpy(ret, s, strlen(s)+1);
15699     }
15700   return ret;
15701 }
15702
15703 char *
15704 StrSavePtr(s, savePtr)
15705      char *s, **savePtr;
15706 {
15707     if (*savePtr) {
15708         free(*savePtr);
15709     }
15710     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15711       safeStrCpy(*savePtr, s, strlen(s)+1);
15712     }
15713     return(*savePtr);
15714 }
15715
15716 char *
15717 PGNDate()
15718 {
15719     time_t clock;
15720     struct tm *tm;
15721     char buf[MSG_SIZ];
15722
15723     clock = time((time_t *)NULL);
15724     tm = localtime(&clock);
15725     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15726             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15727     return StrSave(buf);
15728 }
15729
15730
15731 char *
15732 PositionToFEN(move, overrideCastling)
15733      int move;
15734      char *overrideCastling;
15735 {
15736     int i, j, fromX, fromY, toX, toY;
15737     int whiteToPlay;
15738     char buf[128];
15739     char *p, *q;
15740     int emptycount;
15741     ChessSquare piece;
15742
15743     whiteToPlay = (gameMode == EditPosition) ?
15744       !blackPlaysFirst : (move % 2 == 0);
15745     p = buf;
15746
15747     /* Piece placement data */
15748     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15749         emptycount = 0;
15750         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15751             if (boards[move][i][j] == EmptySquare) {
15752                 emptycount++;
15753             } else { ChessSquare piece = boards[move][i][j];
15754                 if (emptycount > 0) {
15755                     if(emptycount<10) /* [HGM] can be >= 10 */
15756                         *p++ = '0' + emptycount;
15757                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15758                     emptycount = 0;
15759                 }
15760                 if(PieceToChar(piece) == '+') {
15761                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15762                     *p++ = '+';
15763                     piece = (ChessSquare)(DEMOTED piece);
15764                 }
15765                 *p++ = PieceToChar(piece);
15766                 if(p[-1] == '~') {
15767                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15768                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15769                     *p++ = '~';
15770                 }
15771             }
15772         }
15773         if (emptycount > 0) {
15774             if(emptycount<10) /* [HGM] can be >= 10 */
15775                 *p++ = '0' + emptycount;
15776             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15777             emptycount = 0;
15778         }
15779         *p++ = '/';
15780     }
15781     *(p - 1) = ' ';
15782
15783     /* [HGM] print Crazyhouse or Shogi holdings */
15784     if( gameInfo.holdingsWidth ) {
15785         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15786         q = p;
15787         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15788             piece = boards[move][i][BOARD_WIDTH-1];
15789             if( piece != EmptySquare )
15790               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15791                   *p++ = PieceToChar(piece);
15792         }
15793         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15794             piece = boards[move][BOARD_HEIGHT-i-1][0];
15795             if( piece != EmptySquare )
15796               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15797                   *p++ = PieceToChar(piece);
15798         }
15799
15800         if( q == p ) *p++ = '-';
15801         *p++ = ']';
15802         *p++ = ' ';
15803     }
15804
15805     /* Active color */
15806     *p++ = whiteToPlay ? 'w' : 'b';
15807     *p++ = ' ';
15808
15809   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15810     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15811   } else {
15812   if(nrCastlingRights) {
15813      q = p;
15814      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15815        /* [HGM] write directly from rights */
15816            if(boards[move][CASTLING][2] != NoRights &&
15817               boards[move][CASTLING][0] != NoRights   )
15818                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15819            if(boards[move][CASTLING][2] != NoRights &&
15820               boards[move][CASTLING][1] != NoRights   )
15821                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15822            if(boards[move][CASTLING][5] != NoRights &&
15823               boards[move][CASTLING][3] != NoRights   )
15824                 *p++ = boards[move][CASTLING][3] + AAA;
15825            if(boards[move][CASTLING][5] != NoRights &&
15826               boards[move][CASTLING][4] != NoRights   )
15827                 *p++ = boards[move][CASTLING][4] + AAA;
15828      } else {
15829
15830         /* [HGM] write true castling rights */
15831         if( nrCastlingRights == 6 ) {
15832             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15833                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15834             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15835                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15836             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15837                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15838             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15839                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15840         }
15841      }
15842      if (q == p) *p++ = '-'; /* No castling rights */
15843      *p++ = ' ';
15844   }
15845
15846   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15847      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15848     /* En passant target square */
15849     if (move > backwardMostMove) {
15850         fromX = moveList[move - 1][0] - AAA;
15851         fromY = moveList[move - 1][1] - ONE;
15852         toX = moveList[move - 1][2] - AAA;
15853         toY = moveList[move - 1][3] - ONE;
15854         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15855             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15856             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15857             fromX == toX) {
15858             /* 2-square pawn move just happened */
15859             *p++ = toX + AAA;
15860             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15861         } else {
15862             *p++ = '-';
15863         }
15864     } else if(move == backwardMostMove) {
15865         // [HGM] perhaps we should always do it like this, and forget the above?
15866         if((signed char)boards[move][EP_STATUS] >= 0) {
15867             *p++ = boards[move][EP_STATUS] + AAA;
15868             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15869         } else {
15870             *p++ = '-';
15871         }
15872     } else {
15873         *p++ = '-';
15874     }
15875     *p++ = ' ';
15876   }
15877   }
15878
15879     /* [HGM] find reversible plies */
15880     {   int i = 0, j=move;
15881
15882         if (appData.debugMode) { int k;
15883             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15884             for(k=backwardMostMove; k<=forwardMostMove; k++)
15885                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15886
15887         }
15888
15889         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15890         if( j == backwardMostMove ) i += initialRulePlies;
15891         sprintf(p, "%d ", i);
15892         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15893     }
15894     /* Fullmove number */
15895     sprintf(p, "%d", (move / 2) + 1);
15896
15897     return StrSave(buf);
15898 }
15899
15900 Boolean
15901 ParseFEN(board, blackPlaysFirst, fen)
15902     Board board;
15903      int *blackPlaysFirst;
15904      char *fen;
15905 {
15906     int i, j;
15907     char *p, c;
15908     int emptycount;
15909     ChessSquare piece;
15910
15911     p = fen;
15912
15913     /* [HGM] by default clear Crazyhouse holdings, if present */
15914     if(gameInfo.holdingsWidth) {
15915        for(i=0; i<BOARD_HEIGHT; i++) {
15916            board[i][0]             = EmptySquare; /* black holdings */
15917            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15918            board[i][1]             = (ChessSquare) 0; /* black counts */
15919            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15920        }
15921     }
15922
15923     /* Piece placement data */
15924     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15925         j = 0;
15926         for (;;) {
15927             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15928                 if (*p == '/') p++;
15929                 emptycount = gameInfo.boardWidth - j;
15930                 while (emptycount--)
15931                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15932                 break;
15933 #if(BOARD_FILES >= 10)
15934             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15935                 p++; emptycount=10;
15936                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15937                 while (emptycount--)
15938                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15939 #endif
15940             } else if (isdigit(*p)) {
15941                 emptycount = *p++ - '0';
15942                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15943                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15944                 while (emptycount--)
15945                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15946             } else if (*p == '+' || isalpha(*p)) {
15947                 if (j >= gameInfo.boardWidth) return FALSE;
15948                 if(*p=='+') {
15949                     piece = CharToPiece(*++p);
15950                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15951                     piece = (ChessSquare) (PROMOTED piece ); p++;
15952                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15953                 } else piece = CharToPiece(*p++);
15954
15955                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15956                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15957                     piece = (ChessSquare) (PROMOTED piece);
15958                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15959                     p++;
15960                 }
15961                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15962             } else {
15963                 return FALSE;
15964             }
15965         }
15966     }
15967     while (*p == '/' || *p == ' ') p++;
15968
15969     /* [HGM] look for Crazyhouse holdings here */
15970     while(*p==' ') p++;
15971     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15972         if(*p == '[') p++;
15973         if(*p == '-' ) p++; /* empty holdings */ else {
15974             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15975             /* if we would allow FEN reading to set board size, we would   */
15976             /* have to add holdings and shift the board read so far here   */
15977             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15978                 p++;
15979                 if((int) piece >= (int) BlackPawn ) {
15980                     i = (int)piece - (int)BlackPawn;
15981                     i = PieceToNumber((ChessSquare)i);
15982                     if( i >= gameInfo.holdingsSize ) return FALSE;
15983                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15984                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15985                 } else {
15986                     i = (int)piece - (int)WhitePawn;
15987                     i = PieceToNumber((ChessSquare)i);
15988                     if( i >= gameInfo.holdingsSize ) return FALSE;
15989                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15990                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15991                 }
15992             }
15993         }
15994         if(*p == ']') p++;
15995     }
15996
15997     while(*p == ' ') p++;
15998
15999     /* Active color */
16000     c = *p++;
16001     if(appData.colorNickNames) {
16002       if( c == appData.colorNickNames[0] ) c = 'w'; else
16003       if( c == appData.colorNickNames[1] ) c = 'b';
16004     }
16005     switch (c) {
16006       case 'w':
16007         *blackPlaysFirst = FALSE;
16008         break;
16009       case 'b':
16010         *blackPlaysFirst = TRUE;
16011         break;
16012       default:
16013         return FALSE;
16014     }
16015
16016     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16017     /* return the extra info in global variiables             */
16018
16019     /* set defaults in case FEN is incomplete */
16020     board[EP_STATUS] = EP_UNKNOWN;
16021     for(i=0; i<nrCastlingRights; i++ ) {
16022         board[CASTLING][i] =
16023             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16024     }   /* assume possible unless obviously impossible */
16025     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16026     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16027     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16028                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16029     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16030     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16031     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16032                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16033     FENrulePlies = 0;
16034
16035     while(*p==' ') p++;
16036     if(nrCastlingRights) {
16037       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16038           /* castling indicator present, so default becomes no castlings */
16039           for(i=0; i<nrCastlingRights; i++ ) {
16040                  board[CASTLING][i] = NoRights;
16041           }
16042       }
16043       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16044              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16045              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16046              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16047         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16048
16049         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16050             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16051             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16052         }
16053         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16054             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16055         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16056                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16057         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16058                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16059         switch(c) {
16060           case'K':
16061               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16062               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16063               board[CASTLING][2] = whiteKingFile;
16064               break;
16065           case'Q':
16066               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16067               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16068               board[CASTLING][2] = whiteKingFile;
16069               break;
16070           case'k':
16071               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16072               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16073               board[CASTLING][5] = blackKingFile;
16074               break;
16075           case'q':
16076               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16077               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16078               board[CASTLING][5] = blackKingFile;
16079           case '-':
16080               break;
16081           default: /* FRC castlings */
16082               if(c >= 'a') { /* black rights */
16083                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16084                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16085                   if(i == BOARD_RGHT) break;
16086                   board[CASTLING][5] = i;
16087                   c -= AAA;
16088                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16089                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16090                   if(c > i)
16091                       board[CASTLING][3] = c;
16092                   else
16093                       board[CASTLING][4] = c;
16094               } else { /* white rights */
16095                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16096                     if(board[0][i] == WhiteKing) break;
16097                   if(i == BOARD_RGHT) break;
16098                   board[CASTLING][2] = i;
16099                   c -= AAA - 'a' + 'A';
16100                   if(board[0][c] >= WhiteKing) break;
16101                   if(c > i)
16102                       board[CASTLING][0] = c;
16103                   else
16104                       board[CASTLING][1] = c;
16105               }
16106         }
16107       }
16108       for(i=0; i<nrCastlingRights; i++)
16109         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16110     if (appData.debugMode) {
16111         fprintf(debugFP, "FEN castling rights:");
16112         for(i=0; i<nrCastlingRights; i++)
16113         fprintf(debugFP, " %d", board[CASTLING][i]);
16114         fprintf(debugFP, "\n");
16115     }
16116
16117       while(*p==' ') p++;
16118     }
16119
16120     /* read e.p. field in games that know e.p. capture */
16121     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16122        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16123       if(*p=='-') {
16124         p++; board[EP_STATUS] = EP_NONE;
16125       } else {
16126          char c = *p++ - AAA;
16127
16128          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16129          if(*p >= '0' && *p <='9') p++;
16130          board[EP_STATUS] = c;
16131       }
16132     }
16133
16134
16135     if(sscanf(p, "%d", &i) == 1) {
16136         FENrulePlies = i; /* 50-move ply counter */
16137         /* (The move number is still ignored)    */
16138     }
16139
16140     return TRUE;
16141 }
16142
16143 void
16144 EditPositionPasteFEN(char *fen)
16145 {
16146   if (fen != NULL) {
16147     Board initial_position;
16148
16149     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16150       DisplayError(_("Bad FEN position in clipboard"), 0);
16151       return ;
16152     } else {
16153       int savedBlackPlaysFirst = blackPlaysFirst;
16154       EditPositionEvent();
16155       blackPlaysFirst = savedBlackPlaysFirst;
16156       CopyBoard(boards[0], initial_position);
16157       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16158       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16159       DisplayBothClocks();
16160       DrawPosition(FALSE, boards[currentMove]);
16161     }
16162   }
16163 }
16164
16165 static char cseq[12] = "\\   ";
16166
16167 Boolean set_cont_sequence(char *new_seq)
16168 {
16169     int len;
16170     Boolean ret;
16171
16172     // handle bad attempts to set the sequence
16173         if (!new_seq)
16174                 return 0; // acceptable error - no debug
16175
16176     len = strlen(new_seq);
16177     ret = (len > 0) && (len < sizeof(cseq));
16178     if (ret)
16179       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16180     else if (appData.debugMode)
16181       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16182     return ret;
16183 }
16184
16185 /*
16186     reformat a source message so words don't cross the width boundary.  internal
16187     newlines are not removed.  returns the wrapped size (no null character unless
16188     included in source message).  If dest is NULL, only calculate the size required
16189     for the dest buffer.  lp argument indicats line position upon entry, and it's
16190     passed back upon exit.
16191 */
16192 int wrap(char *dest, char *src, int count, int width, int *lp)
16193 {
16194     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16195
16196     cseq_len = strlen(cseq);
16197     old_line = line = *lp;
16198     ansi = len = clen = 0;
16199
16200     for (i=0; i < count; i++)
16201     {
16202         if (src[i] == '\033')
16203             ansi = 1;
16204
16205         // if we hit the width, back up
16206         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16207         {
16208             // store i & len in case the word is too long
16209             old_i = i, old_len = len;
16210
16211             // find the end of the last word
16212             while (i && src[i] != ' ' && src[i] != '\n')
16213             {
16214                 i--;
16215                 len--;
16216             }
16217
16218             // word too long?  restore i & len before splitting it
16219             if ((old_i-i+clen) >= width)
16220             {
16221                 i = old_i;
16222                 len = old_len;
16223             }
16224
16225             // extra space?
16226             if (i && src[i-1] == ' ')
16227                 len--;
16228
16229             if (src[i] != ' ' && src[i] != '\n')
16230             {
16231                 i--;
16232                 if (len)
16233                     len--;
16234             }
16235
16236             // now append the newline and continuation sequence
16237             if (dest)
16238                 dest[len] = '\n';
16239             len++;
16240             if (dest)
16241                 strncpy(dest+len, cseq, cseq_len);
16242             len += cseq_len;
16243             line = cseq_len;
16244             clen = cseq_len;
16245             continue;
16246         }
16247
16248         if (dest)
16249             dest[len] = src[i];
16250         len++;
16251         if (!ansi)
16252             line++;
16253         if (src[i] == '\n')
16254             line = 0;
16255         if (src[i] == 'm')
16256             ansi = 0;
16257     }
16258     if (dest && appData.debugMode)
16259     {
16260         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16261             count, width, line, len, *lp);
16262         show_bytes(debugFP, src, count);
16263         fprintf(debugFP, "\ndest: ");
16264         show_bytes(debugFP, dest, len);
16265         fprintf(debugFP, "\n");
16266     }
16267     *lp = dest ? line : old_line;
16268
16269     return len;
16270 }
16271
16272 // [HGM] vari: routines for shelving variations
16273
16274 void
16275 PushInner(int firstMove, int lastMove)
16276 {
16277         int i, j, nrMoves = lastMove - firstMove;
16278
16279         // push current tail of game on stack
16280         savedResult[storedGames] = gameInfo.result;
16281         savedDetails[storedGames] = gameInfo.resultDetails;
16282         gameInfo.resultDetails = NULL;
16283         savedFirst[storedGames] = firstMove;
16284         savedLast [storedGames] = lastMove;
16285         savedFramePtr[storedGames] = framePtr;
16286         framePtr -= nrMoves; // reserve space for the boards
16287         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16288             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16289             for(j=0; j<MOVE_LEN; j++)
16290                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16291             for(j=0; j<2*MOVE_LEN; j++)
16292                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16293             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16294             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16295             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16296             pvInfoList[firstMove+i-1].depth = 0;
16297             commentList[framePtr+i] = commentList[firstMove+i];
16298             commentList[firstMove+i] = NULL;
16299         }
16300
16301         storedGames++;
16302         forwardMostMove = firstMove; // truncate game so we can start variation
16303 }
16304
16305 void
16306 PushTail(int firstMove, int lastMove)
16307 {
16308         if(appData.icsActive) { // only in local mode
16309                 forwardMostMove = currentMove; // mimic old ICS behavior
16310                 return;
16311         }
16312         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16313
16314         PushInner(firstMove, lastMove);
16315         if(storedGames == 1) GreyRevert(FALSE);
16316 }
16317
16318 void
16319 PopInner(Boolean annotate)
16320 {
16321         int i, j, nrMoves;
16322         char buf[8000], moveBuf[20];
16323
16324         storedGames--;
16325         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16326         nrMoves = savedLast[storedGames] - currentMove;
16327         if(annotate) {
16328                 int cnt = 10;
16329                 if(!WhiteOnMove(currentMove))
16330                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16331                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16332                 for(i=currentMove; i<forwardMostMove; i++) {
16333                         if(WhiteOnMove(i))
16334                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16335                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16336                         strcat(buf, moveBuf);
16337                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16338                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16339                 }
16340                 strcat(buf, ")");
16341         }
16342         for(i=1; i<=nrMoves; i++) { // copy last variation back
16343             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16344             for(j=0; j<MOVE_LEN; j++)
16345                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16346             for(j=0; j<2*MOVE_LEN; j++)
16347                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16348             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16349             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16350             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16351             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16352             commentList[currentMove+i] = commentList[framePtr+i];
16353             commentList[framePtr+i] = NULL;
16354         }
16355         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16356         framePtr = savedFramePtr[storedGames];
16357         gameInfo.result = savedResult[storedGames];
16358         if(gameInfo.resultDetails != NULL) {
16359             free(gameInfo.resultDetails);
16360       }
16361         gameInfo.resultDetails = savedDetails[storedGames];
16362         forwardMostMove = currentMove + nrMoves;
16363 }
16364
16365 Boolean
16366 PopTail(Boolean annotate)
16367 {
16368         if(appData.icsActive) return FALSE; // only in local mode
16369         if(!storedGames) return FALSE; // sanity
16370         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16371
16372         PopInner(annotate);
16373
16374         if(storedGames == 0) GreyRevert(TRUE);
16375         return TRUE;
16376 }
16377
16378 void
16379 CleanupTail()
16380 {       // remove all shelved variations
16381         int i;
16382         for(i=0; i<storedGames; i++) {
16383             if(savedDetails[i])
16384                 free(savedDetails[i]);
16385             savedDetails[i] = NULL;
16386         }
16387         for(i=framePtr; i<MAX_MOVES; i++) {
16388                 if(commentList[i]) free(commentList[i]);
16389                 commentList[i] = NULL;
16390         }
16391         framePtr = MAX_MOVES-1;
16392         storedGames = 0;
16393 }
16394
16395 void
16396 LoadVariation(int index, char *text)
16397 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16398         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16399         int level = 0, move;
16400
16401         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16402         // first find outermost bracketing variation
16403         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16404             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16405                 if(*p == '{') wait = '}'; else
16406                 if(*p == '[') wait = ']'; else
16407                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16408                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16409             }
16410             if(*p == wait) wait = NULLCHAR; // closing ]} found
16411             p++;
16412         }
16413         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16414         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16415         end[1] = NULLCHAR; // clip off comment beyond variation
16416         ToNrEvent(currentMove-1);
16417         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16418         // kludge: use ParsePV() to append variation to game
16419         move = currentMove;
16420         ParsePV(start, TRUE, TRUE);
16421         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16422         ClearPremoveHighlights();
16423         CommentPopDown();
16424         ToNrEvent(currentMove+1);
16425 }
16426