Let XBoard propose name of tourney file
[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];
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(params[0]) {
900         snprintf(command, MSG_SIZ, "%s %s", p, params);
901         p = command;
902     }
903     appData.chessProgram[i] = strdup(p);
904     appData.isUCI[i] = isUCI;
905     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906     appData.hasOwnBookUCI[i] = hasBook;
907     if(!nickName[0]) useNick = FALSE;
908     if(useNick) ASSIGN(appData.pgnName[i], nickName);
909     if(addToList) {
910         int len;
911         q = firstChessProgramNames;
912         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
913         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
914                         useNick ? " -fn \"" : "",
915                         useNick ? nickName : "",
916                         useNick ? "\"" : "",
917                         v1 ? " -firstProtocolVersion 1" : "",
918                         hasBook ? "" : " -fNoOwnBookUCI",
919                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
920                         storeVariant ? " -variant " : "",
921                         storeVariant ? VariantName(gameInfo.variant) : "");
922         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
923         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
924         if(q)   free(q);
925     }
926     ReplaceEngine(cps, i);
927 }
928
929 void
930 InitTimeControls()
931 {
932     int matched, min, sec;
933     /*
934      * Parse timeControl resource
935      */
936     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
937                           appData.movesPerSession)) {
938         char buf[MSG_SIZ];
939         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
940         DisplayFatalError(buf, 0, 2);
941     }
942
943     /*
944      * Parse searchTime resource
945      */
946     if (*appData.searchTime != NULLCHAR) {
947         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
948         if (matched == 1) {
949             searchTime = min * 60;
950         } else if (matched == 2) {
951             searchTime = min * 60 + sec;
952         } else {
953             char buf[MSG_SIZ];
954             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
955             DisplayFatalError(buf, 0, 2);
956         }
957     }
958 }
959
960 void
961 InitBackEnd1()
962 {
963
964     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
965     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
966
967     GetTimeMark(&programStartTime);
968     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
969     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
970
971     ClearProgramStats();
972     programStats.ok_to_send = 1;
973     programStats.seen_stat = 0;
974
975     /*
976      * Initialize game list
977      */
978     ListNew(&gameList);
979
980
981     /*
982      * Internet chess server status
983      */
984     if (appData.icsActive) {
985         appData.matchMode = FALSE;
986         appData.matchGames = 0;
987 #if ZIPPY
988         appData.noChessProgram = !appData.zippyPlay;
989 #else
990         appData.zippyPlay = FALSE;
991         appData.zippyTalk = FALSE;
992         appData.noChessProgram = TRUE;
993 #endif
994         if (*appData.icsHelper != NULLCHAR) {
995             appData.useTelnet = TRUE;
996             appData.telnetProgram = appData.icsHelper;
997         }
998     } else {
999         appData.zippyTalk = appData.zippyPlay = FALSE;
1000     }
1001
1002     /* [AS] Initialize pv info list [HGM] and game state */
1003     {
1004         int i, j;
1005
1006         for( i=0; i<=framePtr; i++ ) {
1007             pvInfoList[i].depth = -1;
1008             boards[i][EP_STATUS] = EP_NONE;
1009             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1010         }
1011     }
1012
1013     InitTimeControls();
1014
1015     /* [AS] Adjudication threshold */
1016     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1017
1018     InitEngine(&first, 0);
1019     InitEngine(&second, 1);
1020     CommonEngineInit();
1021
1022     pairing.which = "pairing"; // pairing engine
1023     pairing.pr = NoProc;
1024     pairing.isr = NULL;
1025     pairing.program = appData.pairingEngine;
1026     pairing.host = "localhost";
1027     pairing.dir = ".";
1028
1029     if (appData.icsActive) {
1030         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1031     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1032         appData.clockMode = FALSE;
1033         first.sendTime = second.sendTime = 0;
1034     }
1035
1036 #if ZIPPY
1037     /* Override some settings from environment variables, for backward
1038        compatibility.  Unfortunately it's not feasible to have the env
1039        vars just set defaults, at least in xboard.  Ugh.
1040     */
1041     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1042       ZippyInit();
1043     }
1044 #endif
1045
1046     if (!appData.icsActive) {
1047       char buf[MSG_SIZ];
1048       int len;
1049
1050       /* Check for variants that are supported only in ICS mode,
1051          or not at all.  Some that are accepted here nevertheless
1052          have bugs; see comments below.
1053       */
1054       VariantClass variant = StringToVariant(appData.variant);
1055       switch (variant) {
1056       case VariantBughouse:     /* need four players and two boards */
1057       case VariantKriegspiel:   /* need to hide pieces and move details */
1058         /* case VariantFischeRandom: (Fabien: moved below) */
1059         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1060         if( (len > MSG_SIZ) && appData.debugMode )
1061           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1062
1063         DisplayFatalError(buf, 0, 2);
1064         return;
1065
1066       case VariantUnknown:
1067       case VariantLoadable:
1068       case Variant29:
1069       case Variant30:
1070       case Variant31:
1071       case Variant32:
1072       case Variant33:
1073       case Variant34:
1074       case Variant35:
1075       case Variant36:
1076       default:
1077         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1078         if( (len > MSG_SIZ) && appData.debugMode )
1079           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1080
1081         DisplayFatalError(buf, 0, 2);
1082         return;
1083
1084       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1085       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1086       case VariantGothic:     /* [HGM] should work */
1087       case VariantCapablanca: /* [HGM] should work */
1088       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1089       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1090       case VariantKnightmate: /* [HGM] should work */
1091       case VariantCylinder:   /* [HGM] untested */
1092       case VariantFalcon:     /* [HGM] untested */
1093       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1094                                  offboard interposition not understood */
1095       case VariantNormal:     /* definitely works! */
1096       case VariantWildCastle: /* pieces not automatically shuffled */
1097       case VariantNoCastle:   /* pieces not automatically shuffled */
1098       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1099       case VariantLosers:     /* should work except for win condition,
1100                                  and doesn't know captures are mandatory */
1101       case VariantSuicide:    /* should work except for win condition,
1102                                  and doesn't know captures are mandatory */
1103       case VariantGiveaway:   /* should work except for win condition,
1104                                  and doesn't know captures are mandatory */
1105       case VariantTwoKings:   /* should work */
1106       case VariantAtomic:     /* should work except for win condition */
1107       case Variant3Check:     /* should work except for win condition */
1108       case VariantShatranj:   /* should work except for all win conditions */
1109       case VariantMakruk:     /* should work except for daw countdown */
1110       case VariantBerolina:   /* might work if TestLegality is off */
1111       case VariantCapaRandom: /* should work */
1112       case VariantJanus:      /* should work */
1113       case VariantSuper:      /* experimental */
1114       case VariantGreat:      /* experimental, requires legality testing to be off */
1115       case VariantSChess:     /* S-Chess, should work */
1116       case VariantSpartan:    /* should work */
1117         break;
1118       }
1119     }
1120
1121 }
1122
1123 int NextIntegerFromString( char ** str, long * value )
1124 {
1125     int result = -1;
1126     char * s = *str;
1127
1128     while( *s == ' ' || *s == '\t' ) {
1129         s++;
1130     }
1131
1132     *value = 0;
1133
1134     if( *s >= '0' && *s <= '9' ) {
1135         while( *s >= '0' && *s <= '9' ) {
1136             *value = *value * 10 + (*s - '0');
1137             s++;
1138         }
1139
1140         result = 0;
1141     }
1142
1143     *str = s;
1144
1145     return result;
1146 }
1147
1148 int NextTimeControlFromString( char ** str, long * value )
1149 {
1150     long temp;
1151     int result = NextIntegerFromString( str, &temp );
1152
1153     if( result == 0 ) {
1154         *value = temp * 60; /* Minutes */
1155         if( **str == ':' ) {
1156             (*str)++;
1157             result = NextIntegerFromString( str, &temp );
1158             *value += temp; /* Seconds */
1159         }
1160     }
1161
1162     return result;
1163 }
1164
1165 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1166 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1167     int result = -1, type = 0; long temp, temp2;
1168
1169     if(**str != ':') return -1; // old params remain in force!
1170     (*str)++;
1171     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1172     if( NextIntegerFromString( str, &temp ) ) return -1;
1173     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1174
1175     if(**str != '/') {
1176         /* time only: incremental or sudden-death time control */
1177         if(**str == '+') { /* increment follows; read it */
1178             (*str)++;
1179             if(**str == '!') type = *(*str)++; // Bronstein TC
1180             if(result = NextIntegerFromString( str, &temp2)) return -1;
1181             *inc = temp2 * 1000;
1182             if(**str == '.') { // read fraction of increment
1183                 char *start = ++(*str);
1184                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1185                 temp2 *= 1000;
1186                 while(start++ < *str) temp2 /= 10;
1187                 *inc += temp2;
1188             }
1189         } else *inc = 0;
1190         *moves = 0; *tc = temp * 1000; *incType = type;
1191         return 0;
1192     }
1193
1194     (*str)++; /* classical time control */
1195     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1196
1197     if(result == 0) {
1198         *moves = temp;
1199         *tc    = temp2 * 1000;
1200         *inc   = 0;
1201         *incType = type;
1202     }
1203     return result;
1204 }
1205
1206 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1207 {   /* [HGM] get time to add from the multi-session time-control string */
1208     int incType, moves=1; /* kludge to force reading of first session */
1209     long time, increment;
1210     char *s = tcString;
1211
1212     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1213     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1214     do {
1215         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1216         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1217         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1218         if(movenr == -1) return time;    /* last move before new session     */
1219         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1220         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1221         if(!moves) return increment;     /* current session is incremental   */
1222         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1223     } while(movenr >= -1);               /* try again for next session       */
1224
1225     return 0; // no new time quota on this move
1226 }
1227
1228 int
1229 ParseTimeControl(tc, ti, mps)
1230      char *tc;
1231      float ti;
1232      int mps;
1233 {
1234   long tc1;
1235   long tc2;
1236   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1237   int min, sec=0;
1238
1239   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1240   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1241       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1242   if(ti > 0) {
1243
1244     if(mps)
1245       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1246     else 
1247       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1248   } else {
1249     if(mps)
1250       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1251     else 
1252       snprintf(buf, MSG_SIZ, ":%s", mytc);
1253   }
1254   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1255   
1256   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1257     return FALSE;
1258   }
1259
1260   if( *tc == '/' ) {
1261     /* Parse second time control */
1262     tc++;
1263
1264     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1265       return FALSE;
1266     }
1267
1268     if( tc2 == 0 ) {
1269       return FALSE;
1270     }
1271
1272     timeControl_2 = tc2 * 1000;
1273   }
1274   else {
1275     timeControl_2 = 0;
1276   }
1277
1278   if( tc1 == 0 ) {
1279     return FALSE;
1280   }
1281
1282   timeControl = tc1 * 1000;
1283
1284   if (ti >= 0) {
1285     timeIncrement = ti * 1000;  /* convert to ms */
1286     movesPerSession = 0;
1287   } else {
1288     timeIncrement = 0;
1289     movesPerSession = mps;
1290   }
1291   return TRUE;
1292 }
1293
1294 void
1295 InitBackEnd2()
1296 {
1297     if (appData.debugMode) {
1298         fprintf(debugFP, "%s\n", programVersion);
1299     }
1300
1301     set_cont_sequence(appData.wrapContSeq);
1302     if (appData.matchGames > 0) {
1303         appData.matchMode = TRUE;
1304     } else if (appData.matchMode) {
1305         appData.matchGames = 1;
1306     }
1307     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1308         appData.matchGames = appData.sameColorGames;
1309     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1310         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1311         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1312     }
1313     Reset(TRUE, FALSE);
1314     if (appData.noChessProgram || first.protocolVersion == 1) {
1315       InitBackEnd3();
1316     } else {
1317       /* kludge: allow timeout for initial "feature" commands */
1318       FreezeUI();
1319       DisplayMessage("", _("Starting chess program"));
1320       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1321     }
1322 }
1323
1324 int
1325 CalculateIndex(int index, int gameNr)
1326 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1327     int res;
1328     if(index > 0) return index; // fixed nmber
1329     if(index == 0) return 1;
1330     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1331     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1332     return res;
1333 }
1334
1335 int
1336 LoadGameOrPosition(int gameNr)
1337 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1338     if (*appData.loadGameFile != NULLCHAR) {
1339         if (!LoadGameFromFile(appData.loadGameFile,
1340                 CalculateIndex(appData.loadGameIndex, gameNr),
1341                               appData.loadGameFile, FALSE)) {
1342             DisplayFatalError(_("Bad game file"), 0, 1);
1343             return 0;
1344         }
1345     } else if (*appData.loadPositionFile != NULLCHAR) {
1346         if (!LoadPositionFromFile(appData.loadPositionFile,
1347                 CalculateIndex(appData.loadPositionIndex, gameNr),
1348                                   appData.loadPositionFile)) {
1349             DisplayFatalError(_("Bad position file"), 0, 1);
1350             return 0;
1351         }
1352     }
1353     return 1;
1354 }
1355
1356 void
1357 ReserveGame(int gameNr, char resChar)
1358 {
1359     FILE *tf = fopen(appData.tourneyFile, "r+");
1360     char *p, *q, c, buf[MSG_SIZ];
1361     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1362     safeStrCpy(buf, lastMsg, MSG_SIZ);
1363     DisplayMessage(_("Pick new game"), "");
1364     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1365     ParseArgsFromFile(tf);
1366     p = q = appData.results;
1367     if(appData.debugMode) {
1368       char *r = appData.participants;
1369       fprintf(debugFP, "results = '%s'\n", p);
1370       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1371       fprintf(debugFP, "\n");
1372     }
1373     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1374     nextGame = q - p;
1375     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1376     safeStrCpy(q, p, strlen(p) + 2);
1377     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1378     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1379     if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1380         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1381         q[nextGame] = '*';
1382     }
1383     fseek(tf, -(strlen(p)+4), SEEK_END);
1384     c = fgetc(tf);
1385     if(c != '"') // depending on DOS or Unix line endings we can be one off
1386          fseek(tf, -(strlen(p)+2), SEEK_END);
1387     else fseek(tf, -(strlen(p)+3), SEEK_END);
1388     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1389     DisplayMessage(buf, "");
1390     free(p); appData.results = q;
1391     if(nextGame <= appData.matchGames && resChar != ' ' &&
1392        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1393         UnloadEngine(&first);  // next game belongs to other pairing;
1394         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1395     }
1396 }
1397
1398 void
1399 MatchEvent(int mode)
1400 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1401         int dummy;
1402         if(matchMode) { // already in match mode: switch it off
1403             abortMatch = TRUE;
1404             appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1405             ModeHighlight(); // kludgey way to remove checkmark...
1406             return;
1407         }
1408 //      if(gameMode != BeginningOfGame) {
1409 //          DisplayError(_("You can only start a match from the initial position."), 0);
1410 //          return;
1411 //      }
1412         abortMatch = FALSE;
1413         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1414         /* Set up machine vs. machine match */
1415         nextGame = 0;
1416         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1417         if(appData.tourneyFile[0]) {
1418             ReserveGame(-1, 0);
1419             if(nextGame > appData.matchGames) {
1420                 char buf[MSG_SIZ];
1421                 if(strchr(appData.results, '*') == NULL) {
1422                     FILE *f;
1423                     appData.tourneyCycles++;
1424                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1425                         fclose(f);
1426                         NextTourneyGame(-1, &dummy);
1427                         ReserveGame(-1, 0);
1428                         if(nextGame <= appData.matchGames) {
1429                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1430                             matchMode = mode;
1431                             ScheduleDelayedEvent(NextMatchGame, 10000);
1432                             return;
1433                         }
1434                     }
1435                 }
1436                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1437                 DisplayError(buf, 0);
1438                 appData.tourneyFile[0] = 0;
1439                 return;
1440             }
1441         } else
1442         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1443             DisplayFatalError(_("Can't have a match with no chess programs"),
1444                               0, 2);
1445             return;
1446         }
1447         matchMode = mode;
1448         matchGame = roundNr = 1;
1449         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1450         NextMatchGame();
1451 }
1452
1453 void
1454 InitBackEnd3 P((void))
1455 {
1456     GameMode initialMode;
1457     char buf[MSG_SIZ];
1458     int err, len;
1459
1460     InitChessProgram(&first, startedFromSetupPosition);
1461
1462     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1463         free(programVersion);
1464         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1465         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1466     }
1467
1468     if (appData.icsActive) {
1469 #ifdef WIN32
1470         /* [DM] Make a console window if needed [HGM] merged ifs */
1471         ConsoleCreate();
1472 #endif
1473         err = establish();
1474         if (err != 0)
1475           {
1476             if (*appData.icsCommPort != NULLCHAR)
1477               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1478                              appData.icsCommPort);
1479             else
1480               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1481                         appData.icsHost, appData.icsPort);
1482
1483             if( (len > MSG_SIZ) && appData.debugMode )
1484               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1485
1486             DisplayFatalError(buf, err, 1);
1487             return;
1488         }
1489         SetICSMode();
1490         telnetISR =
1491           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1492         fromUserISR =
1493           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1494         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1495             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1496     } else if (appData.noChessProgram) {
1497         SetNCPMode();
1498     } else {
1499         SetGNUMode();
1500     }
1501
1502     if (*appData.cmailGameName != NULLCHAR) {
1503         SetCmailMode();
1504         OpenLoopback(&cmailPR);
1505         cmailISR =
1506           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1507     }
1508
1509     ThawUI();
1510     DisplayMessage("", "");
1511     if (StrCaseCmp(appData.initialMode, "") == 0) {
1512       initialMode = BeginningOfGame;
1513       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1514         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1515         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1516         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1517         ModeHighlight();
1518       }
1519     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1520       initialMode = TwoMachinesPlay;
1521     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1522       initialMode = AnalyzeFile;
1523     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1524       initialMode = AnalyzeMode;
1525     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1526       initialMode = MachinePlaysWhite;
1527     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1528       initialMode = MachinePlaysBlack;
1529     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1530       initialMode = EditGame;
1531     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1532       initialMode = EditPosition;
1533     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1534       initialMode = Training;
1535     } else {
1536       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1537       if( (len > MSG_SIZ) && appData.debugMode )
1538         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1539
1540       DisplayFatalError(buf, 0, 2);
1541       return;
1542     }
1543
1544     if (appData.matchMode) {
1545         if(appData.tourneyFile[0]) { // start tourney from command line
1546             FILE *f;
1547             if(f = fopen(appData.tourneyFile, "r")) {
1548                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1549                 fclose(f);
1550             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1551         }
1552         MatchEvent(TRUE);
1553     } else if (*appData.cmailGameName != NULLCHAR) {
1554         /* Set up cmail mode */
1555         ReloadCmailMsgEvent(TRUE);
1556     } else {
1557         /* Set up other modes */
1558         if (initialMode == AnalyzeFile) {
1559           if (*appData.loadGameFile == NULLCHAR) {
1560             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1561             return;
1562           }
1563         }
1564         if (*appData.loadGameFile != NULLCHAR) {
1565             (void) LoadGameFromFile(appData.loadGameFile,
1566                                     appData.loadGameIndex,
1567                                     appData.loadGameFile, TRUE);
1568         } else if (*appData.loadPositionFile != NULLCHAR) {
1569             (void) LoadPositionFromFile(appData.loadPositionFile,
1570                                         appData.loadPositionIndex,
1571                                         appData.loadPositionFile);
1572             /* [HGM] try to make self-starting even after FEN load */
1573             /* to allow automatic setup of fairy variants with wtm */
1574             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1575                 gameMode = BeginningOfGame;
1576                 setboardSpoiledMachineBlack = 1;
1577             }
1578             /* [HGM] loadPos: make that every new game uses the setup */
1579             /* from file as long as we do not switch variant          */
1580             if(!blackPlaysFirst) {
1581                 startedFromPositionFile = TRUE;
1582                 CopyBoard(filePosition, boards[0]);
1583             }
1584         }
1585         if (initialMode == AnalyzeMode) {
1586           if (appData.noChessProgram) {
1587             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1588             return;
1589           }
1590           if (appData.icsActive) {
1591             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1592             return;
1593           }
1594           AnalyzeModeEvent();
1595         } else if (initialMode == AnalyzeFile) {
1596           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1597           ShowThinkingEvent();
1598           AnalyzeFileEvent();
1599           AnalysisPeriodicEvent(1);
1600         } else if (initialMode == MachinePlaysWhite) {
1601           if (appData.noChessProgram) {
1602             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1603                               0, 2);
1604             return;
1605           }
1606           if (appData.icsActive) {
1607             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1608                               0, 2);
1609             return;
1610           }
1611           MachineWhiteEvent();
1612         } else if (initialMode == MachinePlaysBlack) {
1613           if (appData.noChessProgram) {
1614             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1615                               0, 2);
1616             return;
1617           }
1618           if (appData.icsActive) {
1619             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1620                               0, 2);
1621             return;
1622           }
1623           MachineBlackEvent();
1624         } else if (initialMode == TwoMachinesPlay) {
1625           if (appData.noChessProgram) {
1626             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1627                               0, 2);
1628             return;
1629           }
1630           if (appData.icsActive) {
1631             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1632                               0, 2);
1633             return;
1634           }
1635           TwoMachinesEvent();
1636         } else if (initialMode == EditGame) {
1637           EditGameEvent();
1638         } else if (initialMode == EditPosition) {
1639           EditPositionEvent();
1640         } else if (initialMode == Training) {
1641           if (*appData.loadGameFile == NULLCHAR) {
1642             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1643             return;
1644           }
1645           TrainingEvent();
1646         }
1647     }
1648 }
1649
1650 /*
1651  * Establish will establish a contact to a remote host.port.
1652  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1653  *  used to talk to the host.
1654  * Returns 0 if okay, error code if not.
1655  */
1656 int
1657 establish()
1658 {
1659     char buf[MSG_SIZ];
1660
1661     if (*appData.icsCommPort != NULLCHAR) {
1662         /* Talk to the host through a serial comm port */
1663         return OpenCommPort(appData.icsCommPort, &icsPR);
1664
1665     } else if (*appData.gateway != NULLCHAR) {
1666         if (*appData.remoteShell == NULLCHAR) {
1667             /* Use the rcmd protocol to run telnet program on a gateway host */
1668             snprintf(buf, sizeof(buf), "%s %s %s",
1669                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1670             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1671
1672         } else {
1673             /* Use the rsh program to run telnet program on a gateway host */
1674             if (*appData.remoteUser == NULLCHAR) {
1675                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1676                         appData.gateway, appData.telnetProgram,
1677                         appData.icsHost, appData.icsPort);
1678             } else {
1679                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1680                         appData.remoteShell, appData.gateway,
1681                         appData.remoteUser, appData.telnetProgram,
1682                         appData.icsHost, appData.icsPort);
1683             }
1684             return StartChildProcess(buf, "", &icsPR);
1685
1686         }
1687     } else if (appData.useTelnet) {
1688         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1689
1690     } else {
1691         /* TCP socket interface differs somewhat between
1692            Unix and NT; handle details in the front end.
1693            */
1694         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1695     }
1696 }
1697
1698 void EscapeExpand(char *p, char *q)
1699 {       // [HGM] initstring: routine to shape up string arguments
1700         while(*p++ = *q++) if(p[-1] == '\\')
1701             switch(*q++) {
1702                 case 'n': p[-1] = '\n'; break;
1703                 case 'r': p[-1] = '\r'; break;
1704                 case 't': p[-1] = '\t'; break;
1705                 case '\\': p[-1] = '\\'; break;
1706                 case 0: *p = 0; return;
1707                 default: p[-1] = q[-1]; break;
1708             }
1709 }
1710
1711 void
1712 show_bytes(fp, buf, count)
1713      FILE *fp;
1714      char *buf;
1715      int count;
1716 {
1717     while (count--) {
1718         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1719             fprintf(fp, "\\%03o", *buf & 0xff);
1720         } else {
1721             putc(*buf, fp);
1722         }
1723         buf++;
1724     }
1725     fflush(fp);
1726 }
1727
1728 /* Returns an errno value */
1729 int
1730 OutputMaybeTelnet(pr, message, count, outError)
1731      ProcRef pr;
1732      char *message;
1733      int count;
1734      int *outError;
1735 {
1736     char buf[8192], *p, *q, *buflim;
1737     int left, newcount, outcount;
1738
1739     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1740         *appData.gateway != NULLCHAR) {
1741         if (appData.debugMode) {
1742             fprintf(debugFP, ">ICS: ");
1743             show_bytes(debugFP, message, count);
1744             fprintf(debugFP, "\n");
1745         }
1746         return OutputToProcess(pr, message, count, outError);
1747     }
1748
1749     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1750     p = message;
1751     q = buf;
1752     left = count;
1753     newcount = 0;
1754     while (left) {
1755         if (q >= buflim) {
1756             if (appData.debugMode) {
1757                 fprintf(debugFP, ">ICS: ");
1758                 show_bytes(debugFP, buf, newcount);
1759                 fprintf(debugFP, "\n");
1760             }
1761             outcount = OutputToProcess(pr, buf, newcount, outError);
1762             if (outcount < newcount) return -1; /* to be sure */
1763             q = buf;
1764             newcount = 0;
1765         }
1766         if (*p == '\n') {
1767             *q++ = '\r';
1768             newcount++;
1769         } else if (((unsigned char) *p) == TN_IAC) {
1770             *q++ = (char) TN_IAC;
1771             newcount ++;
1772         }
1773         *q++ = *p++;
1774         newcount++;
1775         left--;
1776     }
1777     if (appData.debugMode) {
1778         fprintf(debugFP, ">ICS: ");
1779         show_bytes(debugFP, buf, newcount);
1780         fprintf(debugFP, "\n");
1781     }
1782     outcount = OutputToProcess(pr, buf, newcount, outError);
1783     if (outcount < newcount) return -1; /* to be sure */
1784     return count;
1785 }
1786
1787 void
1788 read_from_player(isr, closure, message, count, error)
1789      InputSourceRef isr;
1790      VOIDSTAR closure;
1791      char *message;
1792      int count;
1793      int error;
1794 {
1795     int outError, outCount;
1796     static int gotEof = 0;
1797
1798     /* Pass data read from player on to ICS */
1799     if (count > 0) {
1800         gotEof = 0;
1801         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1802         if (outCount < count) {
1803             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1804         }
1805     } else if (count < 0) {
1806         RemoveInputSource(isr);
1807         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1808     } else if (gotEof++ > 0) {
1809         RemoveInputSource(isr);
1810         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1811     }
1812 }
1813
1814 void
1815 KeepAlive()
1816 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1817     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1818     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1819     SendToICS("date\n");
1820     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1821 }
1822
1823 /* added routine for printf style output to ics */
1824 void ics_printf(char *format, ...)
1825 {
1826     char buffer[MSG_SIZ];
1827     va_list args;
1828
1829     va_start(args, format);
1830     vsnprintf(buffer, sizeof(buffer), format, args);
1831     buffer[sizeof(buffer)-1] = '\0';
1832     SendToICS(buffer);
1833     va_end(args);
1834 }
1835
1836 void
1837 SendToICS(s)
1838      char *s;
1839 {
1840     int count, outCount, outError;
1841
1842     if (icsPR == NULL) return;
1843
1844     count = strlen(s);
1845     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1846     if (outCount < count) {
1847         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1848     }
1849 }
1850
1851 /* This is used for sending logon scripts to the ICS. Sending
1852    without a delay causes problems when using timestamp on ICC
1853    (at least on my machine). */
1854 void
1855 SendToICSDelayed(s,msdelay)
1856      char *s;
1857      long msdelay;
1858 {
1859     int count, outCount, outError;
1860
1861     if (icsPR == NULL) return;
1862
1863     count = strlen(s);
1864     if (appData.debugMode) {
1865         fprintf(debugFP, ">ICS: ");
1866         show_bytes(debugFP, s, count);
1867         fprintf(debugFP, "\n");
1868     }
1869     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1870                                       msdelay);
1871     if (outCount < count) {
1872         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1873     }
1874 }
1875
1876
1877 /* Remove all highlighting escape sequences in s
1878    Also deletes any suffix starting with '('
1879    */
1880 char *
1881 StripHighlightAndTitle(s)
1882      char *s;
1883 {
1884     static char retbuf[MSG_SIZ];
1885     char *p = retbuf;
1886
1887     while (*s != NULLCHAR) {
1888         while (*s == '\033') {
1889             while (*s != NULLCHAR && !isalpha(*s)) s++;
1890             if (*s != NULLCHAR) s++;
1891         }
1892         while (*s != NULLCHAR && *s != '\033') {
1893             if (*s == '(' || *s == '[') {
1894                 *p = NULLCHAR;
1895                 return retbuf;
1896             }
1897             *p++ = *s++;
1898         }
1899     }
1900     *p = NULLCHAR;
1901     return retbuf;
1902 }
1903
1904 /* Remove all highlighting escape sequences in s */
1905 char *
1906 StripHighlight(s)
1907      char *s;
1908 {
1909     static char retbuf[MSG_SIZ];
1910     char *p = retbuf;
1911
1912     while (*s != NULLCHAR) {
1913         while (*s == '\033') {
1914             while (*s != NULLCHAR && !isalpha(*s)) s++;
1915             if (*s != NULLCHAR) s++;
1916         }
1917         while (*s != NULLCHAR && *s != '\033') {
1918             *p++ = *s++;
1919         }
1920     }
1921     *p = NULLCHAR;
1922     return retbuf;
1923 }
1924
1925 char *variantNames[] = VARIANT_NAMES;
1926 char *
1927 VariantName(v)
1928      VariantClass v;
1929 {
1930     return variantNames[v];
1931 }
1932
1933
1934 /* Identify a variant from the strings the chess servers use or the
1935    PGN Variant tag names we use. */
1936 VariantClass
1937 StringToVariant(e)
1938      char *e;
1939 {
1940     char *p;
1941     int wnum = -1;
1942     VariantClass v = VariantNormal;
1943     int i, found = FALSE;
1944     char buf[MSG_SIZ];
1945     int len;
1946
1947     if (!e) return v;
1948
1949     /* [HGM] skip over optional board-size prefixes */
1950     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1951         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1952         while( *e++ != '_');
1953     }
1954
1955     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1956         v = VariantNormal;
1957         found = TRUE;
1958     } else
1959     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1960       if (StrCaseStr(e, variantNames[i])) {
1961         v = (VariantClass) i;
1962         found = TRUE;
1963         break;
1964       }
1965     }
1966
1967     if (!found) {
1968       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1969           || StrCaseStr(e, "wild/fr")
1970           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1971         v = VariantFischeRandom;
1972       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1973                  (i = 1, p = StrCaseStr(e, "w"))) {
1974         p += i;
1975         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1976         if (isdigit(*p)) {
1977           wnum = atoi(p);
1978         } else {
1979           wnum = -1;
1980         }
1981         switch (wnum) {
1982         case 0: /* FICS only, actually */
1983         case 1:
1984           /* Castling legal even if K starts on d-file */
1985           v = VariantWildCastle;
1986           break;
1987         case 2:
1988         case 3:
1989         case 4:
1990           /* Castling illegal even if K & R happen to start in
1991              normal positions. */
1992           v = VariantNoCastle;
1993           break;
1994         case 5:
1995         case 7:
1996         case 8:
1997         case 10:
1998         case 11:
1999         case 12:
2000         case 13:
2001         case 14:
2002         case 15:
2003         case 18:
2004         case 19:
2005           /* Castling legal iff K & R start in normal positions */
2006           v = VariantNormal;
2007           break;
2008         case 6:
2009         case 20:
2010         case 21:
2011           /* Special wilds for position setup; unclear what to do here */
2012           v = VariantLoadable;
2013           break;
2014         case 9:
2015           /* Bizarre ICC game */
2016           v = VariantTwoKings;
2017           break;
2018         case 16:
2019           v = VariantKriegspiel;
2020           break;
2021         case 17:
2022           v = VariantLosers;
2023           break;
2024         case 22:
2025           v = VariantFischeRandom;
2026           break;
2027         case 23:
2028           v = VariantCrazyhouse;
2029           break;
2030         case 24:
2031           v = VariantBughouse;
2032           break;
2033         case 25:
2034           v = Variant3Check;
2035           break;
2036         case 26:
2037           /* Not quite the same as FICS suicide! */
2038           v = VariantGiveaway;
2039           break;
2040         case 27:
2041           v = VariantAtomic;
2042           break;
2043         case 28:
2044           v = VariantShatranj;
2045           break;
2046
2047         /* Temporary names for future ICC types.  The name *will* change in
2048            the next xboard/WinBoard release after ICC defines it. */
2049         case 29:
2050           v = Variant29;
2051           break;
2052         case 30:
2053           v = Variant30;
2054           break;
2055         case 31:
2056           v = Variant31;
2057           break;
2058         case 32:
2059           v = Variant32;
2060           break;
2061         case 33:
2062           v = Variant33;
2063           break;
2064         case 34:
2065           v = Variant34;
2066           break;
2067         case 35:
2068           v = Variant35;
2069           break;
2070         case 36:
2071           v = Variant36;
2072           break;
2073         case 37:
2074           v = VariantShogi;
2075           break;
2076         case 38:
2077           v = VariantXiangqi;
2078           break;
2079         case 39:
2080           v = VariantCourier;
2081           break;
2082         case 40:
2083           v = VariantGothic;
2084           break;
2085         case 41:
2086           v = VariantCapablanca;
2087           break;
2088         case 42:
2089           v = VariantKnightmate;
2090           break;
2091         case 43:
2092           v = VariantFairy;
2093           break;
2094         case 44:
2095           v = VariantCylinder;
2096           break;
2097         case 45:
2098           v = VariantFalcon;
2099           break;
2100         case 46:
2101           v = VariantCapaRandom;
2102           break;
2103         case 47:
2104           v = VariantBerolina;
2105           break;
2106         case 48:
2107           v = VariantJanus;
2108           break;
2109         case 49:
2110           v = VariantSuper;
2111           break;
2112         case 50:
2113           v = VariantGreat;
2114           break;
2115         case -1:
2116           /* Found "wild" or "w" in the string but no number;
2117              must assume it's normal chess. */
2118           v = VariantNormal;
2119           break;
2120         default:
2121           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2122           if( (len > MSG_SIZ) && appData.debugMode )
2123             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2124
2125           DisplayError(buf, 0);
2126           v = VariantUnknown;
2127           break;
2128         }
2129       }
2130     }
2131     if (appData.debugMode) {
2132       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2133               e, wnum, VariantName(v));
2134     }
2135     return v;
2136 }
2137
2138 static int leftover_start = 0, leftover_len = 0;
2139 char star_match[STAR_MATCH_N][MSG_SIZ];
2140
2141 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2142    advance *index beyond it, and set leftover_start to the new value of
2143    *index; else return FALSE.  If pattern contains the character '*', it
2144    matches any sequence of characters not containing '\r', '\n', or the
2145    character following the '*' (if any), and the matched sequence(s) are
2146    copied into star_match.
2147    */
2148 int
2149 looking_at(buf, index, pattern)
2150      char *buf;
2151      int *index;
2152      char *pattern;
2153 {
2154     char *bufp = &buf[*index], *patternp = pattern;
2155     int star_count = 0;
2156     char *matchp = star_match[0];
2157
2158     for (;;) {
2159         if (*patternp == NULLCHAR) {
2160             *index = leftover_start = bufp - buf;
2161             *matchp = NULLCHAR;
2162             return TRUE;
2163         }
2164         if (*bufp == NULLCHAR) return FALSE;
2165         if (*patternp == '*') {
2166             if (*bufp == *(patternp + 1)) {
2167                 *matchp = NULLCHAR;
2168                 matchp = star_match[++star_count];
2169                 patternp += 2;
2170                 bufp++;
2171                 continue;
2172             } else if (*bufp == '\n' || *bufp == '\r') {
2173                 patternp++;
2174                 if (*patternp == NULLCHAR)
2175                   continue;
2176                 else
2177                   return FALSE;
2178             } else {
2179                 *matchp++ = *bufp++;
2180                 continue;
2181             }
2182         }
2183         if (*patternp != *bufp) return FALSE;
2184         patternp++;
2185         bufp++;
2186     }
2187 }
2188
2189 void
2190 SendToPlayer(data, length)
2191      char *data;
2192      int length;
2193 {
2194     int error, outCount;
2195     outCount = OutputToProcess(NoProc, data, length, &error);
2196     if (outCount < length) {
2197         DisplayFatalError(_("Error writing to display"), error, 1);
2198     }
2199 }
2200
2201 void
2202 PackHolding(packed, holding)
2203      char packed[];
2204      char *holding;
2205 {
2206     char *p = holding;
2207     char *q = packed;
2208     int runlength = 0;
2209     int curr = 9999;
2210     do {
2211         if (*p == curr) {
2212             runlength++;
2213         } else {
2214             switch (runlength) {
2215               case 0:
2216                 break;
2217               case 1:
2218                 *q++ = curr;
2219                 break;
2220               case 2:
2221                 *q++ = curr;
2222                 *q++ = curr;
2223                 break;
2224               default:
2225                 sprintf(q, "%d", runlength);
2226                 while (*q) q++;
2227                 *q++ = curr;
2228                 break;
2229             }
2230             runlength = 1;
2231             curr = *p;
2232         }
2233     } while (*p++);
2234     *q = NULLCHAR;
2235 }
2236
2237 /* Telnet protocol requests from the front end */
2238 void
2239 TelnetRequest(ddww, option)
2240      unsigned char ddww, option;
2241 {
2242     unsigned char msg[3];
2243     int outCount, outError;
2244
2245     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2246
2247     if (appData.debugMode) {
2248         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2249         switch (ddww) {
2250           case TN_DO:
2251             ddwwStr = "DO";
2252             break;
2253           case TN_DONT:
2254             ddwwStr = "DONT";
2255             break;
2256           case TN_WILL:
2257             ddwwStr = "WILL";
2258             break;
2259           case TN_WONT:
2260             ddwwStr = "WONT";
2261             break;
2262           default:
2263             ddwwStr = buf1;
2264             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2265             break;
2266         }
2267         switch (option) {
2268           case TN_ECHO:
2269             optionStr = "ECHO";
2270             break;
2271           default:
2272             optionStr = buf2;
2273             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2274             break;
2275         }
2276         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2277     }
2278     msg[0] = TN_IAC;
2279     msg[1] = ddww;
2280     msg[2] = option;
2281     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2282     if (outCount < 3) {
2283         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2284     }
2285 }
2286
2287 void
2288 DoEcho()
2289 {
2290     if (!appData.icsActive) return;
2291     TelnetRequest(TN_DO, TN_ECHO);
2292 }
2293
2294 void
2295 DontEcho()
2296 {
2297     if (!appData.icsActive) return;
2298     TelnetRequest(TN_DONT, TN_ECHO);
2299 }
2300
2301 void
2302 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2303 {
2304     /* put the holdings sent to us by the server on the board holdings area */
2305     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2306     char p;
2307     ChessSquare piece;
2308
2309     if(gameInfo.holdingsWidth < 2)  return;
2310     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2311         return; // prevent overwriting by pre-board holdings
2312
2313     if( (int)lowestPiece >= BlackPawn ) {
2314         holdingsColumn = 0;
2315         countsColumn = 1;
2316         holdingsStartRow = BOARD_HEIGHT-1;
2317         direction = -1;
2318     } else {
2319         holdingsColumn = BOARD_WIDTH-1;
2320         countsColumn = BOARD_WIDTH-2;
2321         holdingsStartRow = 0;
2322         direction = 1;
2323     }
2324
2325     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2326         board[i][holdingsColumn] = EmptySquare;
2327         board[i][countsColumn]   = (ChessSquare) 0;
2328     }
2329     while( (p=*holdings++) != NULLCHAR ) {
2330         piece = CharToPiece( ToUpper(p) );
2331         if(piece == EmptySquare) continue;
2332         /*j = (int) piece - (int) WhitePawn;*/
2333         j = PieceToNumber(piece);
2334         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2335         if(j < 0) continue;               /* should not happen */
2336         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2337         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2338         board[holdingsStartRow+j*direction][countsColumn]++;
2339     }
2340 }
2341
2342
2343 void
2344 VariantSwitch(Board board, VariantClass newVariant)
2345 {
2346    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2347    static Board oldBoard;
2348
2349    startedFromPositionFile = FALSE;
2350    if(gameInfo.variant == newVariant) return;
2351
2352    /* [HGM] This routine is called each time an assignment is made to
2353     * gameInfo.variant during a game, to make sure the board sizes
2354     * are set to match the new variant. If that means adding or deleting
2355     * holdings, we shift the playing board accordingly
2356     * This kludge is needed because in ICS observe mode, we get boards
2357     * of an ongoing game without knowing the variant, and learn about the
2358     * latter only later. This can be because of the move list we requested,
2359     * in which case the game history is refilled from the beginning anyway,
2360     * but also when receiving holdings of a crazyhouse game. In the latter
2361     * case we want to add those holdings to the already received position.
2362     */
2363
2364
2365    if (appData.debugMode) {
2366      fprintf(debugFP, "Switch board from %s to %s\n",
2367              VariantName(gameInfo.variant), VariantName(newVariant));
2368      setbuf(debugFP, NULL);
2369    }
2370    shuffleOpenings = 0;       /* [HGM] shuffle */
2371    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2372    switch(newVariant)
2373      {
2374      case VariantShogi:
2375        newWidth = 9;  newHeight = 9;
2376        gameInfo.holdingsSize = 7;
2377      case VariantBughouse:
2378      case VariantCrazyhouse:
2379        newHoldingsWidth = 2; break;
2380      case VariantGreat:
2381        newWidth = 10;
2382      case VariantSuper:
2383        newHoldingsWidth = 2;
2384        gameInfo.holdingsSize = 8;
2385        break;
2386      case VariantGothic:
2387      case VariantCapablanca:
2388      case VariantCapaRandom:
2389        newWidth = 10;
2390      default:
2391        newHoldingsWidth = gameInfo.holdingsSize = 0;
2392      };
2393
2394    if(newWidth  != gameInfo.boardWidth  ||
2395       newHeight != gameInfo.boardHeight ||
2396       newHoldingsWidth != gameInfo.holdingsWidth ) {
2397
2398      /* shift position to new playing area, if needed */
2399      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2400        for(i=0; i<BOARD_HEIGHT; i++)
2401          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2402            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2403              board[i][j];
2404        for(i=0; i<newHeight; i++) {
2405          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2406          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2407        }
2408      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2409        for(i=0; i<BOARD_HEIGHT; i++)
2410          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2411            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2412              board[i][j];
2413      }
2414      gameInfo.boardWidth  = newWidth;
2415      gameInfo.boardHeight = newHeight;
2416      gameInfo.holdingsWidth = newHoldingsWidth;
2417      gameInfo.variant = newVariant;
2418      InitDrawingSizes(-2, 0);
2419    } else gameInfo.variant = newVariant;
2420    CopyBoard(oldBoard, board);   // remember correctly formatted board
2421      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2422    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2423 }
2424
2425 static int loggedOn = FALSE;
2426
2427 /*-- Game start info cache: --*/
2428 int gs_gamenum;
2429 char gs_kind[MSG_SIZ];
2430 static char player1Name[128] = "";
2431 static char player2Name[128] = "";
2432 static char cont_seq[] = "\n\\   ";
2433 static int player1Rating = -1;
2434 static int player2Rating = -1;
2435 /*----------------------------*/
2436
2437 ColorClass curColor = ColorNormal;
2438 int suppressKibitz = 0;
2439
2440 // [HGM] seekgraph
2441 Boolean soughtPending = FALSE;
2442 Boolean seekGraphUp;
2443 #define MAX_SEEK_ADS 200
2444 #define SQUARE 0x80
2445 char *seekAdList[MAX_SEEK_ADS];
2446 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2447 float tcList[MAX_SEEK_ADS];
2448 char colorList[MAX_SEEK_ADS];
2449 int nrOfSeekAds = 0;
2450 int minRating = 1010, maxRating = 2800;
2451 int hMargin = 10, vMargin = 20, h, w;
2452 extern int squareSize, lineGap;
2453
2454 void
2455 PlotSeekAd(int i)
2456 {
2457         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2458         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2459         if(r < minRating+100 && r >=0 ) r = minRating+100;
2460         if(r > maxRating) r = maxRating;
2461         if(tc < 1.) tc = 1.;
2462         if(tc > 95.) tc = 95.;
2463         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2464         y = ((double)r - minRating)/(maxRating - minRating)
2465             * (h-vMargin-squareSize/8-1) + vMargin;
2466         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2467         if(strstr(seekAdList[i], " u ")) color = 1;
2468         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2469            !strstr(seekAdList[i], "bullet") &&
2470            !strstr(seekAdList[i], "blitz") &&
2471            !strstr(seekAdList[i], "standard") ) color = 2;
2472         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2473         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2474 }
2475
2476 void
2477 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2478 {
2479         char buf[MSG_SIZ], *ext = "";
2480         VariantClass v = StringToVariant(type);
2481         if(strstr(type, "wild")) {
2482             ext = type + 4; // append wild number
2483             if(v == VariantFischeRandom) type = "chess960"; else
2484             if(v == VariantLoadable) type = "setup"; else
2485             type = VariantName(v);
2486         }
2487         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2488         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2489             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2490             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2491             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2492             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2493             seekNrList[nrOfSeekAds] = nr;
2494             zList[nrOfSeekAds] = 0;
2495             seekAdList[nrOfSeekAds++] = StrSave(buf);
2496             if(plot) PlotSeekAd(nrOfSeekAds-1);
2497         }
2498 }
2499
2500 void
2501 EraseSeekDot(int i)
2502 {
2503     int x = xList[i], y = yList[i], d=squareSize/4, k;
2504     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2505     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2506     // now replot every dot that overlapped
2507     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2508         int xx = xList[k], yy = yList[k];
2509         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2510             DrawSeekDot(xx, yy, colorList[k]);
2511     }
2512 }
2513
2514 void
2515 RemoveSeekAd(int nr)
2516 {
2517         int i;
2518         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2519             EraseSeekDot(i);
2520             if(seekAdList[i]) free(seekAdList[i]);
2521             seekAdList[i] = seekAdList[--nrOfSeekAds];
2522             seekNrList[i] = seekNrList[nrOfSeekAds];
2523             ratingList[i] = ratingList[nrOfSeekAds];
2524             colorList[i]  = colorList[nrOfSeekAds];
2525             tcList[i] = tcList[nrOfSeekAds];
2526             xList[i]  = xList[nrOfSeekAds];
2527             yList[i]  = yList[nrOfSeekAds];
2528             zList[i]  = zList[nrOfSeekAds];
2529             seekAdList[nrOfSeekAds] = NULL;
2530             break;
2531         }
2532 }
2533
2534 Boolean
2535 MatchSoughtLine(char *line)
2536 {
2537     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2538     int nr, base, inc, u=0; char dummy;
2539
2540     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2541        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2542        (u=1) &&
2543        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2544         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2545         // match: compact and save the line
2546         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2547         return TRUE;
2548     }
2549     return FALSE;
2550 }
2551
2552 int
2553 DrawSeekGraph()
2554 {
2555     int i;
2556     if(!seekGraphUp) return FALSE;
2557     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2558     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2559
2560     DrawSeekBackground(0, 0, w, h);
2561     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2562     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2563     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2564         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2565         yy = h-1-yy;
2566         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2567         if(i%500 == 0) {
2568             char buf[MSG_SIZ];
2569             snprintf(buf, MSG_SIZ, "%d", i);
2570             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2571         }
2572     }
2573     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2574     for(i=1; i<100; i+=(i<10?1:5)) {
2575         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2576         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2577         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2578             char buf[MSG_SIZ];
2579             snprintf(buf, MSG_SIZ, "%d", i);
2580             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2581         }
2582     }
2583     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2584     return TRUE;
2585 }
2586
2587 int SeekGraphClick(ClickType click, int x, int y, int moving)
2588 {
2589     static int lastDown = 0, displayed = 0, lastSecond;
2590     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2591         if(click == Release || moving) return FALSE;
2592         nrOfSeekAds = 0;
2593         soughtPending = TRUE;
2594         SendToICS(ics_prefix);
2595         SendToICS("sought\n"); // should this be "sought all"?
2596     } else { // issue challenge based on clicked ad
2597         int dist = 10000; int i, closest = 0, second = 0;
2598         for(i=0; i<nrOfSeekAds; i++) {
2599             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2600             if(d < dist) { dist = d; closest = i; }
2601             second += (d - zList[i] < 120); // count in-range ads
2602             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2603         }
2604         if(dist < 120) {
2605             char buf[MSG_SIZ];
2606             second = (second > 1);
2607             if(displayed != closest || second != lastSecond) {
2608                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2609                 lastSecond = second; displayed = closest;
2610             }
2611             if(click == Press) {
2612                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2613                 lastDown = closest;
2614                 return TRUE;
2615             } // on press 'hit', only show info
2616             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2617             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2618             SendToICS(ics_prefix);
2619             SendToICS(buf);
2620             return TRUE; // let incoming board of started game pop down the graph
2621         } else if(click == Release) { // release 'miss' is ignored
2622             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2623             if(moving == 2) { // right up-click
2624                 nrOfSeekAds = 0; // refresh graph
2625                 soughtPending = TRUE;
2626                 SendToICS(ics_prefix);
2627                 SendToICS("sought\n"); // should this be "sought all"?
2628             }
2629             return TRUE;
2630         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2631         // press miss or release hit 'pop down' seek graph
2632         seekGraphUp = FALSE;
2633         DrawPosition(TRUE, NULL);
2634     }
2635     return TRUE;
2636 }
2637
2638 void
2639 read_from_ics(isr, closure, data, count, error)
2640      InputSourceRef isr;
2641      VOIDSTAR closure;
2642      char *data;
2643      int count;
2644      int error;
2645 {
2646 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2647 #define STARTED_NONE 0
2648 #define STARTED_MOVES 1
2649 #define STARTED_BOARD 2
2650 #define STARTED_OBSERVE 3
2651 #define STARTED_HOLDINGS 4
2652 #define STARTED_CHATTER 5
2653 #define STARTED_COMMENT 6
2654 #define STARTED_MOVES_NOHIDE 7
2655
2656     static int started = STARTED_NONE;
2657     static char parse[20000];
2658     static int parse_pos = 0;
2659     static char buf[BUF_SIZE + 1];
2660     static int firstTime = TRUE, intfSet = FALSE;
2661     static ColorClass prevColor = ColorNormal;
2662     static int savingComment = FALSE;
2663     static int cmatch = 0; // continuation sequence match
2664     char *bp;
2665     char str[MSG_SIZ];
2666     int i, oldi;
2667     int buf_len;
2668     int next_out;
2669     int tkind;
2670     int backup;    /* [DM] For zippy color lines */
2671     char *p;
2672     char talker[MSG_SIZ]; // [HGM] chat
2673     int channel;
2674
2675     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2676
2677     if (appData.debugMode) {
2678       if (!error) {
2679         fprintf(debugFP, "<ICS: ");
2680         show_bytes(debugFP, data, count);
2681         fprintf(debugFP, "\n");
2682       }
2683     }
2684
2685     if (appData.debugMode) { int f = forwardMostMove;
2686         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2687                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2688                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2689     }
2690     if (count > 0) {
2691         /* If last read ended with a partial line that we couldn't parse,
2692            prepend it to the new read and try again. */
2693         if (leftover_len > 0) {
2694             for (i=0; i<leftover_len; i++)
2695               buf[i] = buf[leftover_start + i];
2696         }
2697
2698     /* copy new characters into the buffer */
2699     bp = buf + leftover_len;
2700     buf_len=leftover_len;
2701     for (i=0; i<count; i++)
2702     {
2703         // ignore these
2704         if (data[i] == '\r')
2705             continue;
2706
2707         // join lines split by ICS?
2708         if (!appData.noJoin)
2709         {
2710             /*
2711                 Joining just consists of finding matches against the
2712                 continuation sequence, and discarding that sequence
2713                 if found instead of copying it.  So, until a match
2714                 fails, there's nothing to do since it might be the
2715                 complete sequence, and thus, something we don't want
2716                 copied.
2717             */
2718             if (data[i] == cont_seq[cmatch])
2719             {
2720                 cmatch++;
2721                 if (cmatch == strlen(cont_seq))
2722                 {
2723                     cmatch = 0; // complete match.  just reset the counter
2724
2725                     /*
2726                         it's possible for the ICS to not include the space
2727                         at the end of the last word, making our [correct]
2728                         join operation fuse two separate words.  the server
2729                         does this when the space occurs at the width setting.
2730                     */
2731                     if (!buf_len || buf[buf_len-1] != ' ')
2732                     {
2733                         *bp++ = ' ';
2734                         buf_len++;
2735                     }
2736                 }
2737                 continue;
2738             }
2739             else if (cmatch)
2740             {
2741                 /*
2742                     match failed, so we have to copy what matched before
2743                     falling through and copying this character.  In reality,
2744                     this will only ever be just the newline character, but
2745                     it doesn't hurt to be precise.
2746                 */
2747                 strncpy(bp, cont_seq, cmatch);
2748                 bp += cmatch;
2749                 buf_len += cmatch;
2750                 cmatch = 0;
2751             }
2752         }
2753
2754         // copy this char
2755         *bp++ = data[i];
2756         buf_len++;
2757     }
2758
2759         buf[buf_len] = NULLCHAR;
2760 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2761         next_out = 0;
2762         leftover_start = 0;
2763
2764         i = 0;
2765         while (i < buf_len) {
2766             /* Deal with part of the TELNET option negotiation
2767                protocol.  We refuse to do anything beyond the
2768                defaults, except that we allow the WILL ECHO option,
2769                which ICS uses to turn off password echoing when we are
2770                directly connected to it.  We reject this option
2771                if localLineEditing mode is on (always on in xboard)
2772                and we are talking to port 23, which might be a real
2773                telnet server that will try to keep WILL ECHO on permanently.
2774              */
2775             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2776                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2777                 unsigned char option;
2778                 oldi = i;
2779                 switch ((unsigned char) buf[++i]) {
2780                   case TN_WILL:
2781                     if (appData.debugMode)
2782                       fprintf(debugFP, "\n<WILL ");
2783                     switch (option = (unsigned char) buf[++i]) {
2784                       case TN_ECHO:
2785                         if (appData.debugMode)
2786                           fprintf(debugFP, "ECHO ");
2787                         /* Reply only if this is a change, according
2788                            to the protocol rules. */
2789                         if (remoteEchoOption) break;
2790                         if (appData.localLineEditing &&
2791                             atoi(appData.icsPort) == TN_PORT) {
2792                             TelnetRequest(TN_DONT, TN_ECHO);
2793                         } else {
2794                             EchoOff();
2795                             TelnetRequest(TN_DO, TN_ECHO);
2796                             remoteEchoOption = TRUE;
2797                         }
2798                         break;
2799                       default:
2800                         if (appData.debugMode)
2801                           fprintf(debugFP, "%d ", option);
2802                         /* Whatever this is, we don't want it. */
2803                         TelnetRequest(TN_DONT, option);
2804                         break;
2805                     }
2806                     break;
2807                   case TN_WONT:
2808                     if (appData.debugMode)
2809                       fprintf(debugFP, "\n<WONT ");
2810                     switch (option = (unsigned char) buf[++i]) {
2811                       case TN_ECHO:
2812                         if (appData.debugMode)
2813                           fprintf(debugFP, "ECHO ");
2814                         /* Reply only if this is a change, according
2815                            to the protocol rules. */
2816                         if (!remoteEchoOption) break;
2817                         EchoOn();
2818                         TelnetRequest(TN_DONT, TN_ECHO);
2819                         remoteEchoOption = FALSE;
2820                         break;
2821                       default:
2822                         if (appData.debugMode)
2823                           fprintf(debugFP, "%d ", (unsigned char) option);
2824                         /* Whatever this is, it must already be turned
2825                            off, because we never agree to turn on
2826                            anything non-default, so according to the
2827                            protocol rules, we don't reply. */
2828                         break;
2829                     }
2830                     break;
2831                   case TN_DO:
2832                     if (appData.debugMode)
2833                       fprintf(debugFP, "\n<DO ");
2834                     switch (option = (unsigned char) buf[++i]) {
2835                       default:
2836                         /* Whatever this is, we refuse to do it. */
2837                         if (appData.debugMode)
2838                           fprintf(debugFP, "%d ", option);
2839                         TelnetRequest(TN_WONT, option);
2840                         break;
2841                     }
2842                     break;
2843                   case TN_DONT:
2844                     if (appData.debugMode)
2845                       fprintf(debugFP, "\n<DONT ");
2846                     switch (option = (unsigned char) buf[++i]) {
2847                       default:
2848                         if (appData.debugMode)
2849                           fprintf(debugFP, "%d ", option);
2850                         /* Whatever this is, we are already not doing
2851                            it, because we never agree to do anything
2852                            non-default, so according to the protocol
2853                            rules, we don't reply. */
2854                         break;
2855                     }
2856                     break;
2857                   case TN_IAC:
2858                     if (appData.debugMode)
2859                       fprintf(debugFP, "\n<IAC ");
2860                     /* Doubled IAC; pass it through */
2861                     i--;
2862                     break;
2863                   default:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2866                     /* Drop all other telnet commands on the floor */
2867                     break;
2868                 }
2869                 if (oldi > next_out)
2870                   SendToPlayer(&buf[next_out], oldi - next_out);
2871                 if (++i > next_out)
2872                   next_out = i;
2873                 continue;
2874             }
2875
2876             /* OK, this at least will *usually* work */
2877             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2878                 loggedOn = TRUE;
2879             }
2880
2881             if (loggedOn && !intfSet) {
2882                 if (ics_type == ICS_ICC) {
2883                   snprintf(str, MSG_SIZ,
2884                           "/set-quietly interface %s\n/set-quietly style 12\n",
2885                           programVersion);
2886                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2887                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2888                 } else if (ics_type == ICS_CHESSNET) {
2889                   snprintf(str, MSG_SIZ, "/style 12\n");
2890                 } else {
2891                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2892                   strcat(str, programVersion);
2893                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2894                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2895                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2896 #ifdef WIN32
2897                   strcat(str, "$iset nohighlight 1\n");
2898 #endif
2899                   strcat(str, "$iset lock 1\n$style 12\n");
2900                 }
2901                 SendToICS(str);
2902                 NotifyFrontendLogin();
2903                 intfSet = TRUE;
2904             }
2905
2906             if (started == STARTED_COMMENT) {
2907                 /* Accumulate characters in comment */
2908                 parse[parse_pos++] = buf[i];
2909                 if (buf[i] == '\n') {
2910                     parse[parse_pos] = NULLCHAR;
2911                     if(chattingPartner>=0) {
2912                         char mess[MSG_SIZ];
2913                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2914                         OutputChatMessage(chattingPartner, mess);
2915                         chattingPartner = -1;
2916                         next_out = i+1; // [HGM] suppress printing in ICS window
2917                     } else
2918                     if(!suppressKibitz) // [HGM] kibitz
2919                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2920                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2921                         int nrDigit = 0, nrAlph = 0, j;
2922                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2923                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2924                         parse[parse_pos] = NULLCHAR;
2925                         // try to be smart: if it does not look like search info, it should go to
2926                         // ICS interaction window after all, not to engine-output window.
2927                         for(j=0; j<parse_pos; j++) { // count letters and digits
2928                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2929                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2930                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2931                         }
2932                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2933                             int depth=0; float score;
2934                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2935                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2936                                 pvInfoList[forwardMostMove-1].depth = depth;
2937                                 pvInfoList[forwardMostMove-1].score = 100*score;
2938                             }
2939                             OutputKibitz(suppressKibitz, parse);
2940                         } else {
2941                             char tmp[MSG_SIZ];
2942                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2943                             SendToPlayer(tmp, strlen(tmp));
2944                         }
2945                         next_out = i+1; // [HGM] suppress printing in ICS window
2946                     }
2947                     started = STARTED_NONE;
2948                 } else {
2949                     /* Don't match patterns against characters in comment */
2950                     i++;
2951                     continue;
2952                 }
2953             }
2954             if (started == STARTED_CHATTER) {
2955                 if (buf[i] != '\n') {
2956                     /* Don't match patterns against characters in chatter */
2957                     i++;
2958                     continue;
2959                 }
2960                 started = STARTED_NONE;
2961                 if(suppressKibitz) next_out = i+1;
2962             }
2963
2964             /* Kludge to deal with rcmd protocol */
2965             if (firstTime && looking_at(buf, &i, "\001*")) {
2966                 DisplayFatalError(&buf[1], 0, 1);
2967                 continue;
2968             } else {
2969                 firstTime = FALSE;
2970             }
2971
2972             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2973                 ics_type = ICS_ICC;
2974                 ics_prefix = "/";
2975                 if (appData.debugMode)
2976                   fprintf(debugFP, "ics_type %d\n", ics_type);
2977                 continue;
2978             }
2979             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2980                 ics_type = ICS_FICS;
2981                 ics_prefix = "$";
2982                 if (appData.debugMode)
2983                   fprintf(debugFP, "ics_type %d\n", ics_type);
2984                 continue;
2985             }
2986             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2987                 ics_type = ICS_CHESSNET;
2988                 ics_prefix = "/";
2989                 if (appData.debugMode)
2990                   fprintf(debugFP, "ics_type %d\n", ics_type);
2991                 continue;
2992             }
2993
2994             if (!loggedOn &&
2995                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2996                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2997                  looking_at(buf, &i, "will be \"*\""))) {
2998               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2999               continue;
3000             }
3001
3002             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3003               char buf[MSG_SIZ];
3004               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3005               DisplayIcsInteractionTitle(buf);
3006               have_set_title = TRUE;
3007             }
3008
3009             /* skip finger notes */
3010             if (started == STARTED_NONE &&
3011                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3012                  (buf[i] == '1' && buf[i+1] == '0')) &&
3013                 buf[i+2] == ':' && buf[i+3] == ' ') {
3014               started = STARTED_CHATTER;
3015               i += 3;
3016               continue;
3017             }
3018
3019             oldi = i;
3020             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3021             if(appData.seekGraph) {
3022                 if(soughtPending && MatchSoughtLine(buf+i)) {
3023                     i = strstr(buf+i, "rated") - buf;
3024                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3025                     next_out = leftover_start = i;
3026                     started = STARTED_CHATTER;
3027                     suppressKibitz = TRUE;
3028                     continue;
3029                 }
3030                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3031                         && looking_at(buf, &i, "* ads displayed")) {
3032                     soughtPending = FALSE;
3033                     seekGraphUp = TRUE;
3034                     DrawSeekGraph();
3035                     continue;
3036                 }
3037                 if(appData.autoRefresh) {
3038                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3039                         int s = (ics_type == ICS_ICC); // ICC format differs
3040                         if(seekGraphUp)
3041                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3042                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3043                         looking_at(buf, &i, "*% "); // eat prompt
3044                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3045                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3046                         next_out = i; // suppress
3047                         continue;
3048                     }
3049                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3050                         char *p = star_match[0];
3051                         while(*p) {
3052                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3053                             while(*p && *p++ != ' '); // next
3054                         }
3055                         looking_at(buf, &i, "*% "); // eat prompt
3056                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3057                         next_out = i;
3058                         continue;
3059                     }
3060                 }
3061             }
3062
3063             /* skip formula vars */
3064             if (started == STARTED_NONE &&
3065                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3066               started = STARTED_CHATTER;
3067               i += 3;
3068               continue;
3069             }
3070
3071             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3072             if (appData.autoKibitz && started == STARTED_NONE &&
3073                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3074                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3075                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3076                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3077                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3078                         suppressKibitz = TRUE;
3079                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080                         next_out = i;
3081                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3082                                 && (gameMode == IcsPlayingWhite)) ||
3083                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3084                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3085                             started = STARTED_CHATTER; // own kibitz we simply discard
3086                         else {
3087                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3088                             parse_pos = 0; parse[0] = NULLCHAR;
3089                             savingComment = TRUE;
3090                             suppressKibitz = gameMode != IcsObserving ? 2 :
3091                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3092                         }
3093                         continue;
3094                 } else
3095                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3096                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3097                          && atoi(star_match[0])) {
3098                     // suppress the acknowledgements of our own autoKibitz
3099                     char *p;
3100                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3102                     SendToPlayer(star_match[0], strlen(star_match[0]));
3103                     if(looking_at(buf, &i, "*% ")) // eat prompt
3104                         suppressKibitz = FALSE;
3105                     next_out = i;
3106                     continue;
3107                 }
3108             } // [HGM] kibitz: end of patch
3109
3110             // [HGM] chat: intercept tells by users for which we have an open chat window
3111             channel = -1;
3112             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3113                                            looking_at(buf, &i, "* whispers:") ||
3114                                            looking_at(buf, &i, "* kibitzes:") ||
3115                                            looking_at(buf, &i, "* shouts:") ||
3116                                            looking_at(buf, &i, "* c-shouts:") ||
3117                                            looking_at(buf, &i, "--> * ") ||
3118                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3119                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3120                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3121                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3122                 int p;
3123                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3124                 chattingPartner = -1;
3125
3126                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3127                 for(p=0; p<MAX_CHAT; p++) {
3128                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3129                     talker[0] = '['; strcat(talker, "] ");
3130                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3131                     chattingPartner = p; break;
3132                     }
3133                 } else
3134                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3135                 for(p=0; p<MAX_CHAT; p++) {
3136                     if(!strcmp("kibitzes", chatPartner[p])) {
3137                         talker[0] = '['; strcat(talker, "] ");
3138                         chattingPartner = p; break;
3139                     }
3140                 } else
3141                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3142                 for(p=0; p<MAX_CHAT; p++) {
3143                     if(!strcmp("whispers", chatPartner[p])) {
3144                         talker[0] = '['; strcat(talker, "] ");
3145                         chattingPartner = p; break;
3146                     }
3147                 } else
3148                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3149                   if(buf[i-8] == '-' && buf[i-3] == 't')
3150                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3151                     if(!strcmp("c-shouts", chatPartner[p])) {
3152                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3153                         chattingPartner = p; break;
3154                     }
3155                   }
3156                   if(chattingPartner < 0)
3157                   for(p=0; p<MAX_CHAT; p++) {
3158                     if(!strcmp("shouts", chatPartner[p])) {
3159                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3160                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3161                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3162                         chattingPartner = p; break;
3163                     }
3164                   }
3165                 }
3166                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3167                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3168                     talker[0] = 0; Colorize(ColorTell, FALSE);
3169                     chattingPartner = p; break;
3170                 }
3171                 if(chattingPartner<0) i = oldi; else {
3172                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3173                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3174                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175                     started = STARTED_COMMENT;
3176                     parse_pos = 0; parse[0] = NULLCHAR;
3177                     savingComment = 3 + chattingPartner; // counts as TRUE
3178                     suppressKibitz = TRUE;
3179                     continue;
3180                 }
3181             } // [HGM] chat: end of patch
3182
3183           backup = i;
3184             if (appData.zippyTalk || appData.zippyPlay) {
3185                 /* [DM] Backup address for color zippy lines */
3186 #if ZIPPY
3187                if (loggedOn == TRUE)
3188                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3189                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3190 #endif
3191             } // [DM] 'else { ' deleted
3192                 if (
3193                     /* Regular tells and says */
3194                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3195                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3196                     looking_at(buf, &i, "* says: ") ||
3197                     /* Don't color "message" or "messages" output */
3198                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3199                     looking_at(buf, &i, "*. * at *:*: ") ||
3200                     looking_at(buf, &i, "--* (*:*): ") ||
3201                     /* Message notifications (same color as tells) */
3202                     looking_at(buf, &i, "* has left a message ") ||
3203                     looking_at(buf, &i, "* just sent you a message:\n") ||
3204                     /* Whispers and kibitzes */
3205                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3206                     looking_at(buf, &i, "* kibitzes: ") ||
3207                     /* Channel tells */
3208                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3209
3210                   if (tkind == 1 && strchr(star_match[0], ':')) {
3211                       /* Avoid "tells you:" spoofs in channels */
3212                      tkind = 3;
3213                   }
3214                   if (star_match[0][0] == NULLCHAR ||
3215                       strchr(star_match[0], ' ') ||
3216                       (tkind == 3 && strchr(star_match[1], ' '))) {
3217                     /* Reject bogus matches */
3218                     i = oldi;
3219                   } else {
3220                     if (appData.colorize) {
3221                       if (oldi > next_out) {
3222                         SendToPlayer(&buf[next_out], oldi - next_out);
3223                         next_out = oldi;
3224                       }
3225                       switch (tkind) {
3226                       case 1:
3227                         Colorize(ColorTell, FALSE);
3228                         curColor = ColorTell;
3229                         break;
3230                       case 2:
3231                         Colorize(ColorKibitz, FALSE);
3232                         curColor = ColorKibitz;
3233                         break;
3234                       case 3:
3235                         p = strrchr(star_match[1], '(');
3236                         if (p == NULL) {
3237                           p = star_match[1];
3238                         } else {
3239                           p++;
3240                         }
3241                         if (atoi(p) == 1) {
3242                           Colorize(ColorChannel1, FALSE);
3243                           curColor = ColorChannel1;
3244                         } else {
3245                           Colorize(ColorChannel, FALSE);
3246                           curColor = ColorChannel;
3247                         }
3248                         break;
3249                       case 5:
3250                         curColor = ColorNormal;
3251                         break;
3252                       }
3253                     }
3254                     if (started == STARTED_NONE && appData.autoComment &&
3255                         (gameMode == IcsObserving ||
3256                          gameMode == IcsPlayingWhite ||
3257                          gameMode == IcsPlayingBlack)) {
3258                       parse_pos = i - oldi;
3259                       memcpy(parse, &buf[oldi], parse_pos);
3260                       parse[parse_pos] = NULLCHAR;
3261                       started = STARTED_COMMENT;
3262                       savingComment = TRUE;
3263                     } else {
3264                       started = STARTED_CHATTER;
3265                       savingComment = FALSE;
3266                     }
3267                     loggedOn = TRUE;
3268                     continue;
3269                   }
3270                 }
3271
3272                 if (looking_at(buf, &i, "* s-shouts: ") ||
3273                     looking_at(buf, &i, "* c-shouts: ")) {
3274                     if (appData.colorize) {
3275                         if (oldi > next_out) {
3276                             SendToPlayer(&buf[next_out], oldi - next_out);
3277                             next_out = oldi;
3278                         }
3279                         Colorize(ColorSShout, FALSE);
3280                         curColor = ColorSShout;
3281                     }
3282                     loggedOn = TRUE;
3283                     started = STARTED_CHATTER;
3284                     continue;
3285                 }
3286
3287                 if (looking_at(buf, &i, "--->")) {
3288                     loggedOn = TRUE;
3289                     continue;
3290                 }
3291
3292                 if (looking_at(buf, &i, "* shouts: ") ||
3293                     looking_at(buf, &i, "--> ")) {
3294                     if (appData.colorize) {
3295                         if (oldi > next_out) {
3296                             SendToPlayer(&buf[next_out], oldi - next_out);
3297                             next_out = oldi;
3298                         }
3299                         Colorize(ColorShout, FALSE);
3300                         curColor = ColorShout;
3301                     }
3302                     loggedOn = TRUE;
3303                     started = STARTED_CHATTER;
3304                     continue;
3305                 }
3306
3307                 if (looking_at( buf, &i, "Challenge:")) {
3308                     if (appData.colorize) {
3309                         if (oldi > next_out) {
3310                             SendToPlayer(&buf[next_out], oldi - next_out);
3311                             next_out = oldi;
3312                         }
3313                         Colorize(ColorChallenge, FALSE);
3314                         curColor = ColorChallenge;
3315                     }
3316                     loggedOn = TRUE;
3317                     continue;
3318                 }
3319
3320                 if (looking_at(buf, &i, "* offers you") ||
3321                     looking_at(buf, &i, "* offers to be") ||
3322                     looking_at(buf, &i, "* would like to") ||
3323                     looking_at(buf, &i, "* requests to") ||
3324                     looking_at(buf, &i, "Your opponent offers") ||
3325                     looking_at(buf, &i, "Your opponent requests")) {
3326
3327                     if (appData.colorize) {
3328                         if (oldi > next_out) {
3329                             SendToPlayer(&buf[next_out], oldi - next_out);
3330                             next_out = oldi;
3331                         }
3332                         Colorize(ColorRequest, FALSE);
3333                         curColor = ColorRequest;
3334                     }
3335                     continue;
3336                 }
3337
3338                 if (looking_at(buf, &i, "* (*) seeking")) {
3339                     if (appData.colorize) {
3340                         if (oldi > next_out) {
3341                             SendToPlayer(&buf[next_out], oldi - next_out);
3342                             next_out = oldi;
3343                         }
3344                         Colorize(ColorSeek, FALSE);
3345                         curColor = ColorSeek;
3346                     }
3347                     continue;
3348             }
3349
3350           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3351
3352             if (looking_at(buf, &i, "\\   ")) {
3353                 if (prevColor != ColorNormal) {
3354                     if (oldi > next_out) {
3355                         SendToPlayer(&buf[next_out], oldi - next_out);
3356                         next_out = oldi;
3357                     }
3358                     Colorize(prevColor, TRUE);
3359                     curColor = prevColor;
3360                 }
3361                 if (savingComment) {
3362                     parse_pos = i - oldi;
3363                     memcpy(parse, &buf[oldi], parse_pos);
3364                     parse[parse_pos] = NULLCHAR;
3365                     started = STARTED_COMMENT;
3366                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3367                         chattingPartner = savingComment - 3; // kludge to remember the box
3368                 } else {
3369                     started = STARTED_CHATTER;
3370                 }
3371                 continue;
3372             }
3373
3374             if (looking_at(buf, &i, "Black Strength :") ||
3375                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3376                 looking_at(buf, &i, "<10>") ||
3377                 looking_at(buf, &i, "#@#")) {
3378                 /* Wrong board style */
3379                 loggedOn = TRUE;
3380                 SendToICS(ics_prefix);
3381                 SendToICS("set style 12\n");
3382                 SendToICS(ics_prefix);
3383                 SendToICS("refresh\n");
3384                 continue;
3385             }
3386
3387             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3388                 ICSInitScript();
3389                 have_sent_ICS_logon = 1;
3390                 continue;
3391             }
3392
3393             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3394                 (looking_at(buf, &i, "\n<12> ") ||
3395                  looking_at(buf, &i, "<12> "))) {
3396                 loggedOn = TRUE;
3397                 if (oldi > next_out) {
3398                     SendToPlayer(&buf[next_out], oldi - next_out);
3399                 }
3400                 next_out = i;
3401                 started = STARTED_BOARD;
3402                 parse_pos = 0;
3403                 continue;
3404             }
3405
3406             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3407                 looking_at(buf, &i, "<b1> ")) {
3408                 if (oldi > next_out) {
3409                     SendToPlayer(&buf[next_out], oldi - next_out);
3410                 }
3411                 next_out = i;
3412                 started = STARTED_HOLDINGS;
3413                 parse_pos = 0;
3414                 continue;
3415             }
3416
3417             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3418                 loggedOn = TRUE;
3419                 /* Header for a move list -- first line */
3420
3421                 switch (ics_getting_history) {
3422                   case H_FALSE:
3423                     switch (gameMode) {
3424                       case IcsIdle:
3425                       case BeginningOfGame:
3426                         /* User typed "moves" or "oldmoves" while we
3427                            were idle.  Pretend we asked for these
3428                            moves and soak them up so user can step
3429                            through them and/or save them.
3430                            */
3431                         Reset(FALSE, TRUE);
3432                         gameMode = IcsObserving;
3433                         ModeHighlight();
3434                         ics_gamenum = -1;
3435                         ics_getting_history = H_GOT_UNREQ_HEADER;
3436                         break;
3437                       case EditGame: /*?*/
3438                       case EditPosition: /*?*/
3439                         /* Should above feature work in these modes too? */
3440                         /* For now it doesn't */
3441                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3442                         break;
3443                       default:
3444                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3445                         break;
3446                     }
3447                     break;
3448                   case H_REQUESTED:
3449                     /* Is this the right one? */
3450                     if (gameInfo.white && gameInfo.black &&
3451                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3452                         strcmp(gameInfo.black, star_match[2]) == 0) {
3453                         /* All is well */
3454                         ics_getting_history = H_GOT_REQ_HEADER;
3455                     }
3456                     break;
3457                   case H_GOT_REQ_HEADER:
3458                   case H_GOT_UNREQ_HEADER:
3459                   case H_GOT_UNWANTED_HEADER:
3460                   case H_GETTING_MOVES:
3461                     /* Should not happen */
3462                     DisplayError(_("Error gathering move list: two headers"), 0);
3463                     ics_getting_history = H_FALSE;
3464                     break;
3465                 }
3466
3467                 /* Save player ratings into gameInfo if needed */
3468                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3469                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3470                     (gameInfo.whiteRating == -1 ||
3471                      gameInfo.blackRating == -1)) {
3472
3473                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3474                     gameInfo.blackRating = string_to_rating(star_match[3]);
3475                     if (appData.debugMode)
3476                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3477                               gameInfo.whiteRating, gameInfo.blackRating);
3478                 }
3479                 continue;
3480             }
3481
3482             if (looking_at(buf, &i,
3483               "* * match, initial time: * minute*, increment: * second")) {
3484                 /* Header for a move list -- second line */
3485                 /* Initial board will follow if this is a wild game */
3486                 if (gameInfo.event != NULL) free(gameInfo.event);
3487                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3488                 gameInfo.event = StrSave(str);
3489                 /* [HGM] we switched variant. Translate boards if needed. */
3490                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3491                 continue;
3492             }
3493
3494             if (looking_at(buf, &i, "Move  ")) {
3495                 /* Beginning of a move list */
3496                 switch (ics_getting_history) {
3497                   case H_FALSE:
3498                     /* Normally should not happen */
3499                     /* Maybe user hit reset while we were parsing */
3500                     break;
3501                   case H_REQUESTED:
3502                     /* Happens if we are ignoring a move list that is not
3503                      * the one we just requested.  Common if the user
3504                      * tries to observe two games without turning off
3505                      * getMoveList */
3506                     break;
3507                   case H_GETTING_MOVES:
3508                     /* Should not happen */
3509                     DisplayError(_("Error gathering move list: nested"), 0);
3510                     ics_getting_history = H_FALSE;
3511                     break;
3512                   case H_GOT_REQ_HEADER:
3513                     ics_getting_history = H_GETTING_MOVES;
3514                     started = STARTED_MOVES;
3515                     parse_pos = 0;
3516                     if (oldi > next_out) {
3517                         SendToPlayer(&buf[next_out], oldi - next_out);
3518                     }
3519                     break;
3520                   case H_GOT_UNREQ_HEADER:
3521                     ics_getting_history = H_GETTING_MOVES;
3522                     started = STARTED_MOVES_NOHIDE;
3523                     parse_pos = 0;
3524                     break;
3525                   case H_GOT_UNWANTED_HEADER:
3526                     ics_getting_history = H_FALSE;
3527                     break;
3528                 }
3529                 continue;
3530             }
3531
3532             if (looking_at(buf, &i, "% ") ||
3533                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3534                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3535                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3536                     soughtPending = FALSE;
3537                     seekGraphUp = TRUE;
3538                     DrawSeekGraph();
3539                 }
3540                 if(suppressKibitz) next_out = i;
3541                 savingComment = FALSE;
3542                 suppressKibitz = 0;
3543                 switch (started) {
3544                   case STARTED_MOVES:
3545                   case STARTED_MOVES_NOHIDE:
3546                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3547                     parse[parse_pos + i - oldi] = NULLCHAR;
3548                     ParseGameHistory(parse);
3549 #if ZIPPY
3550                     if (appData.zippyPlay && first.initDone) {
3551                         FeedMovesToProgram(&first, forwardMostMove);
3552                         if (gameMode == IcsPlayingWhite) {
3553                             if (WhiteOnMove(forwardMostMove)) {
3554                                 if (first.sendTime) {
3555                                   if (first.useColors) {
3556                                     SendToProgram("black\n", &first);
3557                                   }
3558                                   SendTimeRemaining(&first, TRUE);
3559                                 }
3560                                 if (first.useColors) {
3561                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3562                                 }
3563                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3564                                 first.maybeThinking = TRUE;
3565                             } else {
3566                                 if (first.usePlayother) {
3567                                   if (first.sendTime) {
3568                                     SendTimeRemaining(&first, TRUE);
3569                                   }
3570                                   SendToProgram("playother\n", &first);
3571                                   firstMove = FALSE;
3572                                 } else {
3573                                   firstMove = TRUE;
3574                                 }
3575                             }
3576                         } else if (gameMode == IcsPlayingBlack) {
3577                             if (!WhiteOnMove(forwardMostMove)) {
3578                                 if (first.sendTime) {
3579                                   if (first.useColors) {
3580                                     SendToProgram("white\n", &first);
3581                                   }
3582                                   SendTimeRemaining(&first, FALSE);
3583                                 }
3584                                 if (first.useColors) {
3585                                   SendToProgram("black\n", &first);
3586                                 }
3587                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3588                                 first.maybeThinking = TRUE;
3589                             } else {
3590                                 if (first.usePlayother) {
3591                                   if (first.sendTime) {
3592                                     SendTimeRemaining(&first, FALSE);
3593                                   }
3594                                   SendToProgram("playother\n", &first);
3595                                   firstMove = FALSE;
3596                                 } else {
3597                                   firstMove = TRUE;
3598                                 }
3599                             }
3600                         }
3601                     }
3602 #endif
3603                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3604                         /* Moves came from oldmoves or moves command
3605                            while we weren't doing anything else.
3606                            */
3607                         currentMove = forwardMostMove;
3608                         ClearHighlights();/*!!could figure this out*/
3609                         flipView = appData.flipView;
3610                         DrawPosition(TRUE, boards[currentMove]);
3611                         DisplayBothClocks();
3612                         snprintf(str, MSG_SIZ, "%s vs. %s",
3613                                 gameInfo.white, gameInfo.black);
3614                         DisplayTitle(str);
3615                         gameMode = IcsIdle;
3616                     } else {
3617                         /* Moves were history of an active game */
3618                         if (gameInfo.resultDetails != NULL) {
3619                             free(gameInfo.resultDetails);
3620                             gameInfo.resultDetails = NULL;
3621                         }
3622                     }
3623                     HistorySet(parseList, backwardMostMove,
3624                                forwardMostMove, currentMove-1);
3625                     DisplayMove(currentMove - 1);
3626                     if (started == STARTED_MOVES) next_out = i;
3627                     started = STARTED_NONE;
3628                     ics_getting_history = H_FALSE;
3629                     break;
3630
3631                   case STARTED_OBSERVE:
3632                     started = STARTED_NONE;
3633                     SendToICS(ics_prefix);
3634                     SendToICS("refresh\n");
3635                     break;
3636
3637                   default:
3638                     break;
3639                 }
3640                 if(bookHit) { // [HGM] book: simulate book reply
3641                     static char bookMove[MSG_SIZ]; // a bit generous?
3642
3643                     programStats.nodes = programStats.depth = programStats.time =
3644                     programStats.score = programStats.got_only_move = 0;
3645                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3646
3647                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3648                     strcat(bookMove, bookHit);
3649                     HandleMachineMove(bookMove, &first);
3650                 }
3651                 continue;
3652             }
3653
3654             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3655                  started == STARTED_HOLDINGS ||
3656                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3657                 /* Accumulate characters in move list or board */
3658                 parse[parse_pos++] = buf[i];
3659             }
3660
3661             /* Start of game messages.  Mostly we detect start of game
3662                when the first board image arrives.  On some versions
3663                of the ICS, though, we need to do a "refresh" after starting
3664                to observe in order to get the current board right away. */
3665             if (looking_at(buf, &i, "Adding game * to observation list")) {
3666                 started = STARTED_OBSERVE;
3667                 continue;
3668             }
3669
3670             /* Handle auto-observe */
3671             if (appData.autoObserve &&
3672                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3673                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3674                 char *player;
3675                 /* Choose the player that was highlighted, if any. */
3676                 if (star_match[0][0] == '\033' ||
3677                     star_match[1][0] != '\033') {
3678                     player = star_match[0];
3679                 } else {
3680                     player = star_match[2];
3681                 }
3682                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3683                         ics_prefix, StripHighlightAndTitle(player));
3684                 SendToICS(str);
3685
3686                 /* Save ratings from notify string */
3687                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3688                 player1Rating = string_to_rating(star_match[1]);
3689                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3690                 player2Rating = string_to_rating(star_match[3]);
3691
3692                 if (appData.debugMode)
3693                   fprintf(debugFP,
3694                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3695                           player1Name, player1Rating,
3696                           player2Name, player2Rating);
3697
3698                 continue;
3699             }
3700
3701             /* Deal with automatic examine mode after a game,
3702                and with IcsObserving -> IcsExamining transition */
3703             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3704                 looking_at(buf, &i, "has made you an examiner of game *")) {
3705
3706                 int gamenum = atoi(star_match[0]);
3707                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3708                     gamenum == ics_gamenum) {
3709                     /* We were already playing or observing this game;
3710                        no need to refetch history */
3711                     gameMode = IcsExamining;
3712                     if (pausing) {
3713                         pauseExamForwardMostMove = forwardMostMove;
3714                     } else if (currentMove < forwardMostMove) {
3715                         ForwardInner(forwardMostMove);
3716                     }
3717                 } else {
3718                     /* I don't think this case really can happen */
3719                     SendToICS(ics_prefix);
3720                     SendToICS("refresh\n");
3721                 }
3722                 continue;
3723             }
3724
3725             /* Error messages */
3726 //          if (ics_user_moved) {
3727             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3728                 if (looking_at(buf, &i, "Illegal move") ||
3729                     looking_at(buf, &i, "Not a legal move") ||
3730                     looking_at(buf, &i, "Your king is in check") ||
3731                     looking_at(buf, &i, "It isn't your turn") ||
3732                     looking_at(buf, &i, "It is not your move")) {
3733                     /* Illegal move */
3734                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3735                         currentMove = forwardMostMove-1;
3736                         DisplayMove(currentMove - 1); /* before DMError */
3737                         DrawPosition(FALSE, boards[currentMove]);
3738                         SwitchClocks(forwardMostMove-1); // [HGM] race
3739                         DisplayBothClocks();
3740                     }
3741                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3742                     ics_user_moved = 0;
3743                     continue;
3744                 }
3745             }
3746
3747             if (looking_at(buf, &i, "still have time") ||
3748                 looking_at(buf, &i, "not out of time") ||
3749                 looking_at(buf, &i, "either player is out of time") ||
3750                 looking_at(buf, &i, "has timeseal; checking")) {
3751                 /* We must have called his flag a little too soon */
3752                 whiteFlag = blackFlag = FALSE;
3753                 continue;
3754             }
3755
3756             if (looking_at(buf, &i, "added * seconds to") ||
3757                 looking_at(buf, &i, "seconds were added to")) {
3758                 /* Update the clocks */
3759                 SendToICS(ics_prefix);
3760                 SendToICS("refresh\n");
3761                 continue;
3762             }
3763
3764             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3765                 ics_clock_paused = TRUE;
3766                 StopClocks();
3767                 continue;
3768             }
3769
3770             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3771                 ics_clock_paused = FALSE;
3772                 StartClocks();
3773                 continue;
3774             }
3775
3776             /* Grab player ratings from the Creating: message.
3777                Note we have to check for the special case when
3778                the ICS inserts things like [white] or [black]. */
3779             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3780                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3781                 /* star_matches:
3782                    0    player 1 name (not necessarily white)
3783                    1    player 1 rating
3784                    2    empty, white, or black (IGNORED)
3785                    3    player 2 name (not necessarily black)
3786                    4    player 2 rating
3787
3788                    The names/ratings are sorted out when the game
3789                    actually starts (below).
3790                 */
3791                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3792                 player1Rating = string_to_rating(star_match[1]);
3793                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3794                 player2Rating = string_to_rating(star_match[4]);
3795
3796                 if (appData.debugMode)
3797                   fprintf(debugFP,
3798                           "Ratings from 'Creating:' %s %d, %s %d\n",
3799                           player1Name, player1Rating,
3800                           player2Name, player2Rating);
3801
3802                 continue;
3803             }
3804
3805             /* Improved generic start/end-of-game messages */
3806             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3807                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3808                 /* If tkind == 0: */
3809                 /* star_match[0] is the game number */
3810                 /*           [1] is the white player's name */
3811                 /*           [2] is the black player's name */
3812                 /* For end-of-game: */
3813                 /*           [3] is the reason for the game end */
3814                 /*           [4] is a PGN end game-token, preceded by " " */
3815                 /* For start-of-game: */
3816                 /*           [3] begins with "Creating" or "Continuing" */
3817                 /*           [4] is " *" or empty (don't care). */
3818                 int gamenum = atoi(star_match[0]);
3819                 char *whitename, *blackname, *why, *endtoken;
3820                 ChessMove endtype = EndOfFile;
3821
3822                 if (tkind == 0) {
3823                   whitename = star_match[1];
3824                   blackname = star_match[2];
3825                   why = star_match[3];
3826                   endtoken = star_match[4];
3827                 } else {
3828                   whitename = star_match[1];
3829                   blackname = star_match[3];
3830                   why = star_match[5];
3831                   endtoken = star_match[6];
3832                 }
3833
3834                 /* Game start messages */
3835                 if (strncmp(why, "Creating ", 9) == 0 ||
3836                     strncmp(why, "Continuing ", 11) == 0) {
3837                     gs_gamenum = gamenum;
3838                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3839                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3840 #if ZIPPY
3841                     if (appData.zippyPlay) {
3842                         ZippyGameStart(whitename, blackname);
3843                     }
3844 #endif /*ZIPPY*/
3845                     partnerBoardValid = FALSE; // [HGM] bughouse
3846                     continue;
3847                 }
3848
3849                 /* Game end messages */
3850                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3851                     ics_gamenum != gamenum) {
3852                     continue;
3853                 }
3854                 while (endtoken[0] == ' ') endtoken++;
3855                 switch (endtoken[0]) {
3856                   case '*':
3857                   default:
3858                     endtype = GameUnfinished;
3859                     break;
3860                   case '0':
3861                     endtype = BlackWins;
3862                     break;
3863                   case '1':
3864                     if (endtoken[1] == '/')
3865                       endtype = GameIsDrawn;
3866                     else
3867                       endtype = WhiteWins;
3868                     break;
3869                 }
3870                 GameEnds(endtype, why, GE_ICS);
3871 #if ZIPPY
3872                 if (appData.zippyPlay && first.initDone) {
3873                     ZippyGameEnd(endtype, why);
3874                     if (first.pr == NULL) {
3875                       /* Start the next process early so that we'll
3876                          be ready for the next challenge */
3877                       StartChessProgram(&first);
3878                     }
3879                     /* Send "new" early, in case this command takes
3880                        a long time to finish, so that we'll be ready
3881                        for the next challenge. */
3882                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3883                     Reset(TRUE, TRUE);
3884                 }
3885 #endif /*ZIPPY*/
3886                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3887                 continue;
3888             }
3889
3890             if (looking_at(buf, &i, "Removing game * from observation") ||
3891                 looking_at(buf, &i, "no longer observing game *") ||
3892                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3893                 if (gameMode == IcsObserving &&
3894                     atoi(star_match[0]) == ics_gamenum)
3895                   {
3896                       /* icsEngineAnalyze */
3897                       if (appData.icsEngineAnalyze) {
3898                             ExitAnalyzeMode();
3899                             ModeHighlight();
3900                       }
3901                       StopClocks();
3902                       gameMode = IcsIdle;
3903                       ics_gamenum = -1;
3904                       ics_user_moved = FALSE;
3905                   }
3906                 continue;
3907             }
3908
3909             if (looking_at(buf, &i, "no longer examining game *")) {
3910                 if (gameMode == IcsExamining &&
3911                     atoi(star_match[0]) == ics_gamenum)
3912                   {
3913                       gameMode = IcsIdle;
3914                       ics_gamenum = -1;
3915                       ics_user_moved = FALSE;
3916                   }
3917                 continue;
3918             }
3919
3920             /* Advance leftover_start past any newlines we find,
3921                so only partial lines can get reparsed */
3922             if (looking_at(buf, &i, "\n")) {
3923                 prevColor = curColor;
3924                 if (curColor != ColorNormal) {
3925                     if (oldi > next_out) {
3926                         SendToPlayer(&buf[next_out], oldi - next_out);
3927                         next_out = oldi;
3928                     }
3929                     Colorize(ColorNormal, FALSE);
3930                     curColor = ColorNormal;
3931                 }
3932                 if (started == STARTED_BOARD) {
3933                     started = STARTED_NONE;
3934                     parse[parse_pos] = NULLCHAR;
3935                     ParseBoard12(parse);
3936                     ics_user_moved = 0;
3937
3938                     /* Send premove here */
3939                     if (appData.premove) {
3940                       char str[MSG_SIZ];
3941                       if (currentMove == 0 &&
3942                           gameMode == IcsPlayingWhite &&
3943                           appData.premoveWhite) {
3944                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3945                         if (appData.debugMode)
3946                           fprintf(debugFP, "Sending premove:\n");
3947                         SendToICS(str);
3948                       } else if (currentMove == 1 &&
3949                                  gameMode == IcsPlayingBlack &&
3950                                  appData.premoveBlack) {
3951                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3952                         if (appData.debugMode)
3953                           fprintf(debugFP, "Sending premove:\n");
3954                         SendToICS(str);
3955                       } else if (gotPremove) {
3956                         gotPremove = 0;
3957                         ClearPremoveHighlights();
3958                         if (appData.debugMode)
3959                           fprintf(debugFP, "Sending premove:\n");
3960                           UserMoveEvent(premoveFromX, premoveFromY,
3961                                         premoveToX, premoveToY,
3962                                         premovePromoChar);
3963                       }
3964                     }
3965
3966                     /* Usually suppress following prompt */
3967                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3968                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3969                         if (looking_at(buf, &i, "*% ")) {
3970                             savingComment = FALSE;
3971                             suppressKibitz = 0;
3972                         }
3973                     }
3974                     next_out = i;
3975                 } else if (started == STARTED_HOLDINGS) {
3976                     int gamenum;
3977                     char new_piece[MSG_SIZ];
3978                     started = STARTED_NONE;
3979                     parse[parse_pos] = NULLCHAR;
3980                     if (appData.debugMode)
3981                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3982                                                         parse, currentMove);
3983                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3984                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3985                         if (gameInfo.variant == VariantNormal) {
3986                           /* [HGM] We seem to switch variant during a game!
3987                            * Presumably no holdings were displayed, so we have
3988                            * to move the position two files to the right to
3989                            * create room for them!
3990                            */
3991                           VariantClass newVariant;
3992                           switch(gameInfo.boardWidth) { // base guess on board width
3993                                 case 9:  newVariant = VariantShogi; break;
3994                                 case 10: newVariant = VariantGreat; break;
3995                                 default: newVariant = VariantCrazyhouse; break;
3996                           }
3997                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3998                           /* Get a move list just to see the header, which
3999                              will tell us whether this is really bug or zh */
4000                           if (ics_getting_history == H_FALSE) {
4001                             ics_getting_history = H_REQUESTED;
4002                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4003                             SendToICS(str);
4004                           }
4005                         }
4006                         new_piece[0] = NULLCHAR;
4007                         sscanf(parse, "game %d white [%s black [%s <- %s",
4008                                &gamenum, white_holding, black_holding,
4009                                new_piece);
4010                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4011                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4012                         /* [HGM] copy holdings to board holdings area */
4013                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4014                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4015                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4016 #if ZIPPY
4017                         if (appData.zippyPlay && first.initDone) {
4018                             ZippyHoldings(white_holding, black_holding,
4019                                           new_piece);
4020                         }
4021 #endif /*ZIPPY*/
4022                         if (tinyLayout || smallLayout) {
4023                             char wh[16], bh[16];
4024                             PackHolding(wh, white_holding);
4025                             PackHolding(bh, black_holding);
4026                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4027                                     gameInfo.white, gameInfo.black);
4028                         } else {
4029                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4030                                     gameInfo.white, white_holding,
4031                                     gameInfo.black, black_holding);
4032                         }
4033                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4034                         DrawPosition(FALSE, boards[currentMove]);
4035                         DisplayTitle(str);
4036                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4037                         sscanf(parse, "game %d white [%s black [%s <- %s",
4038                                &gamenum, white_holding, black_holding,
4039                                new_piece);
4040                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4041                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4042                         /* [HGM] copy holdings to partner-board holdings area */
4043                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4044                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4045                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4046                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4047                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4048                       }
4049                     }
4050                     /* Suppress following prompt */
4051                     if (looking_at(buf, &i, "*% ")) {
4052                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4053                         savingComment = FALSE;
4054                         suppressKibitz = 0;
4055                     }
4056                     next_out = i;
4057                 }
4058                 continue;
4059             }
4060
4061             i++;                /* skip unparsed character and loop back */
4062         }
4063
4064         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4065 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4066 //          SendToPlayer(&buf[next_out], i - next_out);
4067             started != STARTED_HOLDINGS && leftover_start > next_out) {
4068             SendToPlayer(&buf[next_out], leftover_start - next_out);
4069             next_out = i;
4070         }
4071
4072         leftover_len = buf_len - leftover_start;
4073         /* if buffer ends with something we couldn't parse,
4074            reparse it after appending the next read */
4075
4076     } else if (count == 0) {
4077         RemoveInputSource(isr);
4078         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4079     } else {
4080         DisplayFatalError(_("Error reading from ICS"), error, 1);
4081     }
4082 }
4083
4084
4085 /* Board style 12 looks like this:
4086
4087    <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
4088
4089  * The "<12> " is stripped before it gets to this routine.  The two
4090  * trailing 0's (flip state and clock ticking) are later addition, and
4091  * some chess servers may not have them, or may have only the first.
4092  * Additional trailing fields may be added in the future.
4093  */
4094
4095 #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"
4096
4097 #define RELATION_OBSERVING_PLAYED    0
4098 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4099 #define RELATION_PLAYING_MYMOVE      1
4100 #define RELATION_PLAYING_NOTMYMOVE  -1
4101 #define RELATION_EXAMINING           2
4102 #define RELATION_ISOLATED_BOARD     -3
4103 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4104
4105 void
4106 ParseBoard12(string)
4107      char *string;
4108 {
4109     GameMode newGameMode;
4110     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4111     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4112     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4113     char to_play, board_chars[200];
4114     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4115     char black[32], white[32];
4116     Board board;
4117     int prevMove = currentMove;
4118     int ticking = 2;
4119     ChessMove moveType;
4120     int fromX, fromY, toX, toY;
4121     char promoChar;
4122     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4123     char *bookHit = NULL; // [HGM] book
4124     Boolean weird = FALSE, reqFlag = FALSE;
4125
4126     fromX = fromY = toX = toY = -1;
4127
4128     newGame = FALSE;
4129
4130     if (appData.debugMode)
4131       fprintf(debugFP, _("Parsing board: %s\n"), string);
4132
4133     move_str[0] = NULLCHAR;
4134     elapsed_time[0] = NULLCHAR;
4135     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4136         int  i = 0, j;
4137         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4138             if(string[i] == ' ') { ranks++; files = 0; }
4139             else files++;
4140             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4141             i++;
4142         }
4143         for(j = 0; j <i; j++) board_chars[j] = string[j];
4144         board_chars[i] = '\0';
4145         string += i + 1;
4146     }
4147     n = sscanf(string, PATTERN, &to_play, &double_push,
4148                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4149                &gamenum, white, black, &relation, &basetime, &increment,
4150                &white_stren, &black_stren, &white_time, &black_time,
4151                &moveNum, str, elapsed_time, move_str, &ics_flip,
4152                &ticking);
4153
4154     if (n < 21) {
4155         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4156         DisplayError(str, 0);
4157         return;
4158     }
4159
4160     /* Convert the move number to internal form */
4161     moveNum = (moveNum - 1) * 2;
4162     if (to_play == 'B') moveNum++;
4163     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4164       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4165                         0, 1);
4166       return;
4167     }
4168
4169     switch (relation) {
4170       case RELATION_OBSERVING_PLAYED:
4171       case RELATION_OBSERVING_STATIC:
4172         if (gamenum == -1) {
4173             /* Old ICC buglet */
4174             relation = RELATION_OBSERVING_STATIC;
4175         }
4176         newGameMode = IcsObserving;
4177         break;
4178       case RELATION_PLAYING_MYMOVE:
4179       case RELATION_PLAYING_NOTMYMOVE:
4180         newGameMode =
4181           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4182             IcsPlayingWhite : IcsPlayingBlack;
4183         break;
4184       case RELATION_EXAMINING:
4185         newGameMode = IcsExamining;
4186         break;
4187       case RELATION_ISOLATED_BOARD:
4188       default:
4189         /* Just display this board.  If user was doing something else,
4190            we will forget about it until the next board comes. */
4191         newGameMode = IcsIdle;
4192         break;
4193       case RELATION_STARTING_POSITION:
4194         newGameMode = gameMode;
4195         break;
4196     }
4197
4198     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4199          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4200       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4201       char *toSqr;
4202       for (k = 0; k < ranks; k++) {
4203         for (j = 0; j < files; j++)
4204           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4205         if(gameInfo.holdingsWidth > 1) {
4206              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4207              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4208         }
4209       }
4210       CopyBoard(partnerBoard, board);
4211       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4212         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4213         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4214       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4215       if(toSqr = strchr(str, '-')) {
4216         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4217         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4218       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4219       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4220       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4221       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4222       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4223       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4224                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4225       DisplayMessage(partnerStatus, "");
4226         partnerBoardValid = TRUE;
4227       return;
4228     }
4229
4230     /* Modify behavior for initial board display on move listing
4231        of wild games.
4232        */
4233     switch (ics_getting_history) {
4234       case H_FALSE:
4235       case H_REQUESTED:
4236         break;
4237       case H_GOT_REQ_HEADER:
4238       case H_GOT_UNREQ_HEADER:
4239         /* This is the initial position of the current game */
4240         gamenum = ics_gamenum;
4241         moveNum = 0;            /* old ICS bug workaround */
4242         if (to_play == 'B') {
4243           startedFromSetupPosition = TRUE;
4244           blackPlaysFirst = TRUE;
4245           moveNum = 1;
4246           if (forwardMostMove == 0) forwardMostMove = 1;
4247           if (backwardMostMove == 0) backwardMostMove = 1;
4248           if (currentMove == 0) currentMove = 1;
4249         }
4250         newGameMode = gameMode;
4251         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4252         break;
4253       case H_GOT_UNWANTED_HEADER:
4254         /* This is an initial board that we don't want */
4255         return;
4256       case H_GETTING_MOVES:
4257         /* Should not happen */
4258         DisplayError(_("Error gathering move list: extra board"), 0);
4259         ics_getting_history = H_FALSE;
4260         return;
4261     }
4262
4263    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4264                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4265      /* [HGM] We seem to have switched variant unexpectedly
4266       * Try to guess new variant from board size
4267       */
4268           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4269           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4270           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4271           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4272           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4273           if(!weird) newVariant = VariantNormal;
4274           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4275           /* Get a move list just to see the header, which
4276              will tell us whether this is really bug or zh */
4277           if (ics_getting_history == H_FALSE) {
4278             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4279             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4280             SendToICS(str);
4281           }
4282     }
4283
4284     /* Take action if this is the first board of a new game, or of a
4285        different game than is currently being displayed.  */
4286     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4287         relation == RELATION_ISOLATED_BOARD) {
4288
4289         /* Forget the old game and get the history (if any) of the new one */
4290         if (gameMode != BeginningOfGame) {
4291           Reset(TRUE, TRUE);
4292         }
4293         newGame = TRUE;
4294         if (appData.autoRaiseBoard) BoardToTop();
4295         prevMove = -3;
4296         if (gamenum == -1) {
4297             newGameMode = IcsIdle;
4298         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4299                    appData.getMoveList && !reqFlag) {
4300             /* Need to get game history */
4301             ics_getting_history = H_REQUESTED;
4302             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4303             SendToICS(str);
4304         }
4305
4306         /* Initially flip the board to have black on the bottom if playing
4307            black or if the ICS flip flag is set, but let the user change
4308            it with the Flip View button. */
4309         flipView = appData.autoFlipView ?
4310           (newGameMode == IcsPlayingBlack) || ics_flip :
4311           appData.flipView;
4312
4313         /* Done with values from previous mode; copy in new ones */
4314         gameMode = newGameMode;
4315         ModeHighlight();
4316         ics_gamenum = gamenum;
4317         if (gamenum == gs_gamenum) {
4318             int klen = strlen(gs_kind);
4319             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4320             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4321             gameInfo.event = StrSave(str);
4322         } else {
4323             gameInfo.event = StrSave("ICS game");
4324         }
4325         gameInfo.site = StrSave(appData.icsHost);
4326         gameInfo.date = PGNDate();
4327         gameInfo.round = StrSave("-");
4328         gameInfo.white = StrSave(white);
4329         gameInfo.black = StrSave(black);
4330         timeControl = basetime * 60 * 1000;
4331         timeControl_2 = 0;
4332         timeIncrement = increment * 1000;
4333         movesPerSession = 0;
4334         gameInfo.timeControl = TimeControlTagValue();
4335         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4336   if (appData.debugMode) {
4337     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4338     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4339     setbuf(debugFP, NULL);
4340   }
4341
4342         gameInfo.outOfBook = NULL;
4343
4344         /* Do we have the ratings? */
4345         if (strcmp(player1Name, white) == 0 &&
4346             strcmp(player2Name, black) == 0) {
4347             if (appData.debugMode)
4348               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4349                       player1Rating, player2Rating);
4350             gameInfo.whiteRating = player1Rating;
4351             gameInfo.blackRating = player2Rating;
4352         } else if (strcmp(player2Name, white) == 0 &&
4353                    strcmp(player1Name, black) == 0) {
4354             if (appData.debugMode)
4355               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4356                       player2Rating, player1Rating);
4357             gameInfo.whiteRating = player2Rating;
4358             gameInfo.blackRating = player1Rating;
4359         }
4360         player1Name[0] = player2Name[0] = NULLCHAR;
4361
4362         /* Silence shouts if requested */
4363         if (appData.quietPlay &&
4364             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4365             SendToICS(ics_prefix);
4366             SendToICS("set shout 0\n");
4367         }
4368     }
4369
4370     /* Deal with midgame name changes */
4371     if (!newGame) {
4372         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4373             if (gameInfo.white) free(gameInfo.white);
4374             gameInfo.white = StrSave(white);
4375         }
4376         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4377             if (gameInfo.black) free(gameInfo.black);
4378             gameInfo.black = StrSave(black);
4379         }
4380     }
4381
4382     /* Throw away game result if anything actually changes in examine mode */
4383     if (gameMode == IcsExamining && !newGame) {
4384         gameInfo.result = GameUnfinished;
4385         if (gameInfo.resultDetails != NULL) {
4386             free(gameInfo.resultDetails);
4387             gameInfo.resultDetails = NULL;
4388         }
4389     }
4390
4391     /* In pausing && IcsExamining mode, we ignore boards coming
4392        in if they are in a different variation than we are. */
4393     if (pauseExamInvalid) return;
4394     if (pausing && gameMode == IcsExamining) {
4395         if (moveNum <= pauseExamForwardMostMove) {
4396             pauseExamInvalid = TRUE;
4397             forwardMostMove = pauseExamForwardMostMove;
4398             return;
4399         }
4400     }
4401
4402   if (appData.debugMode) {
4403     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4404   }
4405     /* Parse the board */
4406     for (k = 0; k < ranks; k++) {
4407       for (j = 0; j < files; j++)
4408         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4409       if(gameInfo.holdingsWidth > 1) {
4410            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4411            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4412       }
4413     }
4414     CopyBoard(boards[moveNum], board);
4415     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4416     if (moveNum == 0) {
4417         startedFromSetupPosition =
4418           !CompareBoards(board, initialPosition);
4419         if(startedFromSetupPosition)
4420             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4421     }
4422
4423     /* [HGM] Set castling rights. Take the outermost Rooks,
4424        to make it also work for FRC opening positions. Note that board12
4425        is really defective for later FRC positions, as it has no way to
4426        indicate which Rook can castle if they are on the same side of King.
4427        For the initial position we grant rights to the outermost Rooks,
4428        and remember thos rights, and we then copy them on positions
4429        later in an FRC game. This means WB might not recognize castlings with
4430        Rooks that have moved back to their original position as illegal,
4431        but in ICS mode that is not its job anyway.
4432     */
4433     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4434     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4435
4436         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4437             if(board[0][i] == WhiteRook) j = i;
4438         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4439         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4440             if(board[0][i] == WhiteRook) j = i;
4441         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4442         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4443             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4444         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4445         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4446             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4447         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448
4449         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4450         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4451             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4452         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4453             if(board[BOARD_HEIGHT-1][k] == bKing)
4454                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4455         if(gameInfo.variant == VariantTwoKings) {
4456             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4457             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4458             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4459         }
4460     } else { int r;
4461         r = boards[moveNum][CASTLING][0] = initialRights[0];
4462         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4463         r = boards[moveNum][CASTLING][1] = initialRights[1];
4464         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4465         r = boards[moveNum][CASTLING][3] = initialRights[3];
4466         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4467         r = boards[moveNum][CASTLING][4] = initialRights[4];
4468         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4469         /* wildcastle kludge: always assume King has rights */
4470         r = boards[moveNum][CASTLING][2] = initialRights[2];
4471         r = boards[moveNum][CASTLING][5] = initialRights[5];
4472     }
4473     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4474     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4475
4476
4477     if (ics_getting_history == H_GOT_REQ_HEADER ||
4478         ics_getting_history == H_GOT_UNREQ_HEADER) {
4479         /* This was an initial position from a move list, not
4480            the current position */
4481         return;
4482     }
4483
4484     /* Update currentMove and known move number limits */
4485     newMove = newGame || moveNum > forwardMostMove;
4486
4487     if (newGame) {
4488         forwardMostMove = backwardMostMove = currentMove = moveNum;
4489         if (gameMode == IcsExamining && moveNum == 0) {
4490           /* Workaround for ICS limitation: we are not told the wild
4491              type when starting to examine a game.  But if we ask for
4492              the move list, the move list header will tell us */
4493             ics_getting_history = H_REQUESTED;
4494             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4495             SendToICS(str);
4496         }
4497     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4498                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4499 #if ZIPPY
4500         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4501         /* [HGM] applied this also to an engine that is silently watching        */
4502         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4503             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4504             gameInfo.variant == currentlyInitializedVariant) {
4505           takeback = forwardMostMove - moveNum;
4506           for (i = 0; i < takeback; i++) {
4507             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4508             SendToProgram("undo\n", &first);
4509           }
4510         }
4511 #endif
4512
4513         forwardMostMove = moveNum;
4514         if (!pausing || currentMove > forwardMostMove)
4515           currentMove = forwardMostMove;
4516     } else {
4517         /* New part of history that is not contiguous with old part */
4518         if (pausing && gameMode == IcsExamining) {
4519             pauseExamInvalid = TRUE;
4520             forwardMostMove = pauseExamForwardMostMove;
4521             return;
4522         }
4523         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4524 #if ZIPPY
4525             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4526                 // [HGM] when we will receive the move list we now request, it will be
4527                 // fed to the engine from the first move on. So if the engine is not
4528                 // in the initial position now, bring it there.
4529                 InitChessProgram(&first, 0);
4530             }
4531 #endif
4532             ics_getting_history = H_REQUESTED;
4533             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4534             SendToICS(str);
4535         }
4536         forwardMostMove = backwardMostMove = currentMove = moveNum;
4537     }
4538
4539     /* Update the clocks */
4540     if (strchr(elapsed_time, '.')) {
4541       /* Time is in ms */
4542       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4543       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4544     } else {
4545       /* Time is in seconds */
4546       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4547       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4548     }
4549
4550
4551 #if ZIPPY
4552     if (appData.zippyPlay && newGame &&
4553         gameMode != IcsObserving && gameMode != IcsIdle &&
4554         gameMode != IcsExamining)
4555       ZippyFirstBoard(moveNum, basetime, increment);
4556 #endif
4557
4558     /* Put the move on the move list, first converting
4559        to canonical algebraic form. */
4560     if (moveNum > 0) {
4561   if (appData.debugMode) {
4562     if (appData.debugMode) { int f = forwardMostMove;
4563         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4564                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4565                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4566     }
4567     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4568     fprintf(debugFP, "moveNum = %d\n", moveNum);
4569     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4570     setbuf(debugFP, NULL);
4571   }
4572         if (moveNum <= backwardMostMove) {
4573             /* We don't know what the board looked like before
4574                this move.  Punt. */
4575           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4576             strcat(parseList[moveNum - 1], " ");
4577             strcat(parseList[moveNum - 1], elapsed_time);
4578             moveList[moveNum - 1][0] = NULLCHAR;
4579         } else if (strcmp(move_str, "none") == 0) {
4580             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4581             /* Again, we don't know what the board looked like;
4582                this is really the start of the game. */
4583             parseList[moveNum - 1][0] = NULLCHAR;
4584             moveList[moveNum - 1][0] = NULLCHAR;
4585             backwardMostMove = moveNum;
4586             startedFromSetupPosition = TRUE;
4587             fromX = fromY = toX = toY = -1;
4588         } else {
4589           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4590           //                 So we parse the long-algebraic move string in stead of the SAN move
4591           int valid; char buf[MSG_SIZ], *prom;
4592
4593           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4594                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4595           // str looks something like "Q/a1-a2"; kill the slash
4596           if(str[1] == '/')
4597             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4598           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4599           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4600                 strcat(buf, prom); // long move lacks promo specification!
4601           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4602                 if(appData.debugMode)
4603                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4604                 safeStrCpy(move_str, buf, MSG_SIZ);
4605           }
4606           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4607                                 &fromX, &fromY, &toX, &toY, &promoChar)
4608                || ParseOneMove(buf, moveNum - 1, &moveType,
4609                                 &fromX, &fromY, &toX, &toY, &promoChar);
4610           // end of long SAN patch
4611           if (valid) {
4612             (void) CoordsToAlgebraic(boards[moveNum - 1],
4613                                      PosFlags(moveNum - 1),
4614                                      fromY, fromX, toY, toX, promoChar,
4615                                      parseList[moveNum-1]);
4616             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4617               case MT_NONE:
4618               case MT_STALEMATE:
4619               default:
4620                 break;
4621               case MT_CHECK:
4622                 if(gameInfo.variant != VariantShogi)
4623                     strcat(parseList[moveNum - 1], "+");
4624                 break;
4625               case MT_CHECKMATE:
4626               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4627                 strcat(parseList[moveNum - 1], "#");
4628                 break;
4629             }
4630             strcat(parseList[moveNum - 1], " ");
4631             strcat(parseList[moveNum - 1], elapsed_time);
4632             /* currentMoveString is set as a side-effect of ParseOneMove */
4633             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4634             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4635             strcat(moveList[moveNum - 1], "\n");
4636
4637             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4638                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4639               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4640                 ChessSquare old, new = boards[moveNum][k][j];
4641                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4642                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4643                   if(old == new) continue;
4644                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4645                   else if(new == WhiteWazir || new == BlackWazir) {
4646                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4647                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4648                       else boards[moveNum][k][j] = old; // preserve type of Gold
4649                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4650                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4651               }
4652           } else {
4653             /* Move from ICS was illegal!?  Punt. */
4654             if (appData.debugMode) {
4655               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4656               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4657             }
4658             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4659             strcat(parseList[moveNum - 1], " ");
4660             strcat(parseList[moveNum - 1], elapsed_time);
4661             moveList[moveNum - 1][0] = NULLCHAR;
4662             fromX = fromY = toX = toY = -1;
4663           }
4664         }
4665   if (appData.debugMode) {
4666     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4667     setbuf(debugFP, NULL);
4668   }
4669
4670 #if ZIPPY
4671         /* Send move to chess program (BEFORE animating it). */
4672         if (appData.zippyPlay && !newGame && newMove &&
4673            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4674
4675             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4676                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4677                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4678                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4679                             move_str);
4680                     DisplayError(str, 0);
4681                 } else {
4682                     if (first.sendTime) {
4683                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4684                     }
4685                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4686                     if (firstMove && !bookHit) {
4687                         firstMove = FALSE;
4688                         if (first.useColors) {
4689                           SendToProgram(gameMode == IcsPlayingWhite ?
4690                                         "white\ngo\n" :
4691                                         "black\ngo\n", &first);
4692                         } else {
4693                           SendToProgram("go\n", &first);
4694                         }
4695                         first.maybeThinking = TRUE;
4696                     }
4697                 }
4698             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4699               if (moveList[moveNum - 1][0] == NULLCHAR) {
4700                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4701                 DisplayError(str, 0);
4702               } else {
4703                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4704                 SendMoveToProgram(moveNum - 1, &first);
4705               }
4706             }
4707         }
4708 #endif
4709     }
4710
4711     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4712         /* If move comes from a remote source, animate it.  If it
4713            isn't remote, it will have already been animated. */
4714         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4715             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4716         }
4717         if (!pausing && appData.highlightLastMove) {
4718             SetHighlights(fromX, fromY, toX, toY);
4719         }
4720     }
4721
4722     /* Start the clocks */
4723     whiteFlag = blackFlag = FALSE;
4724     appData.clockMode = !(basetime == 0 && increment == 0);
4725     if (ticking == 0) {
4726       ics_clock_paused = TRUE;
4727       StopClocks();
4728     } else if (ticking == 1) {
4729       ics_clock_paused = FALSE;
4730     }
4731     if (gameMode == IcsIdle ||
4732         relation == RELATION_OBSERVING_STATIC ||
4733         relation == RELATION_EXAMINING ||
4734         ics_clock_paused)
4735       DisplayBothClocks();
4736     else
4737       StartClocks();
4738
4739     /* Display opponents and material strengths */
4740     if (gameInfo.variant != VariantBughouse &&
4741         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4742         if (tinyLayout || smallLayout) {
4743             if(gameInfo.variant == VariantNormal)
4744               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4745                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4746                     basetime, increment);
4747             else
4748               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4749                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4750                     basetime, increment, (int) gameInfo.variant);
4751         } else {
4752             if(gameInfo.variant == VariantNormal)
4753               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4754                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4755                     basetime, increment);
4756             else
4757               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4758                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4759                     basetime, increment, VariantName(gameInfo.variant));
4760         }
4761         DisplayTitle(str);
4762   if (appData.debugMode) {
4763     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4764   }
4765     }
4766
4767
4768     /* Display the board */
4769     if (!pausing && !appData.noGUI) {
4770
4771       if (appData.premove)
4772           if (!gotPremove ||
4773              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4774              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4775               ClearPremoveHighlights();
4776
4777       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4778         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4779       DrawPosition(j, boards[currentMove]);
4780
4781       DisplayMove(moveNum - 1);
4782       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4783             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4784               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4785         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4786       }
4787     }
4788
4789     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4790 #if ZIPPY
4791     if(bookHit) { // [HGM] book: simulate book reply
4792         static char bookMove[MSG_SIZ]; // a bit generous?
4793
4794         programStats.nodes = programStats.depth = programStats.time =
4795         programStats.score = programStats.got_only_move = 0;
4796         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4797
4798         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4799         strcat(bookMove, bookHit);
4800         HandleMachineMove(bookMove, &first);
4801     }
4802 #endif
4803 }
4804
4805 void
4806 GetMoveListEvent()
4807 {
4808     char buf[MSG_SIZ];
4809     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4810         ics_getting_history = H_REQUESTED;
4811         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4812         SendToICS(buf);
4813     }
4814 }
4815
4816 void
4817 AnalysisPeriodicEvent(force)
4818      int force;
4819 {
4820     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4821          && !force) || !appData.periodicUpdates)
4822       return;
4823
4824     /* Send . command to Crafty to collect stats */
4825     SendToProgram(".\n", &first);
4826
4827     /* Don't send another until we get a response (this makes
4828        us stop sending to old Crafty's which don't understand
4829        the "." command (sending illegal cmds resets node count & time,
4830        which looks bad)) */
4831     programStats.ok_to_send = 0;
4832 }
4833
4834 void ics_update_width(new_width)
4835         int new_width;
4836 {
4837         ics_printf("set width %d\n", new_width);
4838 }
4839
4840 void
4841 SendMoveToProgram(moveNum, cps)
4842      int moveNum;
4843      ChessProgramState *cps;
4844 {
4845     char buf[MSG_SIZ];
4846
4847     if (cps->useUsermove) {
4848       SendToProgram("usermove ", cps);
4849     }
4850     if (cps->useSAN) {
4851       char *space;
4852       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4853         int len = space - parseList[moveNum];
4854         memcpy(buf, parseList[moveNum], len);
4855         buf[len++] = '\n';
4856         buf[len] = NULLCHAR;
4857       } else {
4858         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4859       }
4860       SendToProgram(buf, cps);
4861     } else {
4862       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4863         AlphaRank(moveList[moveNum], 4);
4864         SendToProgram(moveList[moveNum], cps);
4865         AlphaRank(moveList[moveNum], 4); // and back
4866       } else
4867       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4868        * the engine. It would be nice to have a better way to identify castle
4869        * moves here. */
4870       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4871                                                                          && cps->useOOCastle) {
4872         int fromX = moveList[moveNum][0] - AAA;
4873         int fromY = moveList[moveNum][1] - ONE;
4874         int toX = moveList[moveNum][2] - AAA;
4875         int toY = moveList[moveNum][3] - ONE;
4876         if((boards[moveNum][fromY][fromX] == WhiteKing
4877             && boards[moveNum][toY][toX] == WhiteRook)
4878            || (boards[moveNum][fromY][fromX] == BlackKing
4879                && boards[moveNum][toY][toX] == BlackRook)) {
4880           if(toX > fromX) SendToProgram("O-O\n", cps);
4881           else SendToProgram("O-O-O\n", cps);
4882         }
4883         else SendToProgram(moveList[moveNum], cps);
4884       }
4885       else SendToProgram(moveList[moveNum], cps);
4886       /* End of additions by Tord */
4887     }
4888
4889     /* [HGM] setting up the opening has brought engine in force mode! */
4890     /*       Send 'go' if we are in a mode where machine should play. */
4891     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4892         (gameMode == TwoMachinesPlay   ||
4893 #if ZIPPY
4894          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4895 #endif
4896          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4897         SendToProgram("go\n", cps);
4898   if (appData.debugMode) {
4899     fprintf(debugFP, "(extra)\n");
4900   }
4901     }
4902     setboardSpoiledMachineBlack = 0;
4903 }
4904
4905 void
4906 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4907      ChessMove moveType;
4908      int fromX, fromY, toX, toY;
4909      char promoChar;
4910 {
4911     char user_move[MSG_SIZ];
4912
4913     switch (moveType) {
4914       default:
4915         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4916                 (int)moveType, fromX, fromY, toX, toY);
4917         DisplayError(user_move + strlen("say "), 0);
4918         break;
4919       case WhiteKingSideCastle:
4920       case BlackKingSideCastle:
4921       case WhiteQueenSideCastleWild:
4922       case BlackQueenSideCastleWild:
4923       /* PUSH Fabien */
4924       case WhiteHSideCastleFR:
4925       case BlackHSideCastleFR:
4926       /* POP Fabien */
4927         snprintf(user_move, MSG_SIZ, "o-o\n");
4928         break;
4929       case WhiteQueenSideCastle:
4930       case BlackQueenSideCastle:
4931       case WhiteKingSideCastleWild:
4932       case BlackKingSideCastleWild:
4933       /* PUSH Fabien */
4934       case WhiteASideCastleFR:
4935       case BlackASideCastleFR:
4936       /* POP Fabien */
4937         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4938         break;
4939       case WhiteNonPromotion:
4940       case BlackNonPromotion:
4941         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4942         break;
4943       case WhitePromotion:
4944       case BlackPromotion:
4945         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4946           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4947                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4948                 PieceToChar(WhiteFerz));
4949         else if(gameInfo.variant == VariantGreat)
4950           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4951                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4952                 PieceToChar(WhiteMan));
4953         else
4954           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4955                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4956                 promoChar);
4957         break;
4958       case WhiteDrop:
4959       case BlackDrop:
4960       drop:
4961         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4962                  ToUpper(PieceToChar((ChessSquare) fromX)),
4963                  AAA + toX, ONE + toY);
4964         break;
4965       case IllegalMove:  /* could be a variant we don't quite understand */
4966         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4967       case NormalMove:
4968       case WhiteCapturesEnPassant:
4969       case BlackCapturesEnPassant:
4970         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4971                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4972         break;
4973     }
4974     SendToICS(user_move);
4975     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4976         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4977 }
4978
4979 void
4980 UploadGameEvent()
4981 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4982     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4983     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4984     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4985         DisplayError("You cannot do this while you are playing or observing", 0);
4986         return;
4987     }
4988     if(gameMode != IcsExamining) { // is this ever not the case?
4989         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4990
4991         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4992           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4993         } else { // on FICS we must first go to general examine mode
4994           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4995         }
4996         if(gameInfo.variant != VariantNormal) {
4997             // try figure out wild number, as xboard names are not always valid on ICS
4998             for(i=1; i<=36; i++) {
4999               snprintf(buf, MSG_SIZ, "wild/%d", i);
5000                 if(StringToVariant(buf) == gameInfo.variant) break;
5001             }
5002             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5003             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5004             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5005         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5006         SendToICS(ics_prefix);
5007         SendToICS(buf);
5008         if(startedFromSetupPosition || backwardMostMove != 0) {
5009           fen = PositionToFEN(backwardMostMove, NULL);
5010           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5011             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5012             SendToICS(buf);
5013           } else { // FICS: everything has to set by separate bsetup commands
5014             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5015             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5016             SendToICS(buf);
5017             if(!WhiteOnMove(backwardMostMove)) {
5018                 SendToICS("bsetup tomove black\n");
5019             }
5020             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5021             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5022             SendToICS(buf);
5023             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5024             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5025             SendToICS(buf);
5026             i = boards[backwardMostMove][EP_STATUS];
5027             if(i >= 0) { // set e.p.
5028               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5029                 SendToICS(buf);
5030             }
5031             bsetup++;
5032           }
5033         }
5034       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5035             SendToICS("bsetup done\n"); // switch to normal examining.
5036     }
5037     for(i = backwardMostMove; i<last; i++) {
5038         char buf[20];
5039         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5040         SendToICS(buf);
5041     }
5042     SendToICS(ics_prefix);
5043     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5044 }
5045
5046 void
5047 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5048      int rf, ff, rt, ft;
5049      char promoChar;
5050      char move[7];
5051 {
5052     if (rf == DROP_RANK) {
5053       sprintf(move, "%c@%c%c\n",
5054                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5055     } else {
5056         if (promoChar == 'x' || promoChar == NULLCHAR) {
5057           sprintf(move, "%c%c%c%c\n",
5058                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5059         } else {
5060             sprintf(move, "%c%c%c%c%c\n",
5061                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5062         }
5063     }
5064 }
5065
5066 void
5067 ProcessICSInitScript(f)
5068      FILE *f;
5069 {
5070     char buf[MSG_SIZ];
5071
5072     while (fgets(buf, MSG_SIZ, f)) {
5073         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5074     }
5075
5076     fclose(f);
5077 }
5078
5079
5080 static int lastX, lastY, selectFlag, dragging;
5081
5082 void
5083 Sweep(int step)
5084 {
5085     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5086     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5087     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5088     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5089     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5090     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5091     do {
5092         promoSweep -= step;
5093         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5094         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5095         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5096         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5097         if(!step) step = 1;
5098     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5099             appData.testLegality && (promoSweep == king ||
5100             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5101     ChangeDragPiece(promoSweep);
5102 }
5103
5104 int PromoScroll(int x, int y)
5105 {
5106   int step = 0;
5107
5108   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5109   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5110   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5111   if(!step) return FALSE;
5112   lastX = x; lastY = y;
5113   if((promoSweep < BlackPawn) == flipView) step = -step;
5114   if(step > 0) selectFlag = 1;
5115   if(!selectFlag) Sweep(step);
5116   return FALSE;
5117 }
5118
5119 void
5120 NextPiece(int step)
5121 {
5122     ChessSquare piece = boards[currentMove][toY][toX];
5123     do {
5124         pieceSweep -= step;
5125         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5126         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5127         if(!step) step = -1;
5128     } while(PieceToChar(pieceSweep) == '.');
5129     boards[currentMove][toY][toX] = pieceSweep;
5130     DrawPosition(FALSE, boards[currentMove]);
5131     boards[currentMove][toY][toX] = piece;
5132 }
5133 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5134 void
5135 AlphaRank(char *move, int n)
5136 {
5137 //    char *p = move, c; int x, y;
5138
5139     if (appData.debugMode) {
5140         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5141     }
5142
5143     if(move[1]=='*' &&
5144        move[2]>='0' && move[2]<='9' &&
5145        move[3]>='a' && move[3]<='x'    ) {
5146         move[1] = '@';
5147         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5148         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5149     } else
5150     if(move[0]>='0' && move[0]<='9' &&
5151        move[1]>='a' && move[1]<='x' &&
5152        move[2]>='0' && move[2]<='9' &&
5153        move[3]>='a' && move[3]<='x'    ) {
5154         /* input move, Shogi -> normal */
5155         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5156         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5157         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5158         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5159     } else
5160     if(move[1]=='@' &&
5161        move[3]>='0' && move[3]<='9' &&
5162        move[2]>='a' && move[2]<='x'    ) {
5163         move[1] = '*';
5164         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5165         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5166     } else
5167     if(
5168        move[0]>='a' && move[0]<='x' &&
5169        move[3]>='0' && move[3]<='9' &&
5170        move[2]>='a' && move[2]<='x'    ) {
5171          /* output move, normal -> Shogi */
5172         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5173         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5174         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5175         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5176         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5177     }
5178     if (appData.debugMode) {
5179         fprintf(debugFP, "   out = '%s'\n", move);
5180     }
5181 }
5182
5183 char yy_textstr[8000];
5184
5185 /* Parser for moves from gnuchess, ICS, or user typein box */
5186 Boolean
5187 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5188      char *move;
5189      int moveNum;
5190      ChessMove *moveType;
5191      int *fromX, *fromY, *toX, *toY;
5192      char *promoChar;
5193 {
5194     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5195
5196     switch (*moveType) {
5197       case WhitePromotion:
5198       case BlackPromotion:
5199       case WhiteNonPromotion:
5200       case BlackNonPromotion:
5201       case NormalMove:
5202       case WhiteCapturesEnPassant:
5203       case BlackCapturesEnPassant:
5204       case WhiteKingSideCastle:
5205       case WhiteQueenSideCastle:
5206       case BlackKingSideCastle:
5207       case BlackQueenSideCastle:
5208       case WhiteKingSideCastleWild:
5209       case WhiteQueenSideCastleWild:
5210       case BlackKingSideCastleWild:
5211       case BlackQueenSideCastleWild:
5212       /* Code added by Tord: */
5213       case WhiteHSideCastleFR:
5214       case WhiteASideCastleFR:
5215       case BlackHSideCastleFR:
5216       case BlackASideCastleFR:
5217       /* End of code added by Tord */
5218       case IllegalMove:         /* bug or odd chess variant */
5219         *fromX = currentMoveString[0] - AAA;
5220         *fromY = currentMoveString[1] - ONE;
5221         *toX = currentMoveString[2] - AAA;
5222         *toY = currentMoveString[3] - ONE;
5223         *promoChar = currentMoveString[4];
5224         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5225             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5226     if (appData.debugMode) {
5227         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5228     }
5229             *fromX = *fromY = *toX = *toY = 0;
5230             return FALSE;
5231         }
5232         if (appData.testLegality) {
5233           return (*moveType != IllegalMove);
5234         } else {
5235           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5236                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5237         }
5238
5239       case WhiteDrop:
5240       case BlackDrop:
5241         *fromX = *moveType == WhiteDrop ?
5242           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5243           (int) CharToPiece(ToLower(currentMoveString[0]));
5244         *fromY = DROP_RANK;
5245         *toX = currentMoveString[2] - AAA;
5246         *toY = currentMoveString[3] - ONE;
5247         *promoChar = NULLCHAR;
5248         return TRUE;
5249
5250       case AmbiguousMove:
5251       case ImpossibleMove:
5252       case EndOfFile:
5253       case ElapsedTime:
5254       case Comment:
5255       case PGNTag:
5256       case NAG:
5257       case WhiteWins:
5258       case BlackWins:
5259       case GameIsDrawn:
5260       default:
5261     if (appData.debugMode) {
5262         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5263     }
5264         /* bug? */
5265         *fromX = *fromY = *toX = *toY = 0;
5266         *promoChar = NULLCHAR;
5267         return FALSE;
5268     }
5269 }
5270
5271 Boolean pushed = FALSE;
5272
5273 void
5274 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5275 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5276   int fromX, fromY, toX, toY; char promoChar;
5277   ChessMove moveType;
5278   Boolean valid;
5279   int nr = 0;
5280
5281   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5282     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5283     pushed = TRUE;
5284   }
5285   endPV = forwardMostMove;
5286   do {
5287     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5288     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5289     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5290 if(appData.debugMode){
5291 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);
5292 }
5293     if(!valid && nr == 0 &&
5294        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5295         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5296         // Hande case where played move is different from leading PV move
5297         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5298         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5299         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5300         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5301           endPV += 2; // if position different, keep this
5302           moveList[endPV-1][0] = fromX + AAA;
5303           moveList[endPV-1][1] = fromY + ONE;
5304           moveList[endPV-1][2] = toX + AAA;
5305           moveList[endPV-1][3] = toY + ONE;
5306           parseList[endPV-1][0] = NULLCHAR;
5307           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5308         }
5309       }
5310     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5311     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5312     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5313     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5314         valid++; // allow comments in PV
5315         continue;
5316     }
5317     nr++;
5318     if(endPV+1 > framePtr) break; // no space, truncate
5319     if(!valid) break;
5320     endPV++;
5321     CopyBoard(boards[endPV], boards[endPV-1]);
5322     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5323     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5324     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5325     CoordsToAlgebraic(boards[endPV - 1],
5326                              PosFlags(endPV - 1),
5327                              fromY, fromX, toY, toX, promoChar,
5328                              parseList[endPV - 1]);
5329   } while(valid);
5330   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5331   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5332   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5333                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5334   DrawPosition(TRUE, boards[currentMove]);
5335 }
5336
5337 int
5338 MultiPV(ChessProgramState *cps)
5339 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5340         int i;
5341         for(i=0; i<cps->nrOptions; i++)
5342             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5343                 return i;
5344         return -1;
5345 }
5346
5347 Boolean
5348 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5349 {
5350         int startPV, multi, lineStart, origIndex = index;
5351         char *p, buf2[MSG_SIZ];
5352
5353         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5354         lastX = x; lastY = y;
5355         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5356         lineStart = startPV = index;
5357         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5358         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5359         index = startPV;
5360         do{ while(buf[index] && buf[index] != '\n') index++;
5361         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5362         buf[index] = 0;
5363         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5364                 int n = first.option[multi].value;
5365                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5366                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5367                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5368                 first.option[multi].value = n;
5369                 *start = *end = 0;
5370                 return FALSE;
5371         }
5372         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5373         *start = startPV; *end = index-1;
5374         return TRUE;
5375 }
5376
5377 Boolean
5378 LoadPV(int x, int y)
5379 { // called on right mouse click to load PV
5380   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5381   lastX = x; lastY = y;
5382   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5383   return TRUE;
5384 }
5385
5386 void
5387 UnLoadPV()
5388 {
5389   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5390   if(endPV < 0) return;
5391   endPV = -1;
5392   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5393         Boolean saveAnimate = appData.animate;
5394         if(pushed) {
5395             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5396                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5397             } else storedGames--; // abandon shelved tail of original game
5398         }
5399         pushed = FALSE;
5400         forwardMostMove = currentMove;
5401         currentMove = oldFMM;
5402         appData.animate = FALSE;
5403         ToNrEvent(forwardMostMove);
5404         appData.animate = saveAnimate;
5405   }
5406   currentMove = forwardMostMove;
5407   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5408   ClearPremoveHighlights();
5409   DrawPosition(TRUE, boards[currentMove]);
5410 }
5411
5412 void
5413 MovePV(int x, int y, int h)
5414 { // step through PV based on mouse coordinates (called on mouse move)
5415   int margin = h>>3, step = 0;
5416
5417   // we must somehow check if right button is still down (might be released off board!)
5418   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5419   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5420   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5421   if(!step) return;
5422   lastX = x; lastY = y;
5423
5424   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5425   if(endPV < 0) return;
5426   if(y < margin) step = 1; else
5427   if(y > h - margin) step = -1;
5428   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5429   currentMove += step;
5430   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5431   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5432                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5433   DrawPosition(FALSE, boards[currentMove]);
5434 }
5435
5436
5437 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5438 // All positions will have equal probability, but the current method will not provide a unique
5439 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5440 #define DARK 1
5441 #define LITE 2
5442 #define ANY 3
5443
5444 int squaresLeft[4];
5445 int piecesLeft[(int)BlackPawn];
5446 int seed, nrOfShuffles;
5447
5448 void GetPositionNumber()
5449 {       // sets global variable seed
5450         int i;
5451
5452         seed = appData.defaultFrcPosition;
5453         if(seed < 0) { // randomize based on time for negative FRC position numbers
5454                 for(i=0; i<50; i++) seed += random();
5455                 seed = random() ^ random() >> 8 ^ random() << 8;
5456                 if(seed<0) seed = -seed;
5457         }
5458 }
5459
5460 int put(Board board, int pieceType, int rank, int n, int shade)
5461 // put the piece on the (n-1)-th empty squares of the given shade
5462 {
5463         int i;
5464
5465         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5466                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5467                         board[rank][i] = (ChessSquare) pieceType;
5468                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5469                         squaresLeft[ANY]--;
5470                         piecesLeft[pieceType]--;
5471                         return i;
5472                 }
5473         }
5474         return -1;
5475 }
5476
5477
5478 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5479 // calculate where the next piece goes, (any empty square), and put it there
5480 {
5481         int i;
5482
5483         i = seed % squaresLeft[shade];
5484         nrOfShuffles *= squaresLeft[shade];
5485         seed /= squaresLeft[shade];
5486         put(board, pieceType, rank, i, shade);
5487 }
5488
5489 void AddTwoPieces(Board board, int pieceType, int rank)
5490 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5491 {
5492         int i, n=squaresLeft[ANY], j=n-1, k;
5493
5494         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5495         i = seed % k;  // pick one
5496         nrOfShuffles *= k;
5497         seed /= k;
5498         while(i >= j) i -= j--;
5499         j = n - 1 - j; i += j;
5500         put(board, pieceType, rank, j, ANY);
5501         put(board, pieceType, rank, i, ANY);
5502 }
5503
5504 void SetUpShuffle(Board board, int number)
5505 {
5506         int i, p, first=1;
5507
5508         GetPositionNumber(); nrOfShuffles = 1;
5509
5510         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5511         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5512         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5513
5514         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5515
5516         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5517             p = (int) board[0][i];
5518             if(p < (int) BlackPawn) piecesLeft[p] ++;
5519             board[0][i] = EmptySquare;
5520         }
5521
5522         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5523             // shuffles restricted to allow normal castling put KRR first
5524             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5525                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5526             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5527                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5528             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5529                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5530             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5531                 put(board, WhiteRook, 0, 0, ANY);
5532             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5533         }
5534
5535         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5536             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5537             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5538                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5539                 while(piecesLeft[p] >= 2) {
5540                     AddOnePiece(board, p, 0, LITE);
5541                     AddOnePiece(board, p, 0, DARK);
5542                 }
5543                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5544             }
5545
5546         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5547             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5548             // but we leave King and Rooks for last, to possibly obey FRC restriction
5549             if(p == (int)WhiteRook) continue;
5550             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5551             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5552         }
5553
5554         // now everything is placed, except perhaps King (Unicorn) and Rooks
5555
5556         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5557             // Last King gets castling rights
5558             while(piecesLeft[(int)WhiteUnicorn]) {
5559                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5560                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5561             }
5562
5563             while(piecesLeft[(int)WhiteKing]) {
5564                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5565                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5566             }
5567
5568
5569         } else {
5570             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5571             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5572         }
5573
5574         // Only Rooks can be left; simply place them all
5575         while(piecesLeft[(int)WhiteRook]) {
5576                 i = put(board, WhiteRook, 0, 0, ANY);
5577                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5578                         if(first) {
5579                                 first=0;
5580                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5581                         }
5582                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5583                 }
5584         }
5585         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5586             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5587         }
5588
5589         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5590 }
5591
5592 int SetCharTable( char *table, const char * map )
5593 /* [HGM] moved here from winboard.c because of its general usefulness */
5594 /*       Basically a safe strcpy that uses the last character as King */
5595 {
5596     int result = FALSE; int NrPieces;
5597
5598     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5599                     && NrPieces >= 12 && !(NrPieces&1)) {
5600         int i; /* [HGM] Accept even length from 12 to 34 */
5601
5602         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5603         for( i=0; i<NrPieces/2-1; i++ ) {
5604             table[i] = map[i];
5605             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5606         }
5607         table[(int) WhiteKing]  = map[NrPieces/2-1];
5608         table[(int) BlackKing]  = map[NrPieces-1];
5609
5610         result = TRUE;
5611     }
5612
5613     return result;
5614 }
5615
5616 void Prelude(Board board)
5617 {       // [HGM] superchess: random selection of exo-pieces
5618         int i, j, k; ChessSquare p;
5619         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5620
5621         GetPositionNumber(); // use FRC position number
5622
5623         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5624             SetCharTable(pieceToChar, appData.pieceToCharTable);
5625             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5626                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5627         }
5628
5629         j = seed%4;                 seed /= 4;
5630         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5631         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5632         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5633         j = seed%3 + (seed%3 >= j); seed /= 3;
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;
5638         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5646         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5647         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5648         put(board, exoPieces[0],    0, 0, ANY);
5649         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5650 }
5651
5652 void
5653 InitPosition(redraw)
5654      int redraw;
5655 {
5656     ChessSquare (* pieces)[BOARD_FILES];
5657     int i, j, pawnRow, overrule,
5658     oldx = gameInfo.boardWidth,
5659     oldy = gameInfo.boardHeight,
5660     oldh = gameInfo.holdingsWidth;
5661     static int oldv;
5662
5663     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5664
5665     /* [AS] Initialize pv info list [HGM] and game status */
5666     {
5667         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5668             pvInfoList[i].depth = 0;
5669             boards[i][EP_STATUS] = EP_NONE;
5670             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5671         }
5672
5673         initialRulePlies = 0; /* 50-move counter start */
5674
5675         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5676         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5677     }
5678
5679
5680     /* [HGM] logic here is completely changed. In stead of full positions */
5681     /* the initialized data only consist of the two backranks. The switch */
5682     /* selects which one we will use, which is than copied to the Board   */
5683     /* initialPosition, which for the rest is initialized by Pawns and    */
5684     /* empty squares. This initial position is then copied to boards[0],  */
5685     /* possibly after shuffling, so that it remains available.            */
5686
5687     gameInfo.holdingsWidth = 0; /* default board sizes */
5688     gameInfo.boardWidth    = 8;
5689     gameInfo.boardHeight   = 8;
5690     gameInfo.holdingsSize  = 0;
5691     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5692     for(i=0; i<BOARD_FILES-2; i++)
5693       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5694     initialPosition[EP_STATUS] = EP_NONE;
5695     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5696     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5697          SetCharTable(pieceNickName, appData.pieceNickNames);
5698     else SetCharTable(pieceNickName, "............");
5699     pieces = FIDEArray;
5700
5701     switch (gameInfo.variant) {
5702     case VariantFischeRandom:
5703       shuffleOpenings = TRUE;
5704     default:
5705       break;
5706     case VariantShatranj:
5707       pieces = ShatranjArray;
5708       nrCastlingRights = 0;
5709       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5710       break;
5711     case VariantMakruk:
5712       pieces = makrukArray;
5713       nrCastlingRights = 0;
5714       startedFromSetupPosition = TRUE;
5715       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5716       break;
5717     case VariantTwoKings:
5718       pieces = twoKingsArray;
5719       break;
5720     case VariantCapaRandom:
5721       shuffleOpenings = TRUE;
5722     case VariantCapablanca:
5723       pieces = CapablancaArray;
5724       gameInfo.boardWidth = 10;
5725       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5726       break;
5727     case VariantGothic:
5728       pieces = GothicArray;
5729       gameInfo.boardWidth = 10;
5730       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5731       break;
5732     case VariantSChess:
5733       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5734       gameInfo.holdingsSize = 7;
5735       break;
5736     case VariantJanus:
5737       pieces = JanusArray;
5738       gameInfo.boardWidth = 10;
5739       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5740       nrCastlingRights = 6;
5741         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5742         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5743         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5744         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5745         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5746         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5747       break;
5748     case VariantFalcon:
5749       pieces = FalconArray;
5750       gameInfo.boardWidth = 10;
5751       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5752       break;
5753     case VariantXiangqi:
5754       pieces = XiangqiArray;
5755       gameInfo.boardWidth  = 9;
5756       gameInfo.boardHeight = 10;
5757       nrCastlingRights = 0;
5758       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5759       break;
5760     case VariantShogi:
5761       pieces = ShogiArray;
5762       gameInfo.boardWidth  = 9;
5763       gameInfo.boardHeight = 9;
5764       gameInfo.holdingsSize = 7;
5765       nrCastlingRights = 0;
5766       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5767       break;
5768     case VariantCourier:
5769       pieces = CourierArray;
5770       gameInfo.boardWidth  = 12;
5771       nrCastlingRights = 0;
5772       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5773       break;
5774     case VariantKnightmate:
5775       pieces = KnightmateArray;
5776       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5777       break;
5778     case VariantSpartan:
5779       pieces = SpartanArray;
5780       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5781       break;
5782     case VariantFairy:
5783       pieces = fairyArray;
5784       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5785       break;
5786     case VariantGreat:
5787       pieces = GreatArray;
5788       gameInfo.boardWidth = 10;
5789       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5790       gameInfo.holdingsSize = 8;
5791       break;
5792     case VariantSuper:
5793       pieces = FIDEArray;
5794       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5795       gameInfo.holdingsSize = 8;
5796       startedFromSetupPosition = TRUE;
5797       break;
5798     case VariantCrazyhouse:
5799     case VariantBughouse:
5800       pieces = FIDEArray;
5801       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5802       gameInfo.holdingsSize = 5;
5803       break;
5804     case VariantWildCastle:
5805       pieces = FIDEArray;
5806       /* !!?shuffle with kings guaranteed to be on d or e file */
5807       shuffleOpenings = 1;
5808       break;
5809     case VariantNoCastle:
5810       pieces = FIDEArray;
5811       nrCastlingRights = 0;
5812       /* !!?unconstrained back-rank shuffle */
5813       shuffleOpenings = 1;
5814       break;
5815     }
5816
5817     overrule = 0;
5818     if(appData.NrFiles >= 0) {
5819         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5820         gameInfo.boardWidth = appData.NrFiles;
5821     }
5822     if(appData.NrRanks >= 0) {
5823         gameInfo.boardHeight = appData.NrRanks;
5824     }
5825     if(appData.holdingsSize >= 0) {
5826         i = appData.holdingsSize;
5827         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5828         gameInfo.holdingsSize = i;
5829     }
5830     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5831     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5832         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5833
5834     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5835     if(pawnRow < 1) pawnRow = 1;
5836     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5837
5838     /* User pieceToChar list overrules defaults */
5839     if(appData.pieceToCharTable != NULL)
5840         SetCharTable(pieceToChar, appData.pieceToCharTable);
5841
5842     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5843
5844         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5845             s = (ChessSquare) 0; /* account holding counts in guard band */
5846         for( i=0; i<BOARD_HEIGHT; i++ )
5847             initialPosition[i][j] = s;
5848
5849         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5850         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5851         initialPosition[pawnRow][j] = WhitePawn;
5852         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5853         if(gameInfo.variant == VariantXiangqi) {
5854             if(j&1) {
5855                 initialPosition[pawnRow][j] =
5856                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5857                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5858                    initialPosition[2][j] = WhiteCannon;
5859                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5860                 }
5861             }
5862         }
5863         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5864     }
5865     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5866
5867             j=BOARD_LEFT+1;
5868             initialPosition[1][j] = WhiteBishop;
5869             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5870             j=BOARD_RGHT-2;
5871             initialPosition[1][j] = WhiteRook;
5872             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5873     }
5874
5875     if( nrCastlingRights == -1) {
5876         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5877         /*       This sets default castling rights from none to normal corners   */
5878         /* Variants with other castling rights must set them themselves above    */
5879         nrCastlingRights = 6;
5880
5881         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5882         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5883         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5884         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5885         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5886         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5887      }
5888
5889      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5890      if(gameInfo.variant == VariantGreat) { // promotion commoners
5891         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5892         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5893         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5894         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5895      }
5896      if( gameInfo.variant == VariantSChess ) {
5897       initialPosition[1][0] = BlackMarshall;
5898       initialPosition[2][0] = BlackAngel;
5899       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5900       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5901       initialPosition[1][1] = initialPosition[2][1] = 
5902       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5903      }
5904   if (appData.debugMode) {
5905     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5906   }
5907     if(shuffleOpenings) {
5908         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5909         startedFromSetupPosition = TRUE;
5910     }
5911     if(startedFromPositionFile) {
5912       /* [HGM] loadPos: use PositionFile for every new game */
5913       CopyBoard(initialPosition, filePosition);
5914       for(i=0; i<nrCastlingRights; i++)
5915           initialRights[i] = filePosition[CASTLING][i];
5916       startedFromSetupPosition = TRUE;
5917     }
5918
5919     CopyBoard(boards[0], initialPosition);
5920
5921     if(oldx != gameInfo.boardWidth ||
5922        oldy != gameInfo.boardHeight ||
5923        oldv != gameInfo.variant ||
5924        oldh != gameInfo.holdingsWidth
5925                                          )
5926             InitDrawingSizes(-2 ,0);
5927
5928     oldv = gameInfo.variant;
5929     if (redraw)
5930       DrawPosition(TRUE, boards[currentMove]);
5931 }
5932
5933 void
5934 SendBoard(cps, moveNum)
5935      ChessProgramState *cps;
5936      int moveNum;
5937 {
5938     char message[MSG_SIZ];
5939
5940     if (cps->useSetboard) {
5941       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5942       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5943       SendToProgram(message, cps);
5944       free(fen);
5945
5946     } else {
5947       ChessSquare *bp;
5948       int i, j;
5949       /* Kludge to set black to move, avoiding the troublesome and now
5950        * deprecated "black" command.
5951        */
5952       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5953         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5954
5955       SendToProgram("edit\n", cps);
5956       SendToProgram("#\n", cps);
5957       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5958         bp = &boards[moveNum][i][BOARD_LEFT];
5959         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5960           if ((int) *bp < (int) BlackPawn) {
5961             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5962                     AAA + j, ONE + i);
5963             if(message[0] == '+' || message[0] == '~') {
5964               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5965                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5966                         AAA + j, ONE + i);
5967             }
5968             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5969                 message[1] = BOARD_RGHT   - 1 - j + '1';
5970                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5971             }
5972             SendToProgram(message, cps);
5973           }
5974         }
5975       }
5976
5977       SendToProgram("c\n", cps);
5978       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5979         bp = &boards[moveNum][i][BOARD_LEFT];
5980         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5981           if (((int) *bp != (int) EmptySquare)
5982               && ((int) *bp >= (int) BlackPawn)) {
5983             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5984                     AAA + j, ONE + i);
5985             if(message[0] == '+' || message[0] == '~') {
5986               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5987                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5988                         AAA + j, ONE + i);
5989             }
5990             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5991                 message[1] = BOARD_RGHT   - 1 - j + '1';
5992                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5993             }
5994             SendToProgram(message, cps);
5995           }
5996         }
5997       }
5998
5999       SendToProgram(".\n", cps);
6000     }
6001     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6002 }
6003
6004 ChessSquare
6005 DefaultPromoChoice(int white)
6006 {
6007     ChessSquare result;
6008     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6009         result = WhiteFerz; // no choice
6010     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6011         result= WhiteKing; // in Suicide Q is the last thing we want
6012     else if(gameInfo.variant == VariantSpartan)
6013         result = white ? WhiteQueen : WhiteAngel;
6014     else result = WhiteQueen;
6015     if(!white) result = WHITE_TO_BLACK result;
6016     return result;
6017 }
6018
6019 static int autoQueen; // [HGM] oneclick
6020
6021 int
6022 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6023 {
6024     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6025     /* [HGM] add Shogi promotions */
6026     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6027     ChessSquare piece;
6028     ChessMove moveType;
6029     Boolean premove;
6030
6031     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6032     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6033
6034     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6035       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6036         return FALSE;
6037
6038     piece = boards[currentMove][fromY][fromX];
6039     if(gameInfo.variant == VariantShogi) {
6040         promotionZoneSize = BOARD_HEIGHT/3;
6041         highestPromotingPiece = (int)WhiteFerz;
6042     } else if(gameInfo.variant == VariantMakruk) {
6043         promotionZoneSize = 3;
6044     }
6045
6046     // Treat Lance as Pawn when it is not representing Amazon
6047     if(gameInfo.variant != VariantSuper) {
6048         if(piece == WhiteLance) piece = WhitePawn; else
6049         if(piece == BlackLance) piece = BlackPawn;
6050     }
6051
6052     // next weed out all moves that do not touch the promotion zone at all
6053     if((int)piece >= BlackPawn) {
6054         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6055              return FALSE;
6056         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6057     } else {
6058         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6059            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6060     }
6061
6062     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6063
6064     // weed out mandatory Shogi promotions
6065     if(gameInfo.variant == VariantShogi) {
6066         if(piece >= BlackPawn) {
6067             if(toY == 0 && piece == BlackPawn ||
6068                toY == 0 && piece == BlackQueen ||
6069                toY <= 1 && piece == BlackKnight) {
6070                 *promoChoice = '+';
6071                 return FALSE;
6072             }
6073         } else {
6074             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6075                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6076                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6077                 *promoChoice = '+';
6078                 return FALSE;
6079             }
6080         }
6081     }
6082
6083     // weed out obviously illegal Pawn moves
6084     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6085         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6086         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6087         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6088         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6089         // note we are not allowed to test for valid (non-)capture, due to premove
6090     }
6091
6092     // we either have a choice what to promote to, or (in Shogi) whether to promote
6093     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6094         *promoChoice = PieceToChar(BlackFerz);  // no choice
6095         return FALSE;
6096     }
6097     // no sense asking what we must promote to if it is going to explode...
6098     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6099         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6100         return FALSE;
6101     }
6102     // give caller the default choice even if we will not make it
6103     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6104     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6105     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6106                            && gameInfo.variant != VariantShogi
6107                            && gameInfo.variant != VariantSuper) return FALSE;
6108     if(autoQueen) return FALSE; // predetermined
6109
6110     // suppress promotion popup on illegal moves that are not premoves
6111     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6112               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6113     if(appData.testLegality && !premove) {
6114         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6115                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6116         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6117             return FALSE;
6118     }
6119
6120     return TRUE;
6121 }
6122
6123 int
6124 InPalace(row, column)
6125      int row, column;
6126 {   /* [HGM] for Xiangqi */
6127     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6128          column < (BOARD_WIDTH + 4)/2 &&
6129          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6130     return FALSE;
6131 }
6132
6133 int
6134 PieceForSquare (x, y)
6135      int x;
6136      int y;
6137 {
6138   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6139      return -1;
6140   else
6141      return boards[currentMove][y][x];
6142 }
6143
6144 int
6145 OKToStartUserMove(x, y)
6146      int x, y;
6147 {
6148     ChessSquare from_piece;
6149     int white_piece;
6150
6151     if (matchMode) return FALSE;
6152     if (gameMode == EditPosition) return TRUE;
6153
6154     if (x >= 0 && y >= 0)
6155       from_piece = boards[currentMove][y][x];
6156     else
6157       from_piece = EmptySquare;
6158
6159     if (from_piece == EmptySquare) return FALSE;
6160
6161     white_piece = (int)from_piece >= (int)WhitePawn &&
6162       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6163
6164     switch (gameMode) {
6165       case PlayFromGameFile:
6166       case AnalyzeFile:
6167       case TwoMachinesPlay:
6168       case EndOfGame:
6169         return FALSE;
6170
6171       case IcsObserving:
6172       case IcsIdle:
6173         return FALSE;
6174
6175       case MachinePlaysWhite:
6176       case IcsPlayingBlack:
6177         if (appData.zippyPlay) return FALSE;
6178         if (white_piece) {
6179             DisplayMoveError(_("You are playing Black"));
6180             return FALSE;
6181         }
6182         break;
6183
6184       case MachinePlaysBlack:
6185       case IcsPlayingWhite:
6186         if (appData.zippyPlay) return FALSE;
6187         if (!white_piece) {
6188             DisplayMoveError(_("You are playing White"));
6189             return FALSE;
6190         }
6191         break;
6192
6193       case EditGame:
6194         if (!white_piece && WhiteOnMove(currentMove)) {
6195             DisplayMoveError(_("It is White's turn"));
6196             return FALSE;
6197         }
6198         if (white_piece && !WhiteOnMove(currentMove)) {
6199             DisplayMoveError(_("It is Black's turn"));
6200             return FALSE;
6201         }
6202         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6203             /* Editing correspondence game history */
6204             /* Could disallow this or prompt for confirmation */
6205             cmailOldMove = -1;
6206         }
6207         break;
6208
6209       case BeginningOfGame:
6210         if (appData.icsActive) return FALSE;
6211         if (!appData.noChessProgram) {
6212             if (!white_piece) {
6213                 DisplayMoveError(_("You are playing White"));
6214                 return FALSE;
6215             }
6216         }
6217         break;
6218
6219       case Training:
6220         if (!white_piece && WhiteOnMove(currentMove)) {
6221             DisplayMoveError(_("It is White's turn"));
6222             return FALSE;
6223         }
6224         if (white_piece && !WhiteOnMove(currentMove)) {
6225             DisplayMoveError(_("It is Black's turn"));
6226             return FALSE;
6227         }
6228         break;
6229
6230       default:
6231       case IcsExamining:
6232         break;
6233     }
6234     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6235         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6236         && gameMode != AnalyzeFile && gameMode != Training) {
6237         DisplayMoveError(_("Displayed position is not current"));
6238         return FALSE;
6239     }
6240     return TRUE;
6241 }
6242
6243 Boolean
6244 OnlyMove(int *x, int *y, Boolean captures) {
6245     DisambiguateClosure cl;
6246     if (appData.zippyPlay) return FALSE;
6247     switch(gameMode) {
6248       case MachinePlaysBlack:
6249       case IcsPlayingWhite:
6250       case BeginningOfGame:
6251         if(!WhiteOnMove(currentMove)) return FALSE;
6252         break;
6253       case MachinePlaysWhite:
6254       case IcsPlayingBlack:
6255         if(WhiteOnMove(currentMove)) return FALSE;
6256         break;
6257       case EditGame:
6258         break;
6259       default:
6260         return FALSE;
6261     }
6262     cl.pieceIn = EmptySquare;
6263     cl.rfIn = *y;
6264     cl.ffIn = *x;
6265     cl.rtIn = -1;
6266     cl.ftIn = -1;
6267     cl.promoCharIn = NULLCHAR;
6268     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6269     if( cl.kind == NormalMove ||
6270         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6271         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6272         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6273       fromX = cl.ff;
6274       fromY = cl.rf;
6275       *x = cl.ft;
6276       *y = cl.rt;
6277       return TRUE;
6278     }
6279     if(cl.kind != ImpossibleMove) return FALSE;
6280     cl.pieceIn = EmptySquare;
6281     cl.rfIn = -1;
6282     cl.ffIn = -1;
6283     cl.rtIn = *y;
6284     cl.ftIn = *x;
6285     cl.promoCharIn = NULLCHAR;
6286     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6287     if( cl.kind == NormalMove ||
6288         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6289         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6290         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6291       fromX = cl.ff;
6292       fromY = cl.rf;
6293       *x = cl.ft;
6294       *y = cl.rt;
6295       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6296       return TRUE;
6297     }
6298     return FALSE;
6299 }
6300
6301 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6302 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6303 int lastLoadGameUseList = FALSE;
6304 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6305 ChessMove lastLoadGameStart = EndOfFile;
6306
6307 void
6308 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6309      int fromX, fromY, toX, toY;
6310      int promoChar;
6311 {
6312     ChessMove moveType;
6313     ChessSquare pdown, pup;
6314
6315     /* Check if the user is playing in turn.  This is complicated because we
6316        let the user "pick up" a piece before it is his turn.  So the piece he
6317        tried to pick up may have been captured by the time he puts it down!
6318        Therefore we use the color the user is supposed to be playing in this
6319        test, not the color of the piece that is currently on the starting
6320        square---except in EditGame mode, where the user is playing both
6321        sides; fortunately there the capture race can't happen.  (It can
6322        now happen in IcsExamining mode, but that's just too bad.  The user
6323        will get a somewhat confusing message in that case.)
6324        */
6325
6326     switch (gameMode) {
6327       case PlayFromGameFile:
6328       case AnalyzeFile:
6329       case TwoMachinesPlay:
6330       case EndOfGame:
6331       case IcsObserving:
6332       case IcsIdle:
6333         /* We switched into a game mode where moves are not accepted,
6334            perhaps while the mouse button was down. */
6335         return;
6336
6337       case MachinePlaysWhite:
6338         /* User is moving for Black */
6339         if (WhiteOnMove(currentMove)) {
6340             DisplayMoveError(_("It is White's turn"));
6341             return;
6342         }
6343         break;
6344
6345       case MachinePlaysBlack:
6346         /* User is moving for White */
6347         if (!WhiteOnMove(currentMove)) {
6348             DisplayMoveError(_("It is Black's turn"));
6349             return;
6350         }
6351         break;
6352
6353       case EditGame:
6354       case IcsExamining:
6355       case BeginningOfGame:
6356       case AnalyzeMode:
6357       case Training:
6358         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6359         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6360             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6361             /* User is moving for Black */
6362             if (WhiteOnMove(currentMove)) {
6363                 DisplayMoveError(_("It is White's turn"));
6364                 return;
6365             }
6366         } else {
6367             /* User is moving for White */
6368             if (!WhiteOnMove(currentMove)) {
6369                 DisplayMoveError(_("It is Black's turn"));
6370                 return;
6371             }
6372         }
6373         break;
6374
6375       case IcsPlayingBlack:
6376         /* User is moving for Black */
6377         if (WhiteOnMove(currentMove)) {
6378             if (!appData.premove) {
6379                 DisplayMoveError(_("It is White's turn"));
6380             } else if (toX >= 0 && toY >= 0) {
6381                 premoveToX = toX;
6382                 premoveToY = toY;
6383                 premoveFromX = fromX;
6384                 premoveFromY = fromY;
6385                 premovePromoChar = promoChar;
6386                 gotPremove = 1;
6387                 if (appData.debugMode)
6388                     fprintf(debugFP, "Got premove: fromX %d,"
6389                             "fromY %d, toX %d, toY %d\n",
6390                             fromX, fromY, toX, toY);
6391             }
6392             return;
6393         }
6394         break;
6395
6396       case IcsPlayingWhite:
6397         /* User is moving for White */
6398         if (!WhiteOnMove(currentMove)) {
6399             if (!appData.premove) {
6400                 DisplayMoveError(_("It is Black's turn"));
6401             } else if (toX >= 0 && toY >= 0) {
6402                 premoveToX = toX;
6403                 premoveToY = toY;
6404                 premoveFromX = fromX;
6405                 premoveFromY = fromY;
6406                 premovePromoChar = promoChar;
6407                 gotPremove = 1;
6408                 if (appData.debugMode)
6409                     fprintf(debugFP, "Got premove: fromX %d,"
6410                             "fromY %d, toX %d, toY %d\n",
6411                             fromX, fromY, toX, toY);
6412             }
6413             return;
6414         }
6415         break;
6416
6417       default:
6418         break;
6419
6420       case EditPosition:
6421         /* EditPosition, empty square, or different color piece;
6422            click-click move is possible */
6423         if (toX == -2 || toY == -2) {
6424             boards[0][fromY][fromX] = EmptySquare;
6425             DrawPosition(FALSE, boards[currentMove]);
6426             return;
6427         } else if (toX >= 0 && toY >= 0) {
6428             boards[0][toY][toX] = boards[0][fromY][fromX];
6429             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6430                 if(boards[0][fromY][0] != EmptySquare) {
6431                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6432                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6433                 }
6434             } else
6435             if(fromX == BOARD_RGHT+1) {
6436                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6437                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6438                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6439                 }
6440             } else
6441             boards[0][fromY][fromX] = EmptySquare;
6442             DrawPosition(FALSE, boards[currentMove]);
6443             return;
6444         }
6445         return;
6446     }
6447
6448     if(toX < 0 || toY < 0) return;
6449     pdown = boards[currentMove][fromY][fromX];
6450     pup = boards[currentMove][toY][toX];
6451
6452     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6453     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6454          if( pup != EmptySquare ) return;
6455          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6456            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6457                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6458            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6459            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6460            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6461            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6462          fromY = DROP_RANK;
6463     }
6464
6465     /* [HGM] always test for legality, to get promotion info */
6466     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6467                                          fromY, fromX, toY, toX, promoChar);
6468     /* [HGM] but possibly ignore an IllegalMove result */
6469     if (appData.testLegality) {
6470         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6471             DisplayMoveError(_("Illegal move"));
6472             return;
6473         }
6474     }
6475
6476     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6477 }
6478
6479 /* Common tail of UserMoveEvent and DropMenuEvent */
6480 int
6481 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6482      ChessMove moveType;
6483      int fromX, fromY, toX, toY;
6484      /*char*/int promoChar;
6485 {
6486     char *bookHit = 0;
6487
6488     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6489         // [HGM] superchess: suppress promotions to non-available piece
6490         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6491         if(WhiteOnMove(currentMove)) {
6492             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6493         } else {
6494             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6495         }
6496     }
6497
6498     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6499        move type in caller when we know the move is a legal promotion */
6500     if(moveType == NormalMove && promoChar)
6501         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6502
6503     /* [HGM] <popupFix> The following if has been moved here from
6504        UserMoveEvent(). Because it seemed to belong here (why not allow
6505        piece drops in training games?), and because it can only be
6506        performed after it is known to what we promote. */
6507     if (gameMode == Training) {
6508       /* compare the move played on the board to the next move in the
6509        * game. If they match, display the move and the opponent's response.
6510        * If they don't match, display an error message.
6511        */
6512       int saveAnimate;
6513       Board testBoard;
6514       CopyBoard(testBoard, boards[currentMove]);
6515       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6516
6517       if (CompareBoards(testBoard, boards[currentMove+1])) {
6518         ForwardInner(currentMove+1);
6519
6520         /* Autoplay the opponent's response.
6521          * if appData.animate was TRUE when Training mode was entered,
6522          * the response will be animated.
6523          */
6524         saveAnimate = appData.animate;
6525         appData.animate = animateTraining;
6526         ForwardInner(currentMove+1);
6527         appData.animate = saveAnimate;
6528
6529         /* check for the end of the game */
6530         if (currentMove >= forwardMostMove) {
6531           gameMode = PlayFromGameFile;
6532           ModeHighlight();
6533           SetTrainingModeOff();
6534           DisplayInformation(_("End of game"));
6535         }
6536       } else {
6537         DisplayError(_("Incorrect move"), 0);
6538       }
6539       return 1;
6540     }
6541
6542   /* Ok, now we know that the move is good, so we can kill
6543      the previous line in Analysis Mode */
6544   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6545                                 && currentMove < forwardMostMove) {
6546     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6547     else forwardMostMove = currentMove;
6548   }
6549
6550   /* If we need the chess program but it's dead, restart it */
6551   ResurrectChessProgram();
6552
6553   /* A user move restarts a paused game*/
6554   if (pausing)
6555     PauseEvent();
6556
6557   thinkOutput[0] = NULLCHAR;
6558
6559   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6560
6561   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6562     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6563     return 1;
6564   }
6565
6566   if (gameMode == BeginningOfGame) {
6567     if (appData.noChessProgram) {
6568       gameMode = EditGame;
6569       SetGameInfo();
6570     } else {
6571       char buf[MSG_SIZ];
6572       gameMode = MachinePlaysBlack;
6573       StartClocks();
6574       SetGameInfo();
6575       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6576       DisplayTitle(buf);
6577       if (first.sendName) {
6578         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6579         SendToProgram(buf, &first);
6580       }
6581       StartClocks();
6582     }
6583     ModeHighlight();
6584   }
6585
6586   /* Relay move to ICS or chess engine */
6587   if (appData.icsActive) {
6588     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6589         gameMode == IcsExamining) {
6590       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6591         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6592         SendToICS("draw ");
6593         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6594       }
6595       // also send plain move, in case ICS does not understand atomic claims
6596       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6597       ics_user_moved = 1;
6598     }
6599   } else {
6600     if (first.sendTime && (gameMode == BeginningOfGame ||
6601                            gameMode == MachinePlaysWhite ||
6602                            gameMode == MachinePlaysBlack)) {
6603       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6604     }
6605     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6606          // [HGM] book: if program might be playing, let it use book
6607         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6608         first.maybeThinking = TRUE;
6609     } else SendMoveToProgram(forwardMostMove-1, &first);
6610     if (currentMove == cmailOldMove + 1) {
6611       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6612     }
6613   }
6614
6615   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6616
6617   switch (gameMode) {
6618   case EditGame:
6619     if(appData.testLegality)
6620     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6621     case MT_NONE:
6622     case MT_CHECK:
6623       break;
6624     case MT_CHECKMATE:
6625     case MT_STAINMATE:
6626       if (WhiteOnMove(currentMove)) {
6627         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6628       } else {
6629         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6630       }
6631       break;
6632     case MT_STALEMATE:
6633       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6634       break;
6635     }
6636     break;
6637
6638   case MachinePlaysBlack:
6639   case MachinePlaysWhite:
6640     /* disable certain menu options while machine is thinking */
6641     SetMachineThinkingEnables();
6642     break;
6643
6644   default:
6645     break;
6646   }
6647
6648   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6649   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6650
6651   if(bookHit) { // [HGM] book: simulate book reply
6652         static char bookMove[MSG_SIZ]; // a bit generous?
6653
6654         programStats.nodes = programStats.depth = programStats.time =
6655         programStats.score = programStats.got_only_move = 0;
6656         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6657
6658         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6659         strcat(bookMove, bookHit);
6660         HandleMachineMove(bookMove, &first);
6661   }
6662   return 1;
6663 }
6664
6665 void
6666 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6667      Board board;
6668      int flags;
6669      ChessMove kind;
6670      int rf, ff, rt, ft;
6671      VOIDSTAR closure;
6672 {
6673     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6674     Markers *m = (Markers *) closure;
6675     if(rf == fromY && ff == fromX)
6676         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6677                          || kind == WhiteCapturesEnPassant
6678                          || kind == BlackCapturesEnPassant);
6679     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6680 }
6681
6682 void
6683 MarkTargetSquares(int clear)
6684 {
6685   int x, y;
6686   if(!appData.markers || !appData.highlightDragging ||
6687      !appData.testLegality || gameMode == EditPosition) return;
6688   if(clear) {
6689     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6690   } else {
6691     int capt = 0;
6692     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6693     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6694       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6695       if(capt)
6696       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6697     }
6698   }
6699   DrawPosition(TRUE, NULL);
6700 }
6701
6702 int
6703 Explode(Board board, int fromX, int fromY, int toX, int toY)
6704 {
6705     if(gameInfo.variant == VariantAtomic &&
6706        (board[toY][toX] != EmptySquare ||                     // capture?
6707         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6708                          board[fromY][fromX] == BlackPawn   )
6709       )) {
6710         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6711         return TRUE;
6712     }
6713     return FALSE;
6714 }
6715
6716 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6717
6718 int CanPromote(ChessSquare piece, int y)
6719 {
6720         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6721         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6722         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6723            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6724            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6725                                                   gameInfo.variant == VariantMakruk) return FALSE;
6726         return (piece == BlackPawn && y == 1 ||
6727                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6728                 piece == BlackLance && y == 1 ||
6729                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6730 }
6731
6732 void LeftClick(ClickType clickType, int xPix, int yPix)
6733 {
6734     int x, y;
6735     Boolean saveAnimate;
6736     static int second = 0, promotionChoice = 0, clearFlag = 0;
6737     char promoChoice = NULLCHAR;
6738     ChessSquare piece;
6739
6740     if(appData.seekGraph && appData.icsActive && loggedOn &&
6741         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6742         SeekGraphClick(clickType, xPix, yPix, 0);
6743         return;
6744     }
6745
6746     if (clickType == Press) ErrorPopDown();
6747     MarkTargetSquares(1);
6748
6749     x = EventToSquare(xPix, BOARD_WIDTH);
6750     y = EventToSquare(yPix, BOARD_HEIGHT);
6751     if (!flipView && y >= 0) {
6752         y = BOARD_HEIGHT - 1 - y;
6753     }
6754     if (flipView && x >= 0) {
6755         x = BOARD_WIDTH - 1 - x;
6756     }
6757
6758     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6759         defaultPromoChoice = promoSweep;
6760         promoSweep = EmptySquare;   // terminate sweep
6761         promoDefaultAltered = TRUE;
6762         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6763     }
6764
6765     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6766         if(clickType == Release) return; // ignore upclick of click-click destination
6767         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6768         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6769         if(gameInfo.holdingsWidth &&
6770                 (WhiteOnMove(currentMove)
6771                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6772                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6773             // click in right holdings, for determining promotion piece
6774             ChessSquare p = boards[currentMove][y][x];
6775             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6776             if(p != EmptySquare) {
6777                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6778                 fromX = fromY = -1;
6779                 return;
6780             }
6781         }
6782         DrawPosition(FALSE, boards[currentMove]);
6783         return;
6784     }
6785
6786     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6787     if(clickType == Press
6788             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6789               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6790               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6791         return;
6792
6793     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6794         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6795
6796     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6797         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6798                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6799         defaultPromoChoice = DefaultPromoChoice(side);
6800     }
6801
6802     autoQueen = appData.alwaysPromoteToQueen;
6803
6804     if (fromX == -1) {
6805       int originalY = y;
6806       gatingPiece = EmptySquare;
6807       if (clickType != Press) {
6808         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6809             DragPieceEnd(xPix, yPix); dragging = 0;
6810             DrawPosition(FALSE, NULL);
6811         }
6812         return;
6813       }
6814       fromX = x; fromY = y;
6815       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6816          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6817          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6818             /* First square */
6819             if (OKToStartUserMove(fromX, fromY)) {
6820                 second = 0;
6821                 MarkTargetSquares(0);
6822                 DragPieceBegin(xPix, yPix); dragging = 1;
6823                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6824                     promoSweep = defaultPromoChoice;
6825                     selectFlag = 0; lastX = xPix; lastY = yPix;
6826                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6827                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6828                 }
6829                 if (appData.highlightDragging) {
6830                     SetHighlights(fromX, fromY, -1, -1);
6831                 }
6832             } else fromX = fromY = -1;
6833             return;
6834         }
6835     }
6836
6837     /* fromX != -1 */
6838     if (clickType == Press && gameMode != EditPosition) {
6839         ChessSquare fromP;
6840         ChessSquare toP;
6841         int frc;
6842
6843         // ignore off-board to clicks
6844         if(y < 0 || x < 0) return;
6845
6846         /* Check if clicking again on the same color piece */
6847         fromP = boards[currentMove][fromY][fromX];
6848         toP = boards[currentMove][y][x];
6849         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6850         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6851              WhitePawn <= toP && toP <= WhiteKing &&
6852              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6853              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6854             (BlackPawn <= fromP && fromP <= BlackKing &&
6855              BlackPawn <= toP && toP <= BlackKing &&
6856              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6857              !(fromP == BlackKing && toP == BlackRook && frc))) {
6858             /* Clicked again on same color piece -- changed his mind */
6859             second = (x == fromX && y == fromY);
6860             promoDefaultAltered = FALSE;
6861            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6862             if (appData.highlightDragging) {
6863                 SetHighlights(x, y, -1, -1);
6864             } else {
6865                 ClearHighlights();
6866             }
6867             if (OKToStartUserMove(x, y)) {
6868                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6869                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6870                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6871                  gatingPiece = boards[currentMove][fromY][fromX];
6872                 else gatingPiece = EmptySquare;
6873                 fromX = x;
6874                 fromY = y; dragging = 1;
6875                 MarkTargetSquares(0);
6876                 DragPieceBegin(xPix, yPix);
6877                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6878                     promoSweep = defaultPromoChoice;
6879                     selectFlag = 0; lastX = xPix; lastY = yPix;
6880                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6881                 }
6882             }
6883            }
6884            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6885            second = FALSE; 
6886         }
6887         // ignore clicks on holdings
6888         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6889     }
6890
6891     if (clickType == Release && x == fromX && y == fromY) {
6892         DragPieceEnd(xPix, yPix); dragging = 0;
6893         if(clearFlag) {
6894             // a deferred attempt to click-click move an empty square on top of a piece
6895             boards[currentMove][y][x] = EmptySquare;
6896             ClearHighlights();
6897             DrawPosition(FALSE, boards[currentMove]);
6898             fromX = fromY = -1; clearFlag = 0;
6899             return;
6900         }
6901         if (appData.animateDragging) {
6902             /* Undo animation damage if any */
6903             DrawPosition(FALSE, NULL);
6904         }
6905         if (second) {
6906             /* Second up/down in same square; just abort move */
6907             second = 0;
6908             fromX = fromY = -1;
6909             gatingPiece = EmptySquare;
6910             ClearHighlights();
6911             gotPremove = 0;
6912             ClearPremoveHighlights();
6913         } else {
6914             /* First upclick in same square; start click-click mode */
6915             SetHighlights(x, y, -1, -1);
6916         }
6917         return;
6918     }
6919
6920     clearFlag = 0;
6921
6922     /* we now have a different from- and (possibly off-board) to-square */
6923     /* Completed move */
6924     toX = x;
6925     toY = y;
6926     saveAnimate = appData.animate;
6927     if (clickType == Press) {
6928         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6929             // must be Edit Position mode with empty-square selected
6930             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6931             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6932             return;
6933         }
6934         /* Finish clickclick move */
6935         if (appData.animate || appData.highlightLastMove) {
6936             SetHighlights(fromX, fromY, toX, toY);
6937         } else {
6938             ClearHighlights();
6939         }
6940     } else {
6941         /* Finish drag move */
6942         if (appData.highlightLastMove) {
6943             SetHighlights(fromX, fromY, toX, toY);
6944         } else {
6945             ClearHighlights();
6946         }
6947         DragPieceEnd(xPix, yPix); dragging = 0;
6948         /* Don't animate move and drag both */
6949         appData.animate = FALSE;
6950     }
6951
6952     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6953     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6954         ChessSquare piece = boards[currentMove][fromY][fromX];
6955         if(gameMode == EditPosition && piece != EmptySquare &&
6956            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6957             int n;
6958
6959             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6960                 n = PieceToNumber(piece - (int)BlackPawn);
6961                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6962                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6963                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6964             } else
6965             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6966                 n = PieceToNumber(piece);
6967                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6968                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6969                 boards[currentMove][n][BOARD_WIDTH-2]++;
6970             }
6971             boards[currentMove][fromY][fromX] = EmptySquare;
6972         }
6973         ClearHighlights();
6974         fromX = fromY = -1;
6975         DrawPosition(TRUE, boards[currentMove]);
6976         return;
6977     }
6978
6979     // off-board moves should not be highlighted
6980     if(x < 0 || y < 0) ClearHighlights();
6981
6982     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6983
6984     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6985         SetHighlights(fromX, fromY, toX, toY);
6986         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6987             // [HGM] super: promotion to captured piece selected from holdings
6988             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6989             promotionChoice = TRUE;
6990             // kludge follows to temporarily execute move on display, without promoting yet
6991             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6992             boards[currentMove][toY][toX] = p;
6993             DrawPosition(FALSE, boards[currentMove]);
6994             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6995             boards[currentMove][toY][toX] = q;
6996             DisplayMessage("Click in holdings to choose piece", "");
6997             return;
6998         }
6999         PromotionPopUp();
7000     } else {
7001         int oldMove = currentMove;
7002         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7003         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7004         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7005         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7006            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7007             DrawPosition(TRUE, boards[currentMove]);
7008         fromX = fromY = -1;
7009     }
7010     appData.animate = saveAnimate;
7011     if (appData.animate || appData.animateDragging) {
7012         /* Undo animation damage if needed */
7013         DrawPosition(FALSE, NULL);
7014     }
7015 }
7016
7017 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7018 {   // front-end-free part taken out of PieceMenuPopup
7019     int whichMenu; int xSqr, ySqr;
7020
7021     if(seekGraphUp) { // [HGM] seekgraph
7022         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7023         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7024         return -2;
7025     }
7026
7027     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7028          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7029         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7030         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7031         if(action == Press)   {
7032             originalFlip = flipView;
7033             flipView = !flipView; // temporarily flip board to see game from partners perspective
7034             DrawPosition(TRUE, partnerBoard);
7035             DisplayMessage(partnerStatus, "");
7036             partnerUp = TRUE;
7037         } else if(action == Release) {
7038             flipView = originalFlip;
7039             DrawPosition(TRUE, boards[currentMove]);
7040             partnerUp = FALSE;
7041         }
7042         return -2;
7043     }
7044
7045     xSqr = EventToSquare(x, BOARD_WIDTH);
7046     ySqr = EventToSquare(y, BOARD_HEIGHT);
7047     if (action == Release) {
7048         if(pieceSweep != EmptySquare) {
7049             EditPositionMenuEvent(pieceSweep, toX, toY);
7050             pieceSweep = EmptySquare;
7051         } else UnLoadPV(); // [HGM] pv
7052     }
7053     if (action != Press) return -2; // return code to be ignored
7054     switch (gameMode) {
7055       case IcsExamining:
7056         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7057       case EditPosition:
7058         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7059         if (xSqr < 0 || ySqr < 0) return -1;
7060         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7061         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7062         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7063         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7064         NextPiece(0);
7065         return -2;\r
7066       case IcsObserving:
7067         if(!appData.icsEngineAnalyze) return -1;
7068       case IcsPlayingWhite:
7069       case IcsPlayingBlack:
7070         if(!appData.zippyPlay) goto noZip;
7071       case AnalyzeMode:
7072       case AnalyzeFile:
7073       case MachinePlaysWhite:
7074       case MachinePlaysBlack:
7075       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7076         if (!appData.dropMenu) {
7077           LoadPV(x, y);
7078           return 2; // flag front-end to grab mouse events
7079         }
7080         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7081            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7082       case EditGame:
7083       noZip:
7084         if (xSqr < 0 || ySqr < 0) return -1;
7085         if (!appData.dropMenu || appData.testLegality &&
7086             gameInfo.variant != VariantBughouse &&
7087             gameInfo.variant != VariantCrazyhouse) return -1;
7088         whichMenu = 1; // drop menu
7089         break;
7090       default:
7091         return -1;
7092     }
7093
7094     if (((*fromX = xSqr) < 0) ||
7095         ((*fromY = ySqr) < 0)) {
7096         *fromX = *fromY = -1;
7097         return -1;
7098     }
7099     if (flipView)
7100       *fromX = BOARD_WIDTH - 1 - *fromX;
7101     else
7102       *fromY = BOARD_HEIGHT - 1 - *fromY;
7103
7104     return whichMenu;
7105 }
7106
7107 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7108 {
7109 //    char * hint = lastHint;
7110     FrontEndProgramStats stats;
7111
7112     stats.which = cps == &first ? 0 : 1;
7113     stats.depth = cpstats->depth;
7114     stats.nodes = cpstats->nodes;
7115     stats.score = cpstats->score;
7116     stats.time = cpstats->time;
7117     stats.pv = cpstats->movelist;
7118     stats.hint = lastHint;
7119     stats.an_move_index = 0;
7120     stats.an_move_count = 0;
7121
7122     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7123         stats.hint = cpstats->move_name;
7124         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7125         stats.an_move_count = cpstats->nr_moves;
7126     }
7127
7128     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
7129
7130     SetProgramStats( &stats );
7131 }
7132
7133 #define MAXPLAYERS 500
7134
7135 char *
7136 TourneyStandings(int display)
7137 {
7138     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7139     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7140     char result, *p, *names[MAXPLAYERS];
7141
7142     if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7143
7144     names[0] = p = strdup(appData.participants);
7145     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7146
7147     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7148
7149     while(result = appData.results[nr]) {
7150         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7151         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7152         wScore = bScore = 0;
7153         switch(result) {
7154           case '+': wScore = 2; break;
7155           case '-': bScore = 2; break;
7156           case '=': wScore = bScore = 1; break;
7157           case ' ':
7158           case '*': return strdup("busy"); // tourney not finished
7159         }
7160         score[w] += wScore;
7161         score[b] += bScore;
7162         games[w]++;
7163         games[b]++;
7164         nr++;
7165     }
7166     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7167     for(w=0; w<nPlayers; w++) {
7168         bScore = -1;
7169         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7170         ranking[w] = b; points[w] = bScore; score[b] = -2;
7171     }
7172     p = malloc(nPlayers*34+1);
7173     for(w=0; w<nPlayers && w<display; w++)
7174         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7175     free(names[0]);
7176     return p;
7177 }
7178
7179 void
7180 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7181 {       // count all piece types
7182         int p, f, r;
7183         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7184         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7185         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7186                 p = board[r][f];
7187                 pCnt[p]++;
7188                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7189                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7190                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7191                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7192                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7193                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7194         }
7195 }
7196
7197 int
7198 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7199 {
7200         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7201         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7202
7203         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7204         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7205         if(myPawns == 2 && nMine == 3) // KPP
7206             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7207         if(myPawns == 1 && nMine == 2) // KP
7208             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7209         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7210             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7211         if(myPawns) return FALSE;
7212         if(pCnt[WhiteRook+side])
7213             return pCnt[BlackRook-side] ||
7214                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7215                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7216                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7217         if(pCnt[WhiteCannon+side]) {
7218             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7219             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7220         }
7221         if(pCnt[WhiteKnight+side])
7222             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7223         return FALSE;
7224 }
7225
7226 int
7227 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7228 {
7229         VariantClass v = gameInfo.variant;
7230
7231         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7232         if(v == VariantShatranj) return TRUE; // always winnable through baring
7233         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7234         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7235
7236         if(v == VariantXiangqi) {
7237                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7238
7239                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7240                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7241                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7242                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7243                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7244                 if(stale) // we have at least one last-rank P plus perhaps C
7245                     return majors // KPKX
7246                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7247                 else // KCA*E*
7248                     return pCnt[WhiteFerz+side] // KCAK
7249                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7250                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7251                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7252
7253         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7254                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7255
7256                 if(nMine == 1) return FALSE; // bare King
7257                 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
7258                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7259                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7260                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7261                 if(pCnt[WhiteKnight+side])
7262                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7263                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7264                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7265                 if(nBishops)
7266                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7267                 if(pCnt[WhiteAlfil+side])
7268                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7269                 if(pCnt[WhiteWazir+side])
7270                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7271         }
7272
7273         return TRUE;
7274 }
7275
7276 int
7277 Adjudicate(ChessProgramState *cps)
7278 {       // [HGM] some adjudications useful with buggy engines
7279         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7280         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7281         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7282         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7283         int k, count = 0; static int bare = 1;
7284         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7285         Boolean canAdjudicate = !appData.icsActive;
7286
7287         // most tests only when we understand the game, i.e. legality-checking on
7288             if( appData.testLegality )
7289             {   /* [HGM] Some more adjudications for obstinate engines */
7290                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7291                 static int moveCount = 6;
7292                 ChessMove result;
7293                 char *reason = NULL;
7294
7295                 /* Count what is on board. */
7296                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7297
7298                 /* Some material-based adjudications that have to be made before stalemate test */
7299                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7300                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7301                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7302                      if(canAdjudicate && appData.checkMates) {
7303                          if(engineOpponent)
7304                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7305                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7306                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7307                          return 1;
7308                      }
7309                 }
7310
7311                 /* Bare King in Shatranj (loses) or Losers (wins) */
7312                 if( nrW == 1 || nrB == 1) {
7313                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7314                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7315                      if(canAdjudicate && appData.checkMates) {
7316                          if(engineOpponent)
7317                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7318                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7319                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7320                          return 1;
7321                      }
7322                   } else
7323                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7324                   {    /* bare King */
7325                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7326                         if(canAdjudicate && appData.checkMates) {
7327                             /* but only adjudicate if adjudication enabled */
7328                             if(engineOpponent)
7329                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7330                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7331                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7332                             return 1;
7333                         }
7334                   }
7335                 } else bare = 1;
7336
7337
7338             // don't wait for engine to announce game end if we can judge ourselves
7339             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7340               case MT_CHECK:
7341                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7342                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7343                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7344                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7345                             checkCnt++;
7346                         if(checkCnt >= 2) {
7347                             reason = "Xboard adjudication: 3rd check";
7348                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7349                             break;
7350                         }
7351                     }
7352                 }
7353               case MT_NONE:
7354               default:
7355                 break;
7356               case MT_STALEMATE:
7357               case MT_STAINMATE:
7358                 reason = "Xboard adjudication: Stalemate";
7359                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7360                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7361                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7362                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7363                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7364                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7365                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7366                                                                         EP_CHECKMATE : EP_WINS);
7367                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7368                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7369                 }
7370                 break;
7371               case MT_CHECKMATE:
7372                 reason = "Xboard adjudication: Checkmate";
7373                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7374                 break;
7375             }
7376
7377                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7378                     case EP_STALEMATE:
7379                         result = GameIsDrawn; break;
7380                     case EP_CHECKMATE:
7381                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7382                     case EP_WINS:
7383                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7384                     default:
7385                         result = EndOfFile;
7386                 }
7387                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7388                     if(engineOpponent)
7389                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7390                     GameEnds( result, reason, GE_XBOARD );
7391                     return 1;
7392                 }
7393
7394                 /* Next absolutely insufficient mating material. */
7395                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7396                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7397                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7398
7399                      /* always flag draws, for judging claims */
7400                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7401
7402                      if(canAdjudicate && appData.materialDraws) {
7403                          /* but only adjudicate them if adjudication enabled */
7404                          if(engineOpponent) {
7405                            SendToProgram("force\n", engineOpponent); // suppress reply
7406                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7407                          }
7408                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7409                          return 1;
7410                      }
7411                 }
7412
7413                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7414                 if(gameInfo.variant == VariantXiangqi ?
7415                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7416                  : nrW + nrB == 4 &&
7417                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7418                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7419                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7420                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7421                    ) ) {
7422                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7423                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7424                           if(engineOpponent) {
7425                             SendToProgram("force\n", engineOpponent); // suppress reply
7426                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7427                           }
7428                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7429                           return 1;
7430                      }
7431                 } else moveCount = 6;
7432             }
7433         if (appData.debugMode) { int i;
7434             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7435                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7436                     appData.drawRepeats);
7437             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7438               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7439
7440         }
7441
7442         // Repetition draws and 50-move rule can be applied independently of legality testing
7443
7444                 /* Check for rep-draws */
7445                 count = 0;
7446                 for(k = forwardMostMove-2;
7447                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7448                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7449                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7450                     k-=2)
7451                 {   int rights=0;
7452                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7453                         /* compare castling rights */
7454                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7455                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7456                                 rights++; /* King lost rights, while rook still had them */
7457                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7458                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7459                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7460                                    rights++; /* but at least one rook lost them */
7461                         }
7462                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7463                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7464                                 rights++;
7465                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7466                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7467                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7468                                    rights++;
7469                         }
7470                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7471                             && appData.drawRepeats > 1) {
7472                              /* adjudicate after user-specified nr of repeats */
7473                              int result = GameIsDrawn;
7474                              char *details = "XBoard adjudication: repetition draw";
7475                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7476                                 // [HGM] xiangqi: check for forbidden perpetuals
7477                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7478                                 for(m=forwardMostMove; m>k; m-=2) {
7479                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7480                                         ourPerpetual = 0; // the current mover did not always check
7481                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7482                                         hisPerpetual = 0; // the opponent did not always check
7483                                 }
7484                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7485                                                                         ourPerpetual, hisPerpetual);
7486                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7487                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7488                                     details = "Xboard adjudication: perpetual checking";
7489                                 } else
7490                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7491                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7492                                 } else
7493                                 // Now check for perpetual chases
7494                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7495                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7496                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7497                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7498                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7499                                         details = "Xboard adjudication: perpetual chasing";
7500                                     } else
7501                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7502                                         break; // Abort repetition-checking loop.
7503                                 }
7504                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7505                              }
7506                              if(engineOpponent) {
7507                                SendToProgram("force\n", engineOpponent); // suppress reply
7508                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7509                              }
7510                              GameEnds( result, details, GE_XBOARD );
7511                              return 1;
7512                         }
7513                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7514                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7515                     }
7516                 }
7517
7518                 /* Now we test for 50-move draws. Determine ply count */
7519                 count = forwardMostMove;
7520                 /* look for last irreversble move */
7521                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7522                     count--;
7523                 /* if we hit starting position, add initial plies */
7524                 if( count == backwardMostMove )
7525                     count -= initialRulePlies;
7526                 count = forwardMostMove - count;
7527                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7528                         // adjust reversible move counter for checks in Xiangqi
7529                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7530                         if(i < backwardMostMove) i = backwardMostMove;
7531                         while(i <= forwardMostMove) {
7532                                 lastCheck = inCheck; // check evasion does not count
7533                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7534                                 if(inCheck || lastCheck) count--; // check does not count
7535                                 i++;
7536                         }
7537                 }
7538                 if( count >= 100)
7539                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7540                          /* this is used to judge if draw claims are legal */
7541                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7542                          if(engineOpponent) {
7543                            SendToProgram("force\n", engineOpponent); // suppress reply
7544                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7545                          }
7546                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7547                          return 1;
7548                 }
7549
7550                 /* if draw offer is pending, treat it as a draw claim
7551                  * when draw condition present, to allow engines a way to
7552                  * claim draws before making their move to avoid a race
7553                  * condition occurring after their move
7554                  */
7555                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7556                          char *p = NULL;
7557                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7558                              p = "Draw claim: 50-move rule";
7559                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7560                              p = "Draw claim: 3-fold repetition";
7561                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7562                              p = "Draw claim: insufficient mating material";
7563                          if( p != NULL && canAdjudicate) {
7564                              if(engineOpponent) {
7565                                SendToProgram("force\n", engineOpponent); // suppress reply
7566                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7567                              }
7568                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7569                              return 1;
7570                          }
7571                 }
7572
7573                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7574                     if(engineOpponent) {
7575                       SendToProgram("force\n", engineOpponent); // suppress reply
7576                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7577                     }
7578                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7579                     return 1;
7580                 }
7581         return 0;
7582 }
7583
7584 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7585 {   // [HGM] book: this routine intercepts moves to simulate book replies
7586     char *bookHit = NULL;
7587
7588     //first determine if the incoming move brings opponent into his book
7589     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7590         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7591     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7592     if(bookHit != NULL && !cps->bookSuspend) {
7593         // make sure opponent is not going to reply after receiving move to book position
7594         SendToProgram("force\n", cps);
7595         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7596     }
7597     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7598     // now arrange restart after book miss
7599     if(bookHit) {
7600         // after a book hit we never send 'go', and the code after the call to this routine
7601         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7602         char buf[MSG_SIZ], *move = bookHit;
7603         if(cps->useSAN) {
7604             int fromX, fromY, toX, toY;
7605             char promoChar;
7606             ChessMove moveType;
7607             move = buf + 30;
7608             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7609                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7610                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7611                                     PosFlags(forwardMostMove),
7612                                     fromY, fromX, toY, toX, promoChar, move);
7613             } else {
7614                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7615                 bookHit = NULL;
7616             }
7617         }
7618         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7619         SendToProgram(buf, cps);
7620         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7621     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7622         SendToProgram("go\n", cps);
7623         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7624     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7625         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7626             SendToProgram("go\n", cps);
7627         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7628     }
7629     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7630 }
7631
7632 char *savedMessage;
7633 ChessProgramState *savedState;
7634 void DeferredBookMove(void)
7635 {
7636         if(savedState->lastPing != savedState->lastPong)
7637                     ScheduleDelayedEvent(DeferredBookMove, 10);
7638         else
7639         HandleMachineMove(savedMessage, savedState);
7640 }
7641
7642 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7643
7644 void
7645 HandleMachineMove(message, cps)
7646      char *message;
7647      ChessProgramState *cps;
7648 {
7649     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7650     char realname[MSG_SIZ];
7651     int fromX, fromY, toX, toY;
7652     ChessMove moveType;
7653     char promoChar;
7654     char *p;
7655     int machineWhite;
7656     char *bookHit;
7657
7658     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7659         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7660         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
7661         pairingReceived = 1;
7662         NextMatchGame();
7663         return; // Skim the pairing messages here.
7664     }
7665
7666     cps->userError = 0;
7667
7668 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7669     /*
7670      * Kludge to ignore BEL characters
7671      */
7672     while (*message == '\007') message++;
7673
7674     /*
7675      * [HGM] engine debug message: ignore lines starting with '#' character
7676      */
7677     if(cps->debug && *message == '#') return;
7678
7679     /*
7680      * Look for book output
7681      */
7682     if (cps == &first && bookRequested) {
7683         if (message[0] == '\t' || message[0] == ' ') {
7684             /* Part of the book output is here; append it */
7685             strcat(bookOutput, message);
7686             strcat(bookOutput, "  \n");
7687             return;
7688         } else if (bookOutput[0] != NULLCHAR) {
7689             /* All of book output has arrived; display it */
7690             char *p = bookOutput;
7691             while (*p != NULLCHAR) {
7692                 if (*p == '\t') *p = ' ';
7693                 p++;
7694             }
7695             DisplayInformation(bookOutput);
7696             bookRequested = FALSE;
7697             /* Fall through to parse the current output */
7698         }
7699     }
7700
7701     /*
7702      * Look for machine move.
7703      */
7704     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7705         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7706     {
7707         /* This method is only useful on engines that support ping */
7708         if (cps->lastPing != cps->lastPong) {
7709           if (gameMode == BeginningOfGame) {
7710             /* Extra move from before last new; ignore */
7711             if (appData.debugMode) {
7712                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7713             }
7714           } else {
7715             if (appData.debugMode) {
7716                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7717                         cps->which, gameMode);
7718             }
7719
7720             SendToProgram("undo\n", cps);
7721           }
7722           return;
7723         }
7724
7725         switch (gameMode) {
7726           case BeginningOfGame:
7727             /* Extra move from before last reset; ignore */
7728             if (appData.debugMode) {
7729                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7730             }
7731             return;
7732
7733           case EndOfGame:
7734           case IcsIdle:
7735           default:
7736             /* Extra move after we tried to stop.  The mode test is
7737                not a reliable way of detecting this problem, but it's
7738                the best we can do on engines that don't support ping.
7739             */
7740             if (appData.debugMode) {
7741                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7742                         cps->which, gameMode);
7743             }
7744             SendToProgram("undo\n", cps);
7745             return;
7746
7747           case MachinePlaysWhite:
7748           case IcsPlayingWhite:
7749             machineWhite = TRUE;
7750             break;
7751
7752           case MachinePlaysBlack:
7753           case IcsPlayingBlack:
7754             machineWhite = FALSE;
7755             break;
7756
7757           case TwoMachinesPlay:
7758             machineWhite = (cps->twoMachinesColor[0] == 'w');
7759             break;
7760         }
7761         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7762             if (appData.debugMode) {
7763                 fprintf(debugFP,
7764                         "Ignoring move out of turn by %s, gameMode %d"
7765                         ", forwardMost %d\n",
7766                         cps->which, gameMode, forwardMostMove);
7767             }
7768             return;
7769         }
7770
7771     if (appData.debugMode) { int f = forwardMostMove;
7772         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7773                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7774                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7775     }
7776         if(cps->alphaRank) AlphaRank(machineMove, 4);
7777         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7778                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7779             /* Machine move could not be parsed; ignore it. */
7780           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7781                     machineMove, _(cps->which));
7782             DisplayError(buf1, 0);
7783             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7784                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7785             if (gameMode == TwoMachinesPlay) {
7786               GameEnds(machineWhite ? BlackWins : WhiteWins,
7787                        buf1, GE_XBOARD);
7788             }
7789             return;
7790         }
7791
7792         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7793         /* So we have to redo legality test with true e.p. status here,  */
7794         /* to make sure an illegal e.p. capture does not slip through,   */
7795         /* to cause a forfeit on a justified illegal-move complaint      */
7796         /* of the opponent.                                              */
7797         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7798            ChessMove moveType;
7799            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7800                              fromY, fromX, toY, toX, promoChar);
7801             if (appData.debugMode) {
7802                 int i;
7803                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7804                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7805                 fprintf(debugFP, "castling rights\n");
7806             }
7807             if(moveType == IllegalMove) {
7808               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7809                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7810                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7811                            buf1, GE_XBOARD);
7812                 return;
7813            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7814            /* [HGM] Kludge to handle engines that send FRC-style castling
7815               when they shouldn't (like TSCP-Gothic) */
7816            switch(moveType) {
7817              case WhiteASideCastleFR:
7818              case BlackASideCastleFR:
7819                toX+=2;
7820                currentMoveString[2]++;
7821                break;
7822              case WhiteHSideCastleFR:
7823              case BlackHSideCastleFR:
7824                toX--;
7825                currentMoveString[2]--;
7826                break;
7827              default: ; // nothing to do, but suppresses warning of pedantic compilers
7828            }
7829         }
7830         hintRequested = FALSE;
7831         lastHint[0] = NULLCHAR;
7832         bookRequested = FALSE;
7833         /* Program may be pondering now */
7834         cps->maybeThinking = TRUE;
7835         if (cps->sendTime == 2) cps->sendTime = 1;
7836         if (cps->offeredDraw) cps->offeredDraw--;
7837
7838         /* [AS] Save move info*/
7839         pvInfoList[ forwardMostMove ].score = programStats.score;
7840         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7841         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7842
7843         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7844
7845         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7846         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7847             int count = 0;
7848
7849             while( count < adjudicateLossPlies ) {
7850                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7851
7852                 if( count & 1 ) {
7853                     score = -score; /* Flip score for winning side */
7854                 }
7855
7856                 if( score > adjudicateLossThreshold ) {
7857                     break;
7858                 }
7859
7860                 count++;
7861             }
7862
7863             if( count >= adjudicateLossPlies ) {
7864                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7865
7866                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7867                     "Xboard adjudication",
7868                     GE_XBOARD );
7869
7870                 return;
7871             }
7872         }
7873
7874         if(Adjudicate(cps)) {
7875             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7876             return; // [HGM] adjudicate: for all automatic game ends
7877         }
7878
7879 #if ZIPPY
7880         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7881             first.initDone) {
7882           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7883                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7884                 SendToICS("draw ");
7885                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7886           }
7887           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7888           ics_user_moved = 1;
7889           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7890                 char buf[3*MSG_SIZ];
7891
7892                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7893                         programStats.score / 100.,
7894                         programStats.depth,
7895                         programStats.time / 100.,
7896                         (unsigned int)programStats.nodes,
7897                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7898                         programStats.movelist);
7899                 SendToICS(buf);
7900 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7901           }
7902         }
7903 #endif
7904
7905         /* [AS] Clear stats for next move */
7906         ClearProgramStats();
7907         thinkOutput[0] = NULLCHAR;
7908         hiddenThinkOutputState = 0;
7909
7910         bookHit = NULL;
7911         if (gameMode == TwoMachinesPlay) {
7912             /* [HGM] relaying draw offers moved to after reception of move */
7913             /* and interpreting offer as claim if it brings draw condition */
7914             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7915                 SendToProgram("draw\n", cps->other);
7916             }
7917             if (cps->other->sendTime) {
7918                 SendTimeRemaining(cps->other,
7919                                   cps->other->twoMachinesColor[0] == 'w');
7920             }
7921             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7922             if (firstMove && !bookHit) {
7923                 firstMove = FALSE;
7924                 if (cps->other->useColors) {
7925                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7926                 }
7927                 SendToProgram("go\n", cps->other);
7928             }
7929             cps->other->maybeThinking = TRUE;
7930         }
7931
7932         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7933
7934         if (!pausing && appData.ringBellAfterMoves) {
7935             RingBell();
7936         }
7937
7938         /*
7939          * Reenable menu items that were disabled while
7940          * machine was thinking
7941          */
7942         if (gameMode != TwoMachinesPlay)
7943             SetUserThinkingEnables();
7944
7945         // [HGM] book: after book hit opponent has received move and is now in force mode
7946         // force the book reply into it, and then fake that it outputted this move by jumping
7947         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7948         if(bookHit) {
7949                 static char bookMove[MSG_SIZ]; // a bit generous?
7950
7951                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7952                 strcat(bookMove, bookHit);
7953                 message = bookMove;
7954                 cps = cps->other;
7955                 programStats.nodes = programStats.depth = programStats.time =
7956                 programStats.score = programStats.got_only_move = 0;
7957                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7958
7959                 if(cps->lastPing != cps->lastPong) {
7960                     savedMessage = message; // args for deferred call
7961                     savedState = cps;
7962                     ScheduleDelayedEvent(DeferredBookMove, 10);
7963                     return;
7964                 }
7965                 goto FakeBookMove;
7966         }
7967
7968         return;
7969     }
7970
7971     /* Set special modes for chess engines.  Later something general
7972      *  could be added here; for now there is just one kludge feature,
7973      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7974      *  when "xboard" is given as an interactive command.
7975      */
7976     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7977         cps->useSigint = FALSE;
7978         cps->useSigterm = FALSE;
7979     }
7980     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7981       ParseFeatures(message+8, cps);
7982       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7983     }
7984
7985     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7986       int dummy, s=6; char buf[MSG_SIZ];
7987       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7988       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7989       ParseFEN(boards[0], &dummy, message+s);
7990       DrawPosition(TRUE, boards[0]);
7991       startedFromSetupPosition = TRUE;
7992       return;
7993     }
7994     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7995      * want this, I was asked to put it in, and obliged.
7996      */
7997     if (!strncmp(message, "setboard ", 9)) {
7998         Board initial_position;
7999
8000         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8001
8002         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8003             DisplayError(_("Bad FEN received from engine"), 0);
8004             return ;
8005         } else {
8006            Reset(TRUE, FALSE);
8007            CopyBoard(boards[0], initial_position);
8008            initialRulePlies = FENrulePlies;
8009            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8010            else gameMode = MachinePlaysBlack;
8011            DrawPosition(FALSE, boards[currentMove]);
8012         }
8013         return;
8014     }
8015
8016     /*
8017      * Look for communication commands
8018      */
8019     if (!strncmp(message, "telluser ", 9)) {
8020         if(message[9] == '\\' && message[10] == '\\')
8021             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8022         DisplayNote(message + 9);
8023         return;
8024     }
8025     if (!strncmp(message, "tellusererror ", 14)) {
8026         cps->userError = 1;
8027         if(message[14] == '\\' && message[15] == '\\')
8028             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8029         DisplayError(message + 14, 0);
8030         return;
8031     }
8032     if (!strncmp(message, "tellopponent ", 13)) {
8033       if (appData.icsActive) {
8034         if (loggedOn) {
8035           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8036           SendToICS(buf1);
8037         }
8038       } else {
8039         DisplayNote(message + 13);
8040       }
8041       return;
8042     }
8043     if (!strncmp(message, "tellothers ", 11)) {
8044       if (appData.icsActive) {
8045         if (loggedOn) {
8046           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8047           SendToICS(buf1);
8048         }
8049       }
8050       return;
8051     }
8052     if (!strncmp(message, "tellall ", 8)) {
8053       if (appData.icsActive) {
8054         if (loggedOn) {
8055           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8056           SendToICS(buf1);
8057         }
8058       } else {
8059         DisplayNote(message + 8);
8060       }
8061       return;
8062     }
8063     if (strncmp(message, "warning", 7) == 0) {
8064         /* Undocumented feature, use tellusererror in new code */
8065         DisplayError(message, 0);
8066         return;
8067     }
8068     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8069         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8070         strcat(realname, " query");
8071         AskQuestion(realname, buf2, buf1, cps->pr);
8072         return;
8073     }
8074     /* Commands from the engine directly to ICS.  We don't allow these to be
8075      *  sent until we are logged on. Crafty kibitzes have been known to
8076      *  interfere with the login process.
8077      */
8078     if (loggedOn) {
8079         if (!strncmp(message, "tellics ", 8)) {
8080             SendToICS(message + 8);
8081             SendToICS("\n");
8082             return;
8083         }
8084         if (!strncmp(message, "tellicsnoalias ", 15)) {
8085             SendToICS(ics_prefix);
8086             SendToICS(message + 15);
8087             SendToICS("\n");
8088             return;
8089         }
8090         /* The following are for backward compatibility only */
8091         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8092             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8093             SendToICS(ics_prefix);
8094             SendToICS(message);
8095             SendToICS("\n");
8096             return;
8097         }
8098     }
8099     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8100         return;
8101     }
8102     /*
8103      * If the move is illegal, cancel it and redraw the board.
8104      * Also deal with other error cases.  Matching is rather loose
8105      * here to accommodate engines written before the spec.
8106      */
8107     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8108         strncmp(message, "Error", 5) == 0) {
8109         if (StrStr(message, "name") ||
8110             StrStr(message, "rating") || StrStr(message, "?") ||
8111             StrStr(message, "result") || StrStr(message, "board") ||
8112             StrStr(message, "bk") || StrStr(message, "computer") ||
8113             StrStr(message, "variant") || StrStr(message, "hint") ||
8114             StrStr(message, "random") || StrStr(message, "depth") ||
8115             StrStr(message, "accepted")) {
8116             return;
8117         }
8118         if (StrStr(message, "protover")) {
8119           /* Program is responding to input, so it's apparently done
8120              initializing, and this error message indicates it is
8121              protocol version 1.  So we don't need to wait any longer
8122              for it to initialize and send feature commands. */
8123           FeatureDone(cps, 1);
8124           cps->protocolVersion = 1;
8125           return;
8126         }
8127         cps->maybeThinking = FALSE;
8128
8129         if (StrStr(message, "draw")) {
8130             /* Program doesn't have "draw" command */
8131             cps->sendDrawOffers = 0;
8132             return;
8133         }
8134         if (cps->sendTime != 1 &&
8135             (StrStr(message, "time") || StrStr(message, "otim"))) {
8136           /* Program apparently doesn't have "time" or "otim" command */
8137           cps->sendTime = 0;
8138           return;
8139         }
8140         if (StrStr(message, "analyze")) {
8141             cps->analysisSupport = FALSE;
8142             cps->analyzing = FALSE;
8143             Reset(FALSE, TRUE);
8144             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8145             DisplayError(buf2, 0);
8146             return;
8147         }
8148         if (StrStr(message, "(no matching move)st")) {
8149           /* Special kludge for GNU Chess 4 only */
8150           cps->stKludge = TRUE;
8151           SendTimeControl(cps, movesPerSession, timeControl,
8152                           timeIncrement, appData.searchDepth,
8153                           searchTime);
8154           return;
8155         }
8156         if (StrStr(message, "(no matching move)sd")) {
8157           /* Special kludge for GNU Chess 4 only */
8158           cps->sdKludge = TRUE;
8159           SendTimeControl(cps, movesPerSession, timeControl,
8160                           timeIncrement, appData.searchDepth,
8161                           searchTime);
8162           return;
8163         }
8164         if (!StrStr(message, "llegal")) {
8165             return;
8166         }
8167         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8168             gameMode == IcsIdle) return;
8169         if (forwardMostMove <= backwardMostMove) return;
8170         if (pausing) PauseEvent();
8171       if(appData.forceIllegal) {
8172             // [HGM] illegal: machine refused move; force position after move into it
8173           SendToProgram("force\n", cps);
8174           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8175                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8176                 // when black is to move, while there might be nothing on a2 or black
8177                 // might already have the move. So send the board as if white has the move.
8178                 // But first we must change the stm of the engine, as it refused the last move
8179                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8180                 if(WhiteOnMove(forwardMostMove)) {
8181                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8182                     SendBoard(cps, forwardMostMove); // kludgeless board
8183                 } else {
8184                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8185                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8186                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8187                 }
8188           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8189             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8190                  gameMode == TwoMachinesPlay)
8191               SendToProgram("go\n", cps);
8192             return;
8193       } else
8194         if (gameMode == PlayFromGameFile) {
8195             /* Stop reading this game file */
8196             gameMode = EditGame;
8197             ModeHighlight();
8198         }
8199         /* [HGM] illegal-move claim should forfeit game when Xboard */
8200         /* only passes fully legal moves                            */
8201         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8202             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8203                                 "False illegal-move claim", GE_XBOARD );
8204             return; // do not take back move we tested as valid
8205         }
8206         currentMove = forwardMostMove-1;
8207         DisplayMove(currentMove-1); /* before DisplayMoveError */
8208         SwitchClocks(forwardMostMove-1); // [HGM] race
8209         DisplayBothClocks();
8210         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8211                 parseList[currentMove], _(cps->which));
8212         DisplayMoveError(buf1);
8213         DrawPosition(FALSE, boards[currentMove]);
8214         return;
8215     }
8216     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8217         /* Program has a broken "time" command that
8218            outputs a string not ending in newline.
8219            Don't use it. */
8220         cps->sendTime = 0;
8221     }
8222
8223     /*
8224      * If chess program startup fails, exit with an error message.
8225      * Attempts to recover here are futile.
8226      */
8227     if ((StrStr(message, "unknown host") != NULL)
8228         || (StrStr(message, "No remote directory") != NULL)
8229         || (StrStr(message, "not found") != NULL)
8230         || (StrStr(message, "No such file") != NULL)
8231         || (StrStr(message, "can't alloc") != NULL)
8232         || (StrStr(message, "Permission denied") != NULL)) {
8233
8234         cps->maybeThinking = FALSE;
8235         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8236                 _(cps->which), cps->program, cps->host, message);
8237         RemoveInputSource(cps->isr);
8238         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8239             if(cps == &first) appData.noChessProgram = TRUE;
8240             DisplayError(buf1, 0);
8241         }
8242         return;
8243     }
8244
8245     /*
8246      * Look for hint output
8247      */
8248     if (sscanf(message, "Hint: %s", buf1) == 1) {
8249         if (cps == &first && hintRequested) {
8250             hintRequested = FALSE;
8251             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8252                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8253                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8254                                     PosFlags(forwardMostMove),
8255                                     fromY, fromX, toY, toX, promoChar, buf1);
8256                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8257                 DisplayInformation(buf2);
8258             } else {
8259                 /* Hint move could not be parsed!? */
8260               snprintf(buf2, sizeof(buf2),
8261                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8262                         buf1, _(cps->which));
8263                 DisplayError(buf2, 0);
8264             }
8265         } else {
8266           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8267         }
8268         return;
8269     }
8270
8271     /*
8272      * Ignore other messages if game is not in progress
8273      */
8274     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8275         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8276
8277     /*
8278      * look for win, lose, draw, or draw offer
8279      */
8280     if (strncmp(message, "1-0", 3) == 0) {
8281         char *p, *q, *r = "";
8282         p = strchr(message, '{');
8283         if (p) {
8284             q = strchr(p, '}');
8285             if (q) {
8286                 *q = NULLCHAR;
8287                 r = p + 1;
8288             }
8289         }
8290         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8291         return;
8292     } else if (strncmp(message, "0-1", 3) == 0) {
8293         char *p, *q, *r = "";
8294         p = strchr(message, '{');
8295         if (p) {
8296             q = strchr(p, '}');
8297             if (q) {
8298                 *q = NULLCHAR;
8299                 r = p + 1;
8300             }
8301         }
8302         /* Kludge for Arasan 4.1 bug */
8303         if (strcmp(r, "Black resigns") == 0) {
8304             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8305             return;
8306         }
8307         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8308         return;
8309     } else if (strncmp(message, "1/2", 3) == 0) {
8310         char *p, *q, *r = "";
8311         p = strchr(message, '{');
8312         if (p) {
8313             q = strchr(p, '}');
8314             if (q) {
8315                 *q = NULLCHAR;
8316                 r = p + 1;
8317             }
8318         }
8319
8320         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8321         return;
8322
8323     } else if (strncmp(message, "White resign", 12) == 0) {
8324         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8325         return;
8326     } else if (strncmp(message, "Black resign", 12) == 0) {
8327         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8328         return;
8329     } else if (strncmp(message, "White matches", 13) == 0 ||
8330                strncmp(message, "Black matches", 13) == 0   ) {
8331         /* [HGM] ignore GNUShogi noises */
8332         return;
8333     } else if (strncmp(message, "White", 5) == 0 &&
8334                message[5] != '(' &&
8335                StrStr(message, "Black") == NULL) {
8336         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8337         return;
8338     } else if (strncmp(message, "Black", 5) == 0 &&
8339                message[5] != '(') {
8340         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8341         return;
8342     } else if (strcmp(message, "resign") == 0 ||
8343                strcmp(message, "computer resigns") == 0) {
8344         switch (gameMode) {
8345           case MachinePlaysBlack:
8346           case IcsPlayingBlack:
8347             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8348             break;
8349           case MachinePlaysWhite:
8350           case IcsPlayingWhite:
8351             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8352             break;
8353           case TwoMachinesPlay:
8354             if (cps->twoMachinesColor[0] == 'w')
8355               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8356             else
8357               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8358             break;
8359           default:
8360             /* can't happen */
8361             break;
8362         }
8363         return;
8364     } else if (strncmp(message, "opponent mates", 14) == 0) {
8365         switch (gameMode) {
8366           case MachinePlaysBlack:
8367           case IcsPlayingBlack:
8368             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8369             break;
8370           case MachinePlaysWhite:
8371           case IcsPlayingWhite:
8372             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8373             break;
8374           case TwoMachinesPlay:
8375             if (cps->twoMachinesColor[0] == 'w')
8376               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8377             else
8378               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8379             break;
8380           default:
8381             /* can't happen */
8382             break;
8383         }
8384         return;
8385     } else if (strncmp(message, "computer mates", 14) == 0) {
8386         switch (gameMode) {
8387           case MachinePlaysBlack:
8388           case IcsPlayingBlack:
8389             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8390             break;
8391           case MachinePlaysWhite:
8392           case IcsPlayingWhite:
8393             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8394             break;
8395           case TwoMachinesPlay:
8396             if (cps->twoMachinesColor[0] == 'w')
8397               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8398             else
8399               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8400             break;
8401           default:
8402             /* can't happen */
8403             break;
8404         }
8405         return;
8406     } else if (strncmp(message, "checkmate", 9) == 0) {
8407         if (WhiteOnMove(forwardMostMove)) {
8408             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8409         } else {
8410             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8411         }
8412         return;
8413     } else if (strstr(message, "Draw") != NULL ||
8414                strstr(message, "game is a draw") != NULL) {
8415         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8416         return;
8417     } else if (strstr(message, "offer") != NULL &&
8418                strstr(message, "draw") != NULL) {
8419 #if ZIPPY
8420         if (appData.zippyPlay && first.initDone) {
8421             /* Relay offer to ICS */
8422             SendToICS(ics_prefix);
8423             SendToICS("draw\n");
8424         }
8425 #endif
8426         cps->offeredDraw = 2; /* valid until this engine moves twice */
8427         if (gameMode == TwoMachinesPlay) {
8428             if (cps->other->offeredDraw) {
8429                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8430             /* [HGM] in two-machine mode we delay relaying draw offer      */
8431             /* until after we also have move, to see if it is really claim */
8432             }
8433         } else if (gameMode == MachinePlaysWhite ||
8434                    gameMode == MachinePlaysBlack) {
8435           if (userOfferedDraw) {
8436             DisplayInformation(_("Machine accepts your draw offer"));
8437             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8438           } else {
8439             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8440           }
8441         }
8442     }
8443
8444
8445     /*
8446      * Look for thinking output
8447      */
8448     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8449           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8450                                 ) {
8451         int plylev, mvleft, mvtot, curscore, time;
8452         char mvname[MOVE_LEN];
8453         u64 nodes; // [DM]
8454         char plyext;
8455         int ignore = FALSE;
8456         int prefixHint = FALSE;
8457         mvname[0] = NULLCHAR;
8458
8459         switch (gameMode) {
8460           case MachinePlaysBlack:
8461           case IcsPlayingBlack:
8462             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8463             break;
8464           case MachinePlaysWhite:
8465           case IcsPlayingWhite:
8466             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8467             break;
8468           case AnalyzeMode:
8469           case AnalyzeFile:
8470             break;
8471           case IcsObserving: /* [DM] icsEngineAnalyze */
8472             if (!appData.icsEngineAnalyze) ignore = TRUE;
8473             break;
8474           case TwoMachinesPlay:
8475             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8476                 ignore = TRUE;
8477             }
8478             break;
8479           default:
8480             ignore = TRUE;
8481             break;
8482         }
8483
8484         if (!ignore) {
8485             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8486             buf1[0] = NULLCHAR;
8487             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8488                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8489
8490                 if (plyext != ' ' && plyext != '\t') {
8491                     time *= 100;
8492                 }
8493
8494                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8495                 if( cps->scoreIsAbsolute &&
8496                     ( gameMode == MachinePlaysBlack ||
8497                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8498                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8499                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8500                      !WhiteOnMove(currentMove)
8501                     ) )
8502                 {
8503                     curscore = -curscore;
8504                 }
8505
8506
8507                 tempStats.depth = plylev;
8508                 tempStats.nodes = nodes;
8509                 tempStats.time = time;
8510                 tempStats.score = curscore;
8511                 tempStats.got_only_move = 0;
8512
8513                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8514                         int ticklen;
8515
8516                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8517                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8518                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8519                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8520                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8521                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8522                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8523                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8524                 }
8525
8526                 /* Buffer overflow protection */
8527                 if (buf1[0] != NULLCHAR) {
8528                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8529                         && appData.debugMode) {
8530                         fprintf(debugFP,
8531                                 "PV is too long; using the first %u bytes.\n",
8532                                 (unsigned) sizeof(tempStats.movelist) - 1);
8533                     }
8534
8535                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8536                 } else {
8537                     sprintf(tempStats.movelist, " no PV\n");
8538                 }
8539
8540                 if (tempStats.seen_stat) {
8541                     tempStats.ok_to_send = 1;
8542                 }
8543
8544                 if (strchr(tempStats.movelist, '(') != NULL) {
8545                     tempStats.line_is_book = 1;
8546                     tempStats.nr_moves = 0;
8547                     tempStats.moves_left = 0;
8548                 } else {
8549                     tempStats.line_is_book = 0;
8550                 }
8551
8552                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8553                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8554
8555                 SendProgramStatsToFrontend( cps, &tempStats );
8556
8557                 /*
8558                     [AS] Protect the thinkOutput buffer from overflow... this
8559                     is only useful if buf1 hasn't overflowed first!
8560                 */
8561                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8562                          plylev,
8563                          (gameMode == TwoMachinesPlay ?
8564                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8565                          ((double) curscore) / 100.0,
8566                          prefixHint ? lastHint : "",
8567                          prefixHint ? " " : "" );
8568
8569                 if( buf1[0] != NULLCHAR ) {
8570                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8571
8572                     if( strlen(buf1) > max_len ) {
8573                         if( appData.debugMode) {
8574                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8575                         }
8576                         buf1[max_len+1] = '\0';
8577                     }
8578
8579                     strcat( thinkOutput, buf1 );
8580                 }
8581
8582                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8583                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8584                     DisplayMove(currentMove - 1);
8585                 }
8586                 return;
8587
8588             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8589                 /* crafty (9.25+) says "(only move) <move>"
8590                  * if there is only 1 legal move
8591                  */
8592                 sscanf(p, "(only move) %s", buf1);
8593                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8594                 sprintf(programStats.movelist, "%s (only move)", buf1);
8595                 programStats.depth = 1;
8596                 programStats.nr_moves = 1;
8597                 programStats.moves_left = 1;
8598                 programStats.nodes = 1;
8599                 programStats.time = 1;
8600                 programStats.got_only_move = 1;
8601
8602                 /* Not really, but we also use this member to
8603                    mean "line isn't going to change" (Crafty
8604                    isn't searching, so stats won't change) */
8605                 programStats.line_is_book = 1;
8606
8607                 SendProgramStatsToFrontend( cps, &programStats );
8608
8609                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8610                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8611                     DisplayMove(currentMove - 1);
8612                 }
8613                 return;
8614             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8615                               &time, &nodes, &plylev, &mvleft,
8616                               &mvtot, mvname) >= 5) {
8617                 /* The stat01: line is from Crafty (9.29+) in response
8618                    to the "." command */
8619                 programStats.seen_stat = 1;
8620                 cps->maybeThinking = TRUE;
8621
8622                 if (programStats.got_only_move || !appData.periodicUpdates)
8623                   return;
8624
8625                 programStats.depth = plylev;
8626                 programStats.time = time;
8627                 programStats.nodes = nodes;
8628                 programStats.moves_left = mvleft;
8629                 programStats.nr_moves = mvtot;
8630                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8631                 programStats.ok_to_send = 1;
8632                 programStats.movelist[0] = '\0';
8633
8634                 SendProgramStatsToFrontend( cps, &programStats );
8635
8636                 return;
8637
8638             } else if (strncmp(message,"++",2) == 0) {
8639                 /* Crafty 9.29+ outputs this */
8640                 programStats.got_fail = 2;
8641                 return;
8642
8643             } else if (strncmp(message,"--",2) == 0) {
8644                 /* Crafty 9.29+ outputs this */
8645                 programStats.got_fail = 1;
8646                 return;
8647
8648             } else if (thinkOutput[0] != NULLCHAR &&
8649                        strncmp(message, "    ", 4) == 0) {
8650                 unsigned message_len;
8651
8652                 p = message;
8653                 while (*p && *p == ' ') p++;
8654
8655                 message_len = strlen( p );
8656
8657                 /* [AS] Avoid buffer overflow */
8658                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8659                     strcat(thinkOutput, " ");
8660                     strcat(thinkOutput, p);
8661                 }
8662
8663                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8664                     strcat(programStats.movelist, " ");
8665                     strcat(programStats.movelist, p);
8666                 }
8667
8668                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8669                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8670                     DisplayMove(currentMove - 1);
8671                 }
8672                 return;
8673             }
8674         }
8675         else {
8676             buf1[0] = NULLCHAR;
8677
8678             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8679                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8680             {
8681                 ChessProgramStats cpstats;
8682
8683                 if (plyext != ' ' && plyext != '\t') {
8684                     time *= 100;
8685                 }
8686
8687                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8688                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8689                     curscore = -curscore;
8690                 }
8691
8692                 cpstats.depth = plylev;
8693                 cpstats.nodes = nodes;
8694                 cpstats.time = time;
8695                 cpstats.score = curscore;
8696                 cpstats.got_only_move = 0;
8697                 cpstats.movelist[0] = '\0';
8698
8699                 if (buf1[0] != NULLCHAR) {
8700                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8701                 }
8702
8703                 cpstats.ok_to_send = 0;
8704                 cpstats.line_is_book = 0;
8705                 cpstats.nr_moves = 0;
8706                 cpstats.moves_left = 0;
8707
8708                 SendProgramStatsToFrontend( cps, &cpstats );
8709             }
8710         }
8711     }
8712 }
8713
8714
8715 /* Parse a game score from the character string "game", and
8716    record it as the history of the current game.  The game
8717    score is NOT assumed to start from the standard position.
8718    The display is not updated in any way.
8719    */
8720 void
8721 ParseGameHistory(game)
8722      char *game;
8723 {
8724     ChessMove moveType;
8725     int fromX, fromY, toX, toY, boardIndex;
8726     char promoChar;
8727     char *p, *q;
8728     char buf[MSG_SIZ];
8729
8730     if (appData.debugMode)
8731       fprintf(debugFP, "Parsing game history: %s\n", game);
8732
8733     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8734     gameInfo.site = StrSave(appData.icsHost);
8735     gameInfo.date = PGNDate();
8736     gameInfo.round = StrSave("-");
8737
8738     /* Parse out names of players */
8739     while (*game == ' ') game++;
8740     p = buf;
8741     while (*game != ' ') *p++ = *game++;
8742     *p = NULLCHAR;
8743     gameInfo.white = StrSave(buf);
8744     while (*game == ' ') game++;
8745     p = buf;
8746     while (*game != ' ' && *game != '\n') *p++ = *game++;
8747     *p = NULLCHAR;
8748     gameInfo.black = StrSave(buf);
8749
8750     /* Parse moves */
8751     boardIndex = blackPlaysFirst ? 1 : 0;
8752     yynewstr(game);
8753     for (;;) {
8754         yyboardindex = boardIndex;
8755         moveType = (ChessMove) Myylex();
8756         switch (moveType) {
8757           case IllegalMove:             /* maybe suicide chess, etc. */
8758   if (appData.debugMode) {
8759     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8760     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8761     setbuf(debugFP, NULL);
8762   }
8763           case WhitePromotion:
8764           case BlackPromotion:
8765           case WhiteNonPromotion:
8766           case BlackNonPromotion:
8767           case NormalMove:
8768           case WhiteCapturesEnPassant:
8769           case BlackCapturesEnPassant:
8770           case WhiteKingSideCastle:
8771           case WhiteQueenSideCastle:
8772           case BlackKingSideCastle:
8773           case BlackQueenSideCastle:
8774           case WhiteKingSideCastleWild:
8775           case WhiteQueenSideCastleWild:
8776           case BlackKingSideCastleWild:
8777           case BlackQueenSideCastleWild:
8778           /* PUSH Fabien */
8779           case WhiteHSideCastleFR:
8780           case WhiteASideCastleFR:
8781           case BlackHSideCastleFR:
8782           case BlackASideCastleFR:
8783           /* POP Fabien */
8784             fromX = currentMoveString[0] - AAA;
8785             fromY = currentMoveString[1] - ONE;
8786             toX = currentMoveString[2] - AAA;
8787             toY = currentMoveString[3] - ONE;
8788             promoChar = currentMoveString[4];
8789             break;
8790           case WhiteDrop:
8791           case BlackDrop:
8792             fromX = moveType == WhiteDrop ?
8793               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8794             (int) CharToPiece(ToLower(currentMoveString[0]));
8795             fromY = DROP_RANK;
8796             toX = currentMoveString[2] - AAA;
8797             toY = currentMoveString[3] - ONE;
8798             promoChar = NULLCHAR;
8799             break;
8800           case AmbiguousMove:
8801             /* bug? */
8802             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8803   if (appData.debugMode) {
8804     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8805     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8806     setbuf(debugFP, NULL);
8807   }
8808             DisplayError(buf, 0);
8809             return;
8810           case ImpossibleMove:
8811             /* bug? */
8812             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8813   if (appData.debugMode) {
8814     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8815     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8816     setbuf(debugFP, NULL);
8817   }
8818             DisplayError(buf, 0);
8819             return;
8820           case EndOfFile:
8821             if (boardIndex < backwardMostMove) {
8822                 /* Oops, gap.  How did that happen? */
8823                 DisplayError(_("Gap in move list"), 0);
8824                 return;
8825             }
8826             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8827             if (boardIndex > forwardMostMove) {
8828                 forwardMostMove = boardIndex;
8829             }
8830             return;
8831           case ElapsedTime:
8832             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8833                 strcat(parseList[boardIndex-1], " ");
8834                 strcat(parseList[boardIndex-1], yy_text);
8835             }
8836             continue;
8837           case Comment:
8838           case PGNTag:
8839           case NAG:
8840           default:
8841             /* ignore */
8842             continue;
8843           case WhiteWins:
8844           case BlackWins:
8845           case GameIsDrawn:
8846           case GameUnfinished:
8847             if (gameMode == IcsExamining) {
8848                 if (boardIndex < backwardMostMove) {
8849                     /* Oops, gap.  How did that happen? */
8850                     return;
8851                 }
8852                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8853                 return;
8854             }
8855             gameInfo.result = moveType;
8856             p = strchr(yy_text, '{');
8857             if (p == NULL) p = strchr(yy_text, '(');
8858             if (p == NULL) {
8859                 p = yy_text;
8860                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8861             } else {
8862                 q = strchr(p, *p == '{' ? '}' : ')');
8863                 if (q != NULL) *q = NULLCHAR;
8864                 p++;
8865             }
8866             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8867             gameInfo.resultDetails = StrSave(p);
8868             continue;
8869         }
8870         if (boardIndex >= forwardMostMove &&
8871             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8872             backwardMostMove = blackPlaysFirst ? 1 : 0;
8873             return;
8874         }
8875         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8876                                  fromY, fromX, toY, toX, promoChar,
8877                                  parseList[boardIndex]);
8878         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8879         /* currentMoveString is set as a side-effect of yylex */
8880         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8881         strcat(moveList[boardIndex], "\n");
8882         boardIndex++;
8883         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8884         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8885           case MT_NONE:
8886           case MT_STALEMATE:
8887           default:
8888             break;
8889           case MT_CHECK:
8890             if(gameInfo.variant != VariantShogi)
8891                 strcat(parseList[boardIndex - 1], "+");
8892             break;
8893           case MT_CHECKMATE:
8894           case MT_STAINMATE:
8895             strcat(parseList[boardIndex - 1], "#");
8896             break;
8897         }
8898     }
8899 }
8900
8901
8902 /* Apply a move to the given board  */
8903 void
8904 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8905      int fromX, fromY, toX, toY;
8906      int promoChar;
8907      Board board;
8908 {
8909   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8910   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8911
8912     /* [HGM] compute & store e.p. status and castling rights for new position */
8913     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8914
8915       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8916       oldEP = (signed char)board[EP_STATUS];
8917       board[EP_STATUS] = EP_NONE;
8918
8919       if( board[toY][toX] != EmptySquare )
8920            board[EP_STATUS] = EP_CAPTURE;
8921
8922   if (fromY == DROP_RANK) {
8923         /* must be first */
8924         piece = board[toY][toX] = (ChessSquare) fromX;
8925   } else {
8926       int i;
8927
8928       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8929            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8930                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8931       } else
8932       if( board[fromY][fromX] == WhitePawn ) {
8933            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8934                board[EP_STATUS] = EP_PAWN_MOVE;
8935            if( toY-fromY==2) {
8936                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8937                         gameInfo.variant != VariantBerolina || toX < fromX)
8938                       board[EP_STATUS] = toX | berolina;
8939                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8940                         gameInfo.variant != VariantBerolina || toX > fromX)
8941                       board[EP_STATUS] = toX;
8942            }
8943       } else
8944       if( board[fromY][fromX] == BlackPawn ) {
8945            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8946                board[EP_STATUS] = EP_PAWN_MOVE;
8947            if( toY-fromY== -2) {
8948                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8949                         gameInfo.variant != VariantBerolina || toX < fromX)
8950                       board[EP_STATUS] = toX | berolina;
8951                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8952                         gameInfo.variant != VariantBerolina || toX > fromX)
8953                       board[EP_STATUS] = toX;
8954            }
8955        }
8956
8957        for(i=0; i<nrCastlingRights; i++) {
8958            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8959               board[CASTLING][i] == toX   && castlingRank[i] == toY
8960              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8961        }
8962
8963      if (fromX == toX && fromY == toY) return;
8964
8965      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8966      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8967      if(gameInfo.variant == VariantKnightmate)
8968          king += (int) WhiteUnicorn - (int) WhiteKing;
8969
8970     /* Code added by Tord: */
8971     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8972     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8973         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8974       board[fromY][fromX] = EmptySquare;
8975       board[toY][toX] = EmptySquare;
8976       if((toX > fromX) != (piece == WhiteRook)) {
8977         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8978       } else {
8979         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8980       }
8981     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8982                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8983       board[fromY][fromX] = EmptySquare;
8984       board[toY][toX] = EmptySquare;
8985       if((toX > fromX) != (piece == BlackRook)) {
8986         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8987       } else {
8988         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8989       }
8990     /* End of code added by Tord */
8991
8992     } else if (board[fromY][fromX] == king
8993         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8994         && toY == fromY && toX > fromX+1) {
8995         board[fromY][fromX] = EmptySquare;
8996         board[toY][toX] = king;
8997         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8998         board[fromY][BOARD_RGHT-1] = EmptySquare;
8999     } else if (board[fromY][fromX] == king
9000         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9001                && toY == fromY && toX < fromX-1) {
9002         board[fromY][fromX] = EmptySquare;
9003         board[toY][toX] = king;
9004         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9005         board[fromY][BOARD_LEFT] = EmptySquare;
9006     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9007                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9008                && toY >= BOARD_HEIGHT-promoRank
9009                ) {
9010         /* white pawn promotion */
9011         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9012         if (board[toY][toX] == EmptySquare) {
9013             board[toY][toX] = WhiteQueen;
9014         }
9015         if(gameInfo.variant==VariantBughouse ||
9016            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9017             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9018         board[fromY][fromX] = EmptySquare;
9019     } else if ((fromY == BOARD_HEIGHT-4)
9020                && (toX != fromX)
9021                && gameInfo.variant != VariantXiangqi
9022                && gameInfo.variant != VariantBerolina
9023                && (board[fromY][fromX] == WhitePawn)
9024                && (board[toY][toX] == EmptySquare)) {
9025         board[fromY][fromX] = EmptySquare;
9026         board[toY][toX] = WhitePawn;
9027         captured = board[toY - 1][toX];
9028         board[toY - 1][toX] = EmptySquare;
9029     } else if ((fromY == BOARD_HEIGHT-4)
9030                && (toX == fromX)
9031                && gameInfo.variant == VariantBerolina
9032                && (board[fromY][fromX] == WhitePawn)
9033                && (board[toY][toX] == EmptySquare)) {
9034         board[fromY][fromX] = EmptySquare;
9035         board[toY][toX] = WhitePawn;
9036         if(oldEP & EP_BEROLIN_A) {
9037                 captured = board[fromY][fromX-1];
9038                 board[fromY][fromX-1] = EmptySquare;
9039         }else{  captured = board[fromY][fromX+1];
9040                 board[fromY][fromX+1] = EmptySquare;
9041         }
9042     } else if (board[fromY][fromX] == king
9043         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9044                && toY == fromY && toX > fromX+1) {
9045         board[fromY][fromX] = EmptySquare;
9046         board[toY][toX] = king;
9047         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9048         board[fromY][BOARD_RGHT-1] = EmptySquare;
9049     } else if (board[fromY][fromX] == king
9050         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9051                && toY == fromY && toX < fromX-1) {
9052         board[fromY][fromX] = EmptySquare;
9053         board[toY][toX] = king;
9054         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9055         board[fromY][BOARD_LEFT] = EmptySquare;
9056     } else if (fromY == 7 && fromX == 3
9057                && board[fromY][fromX] == BlackKing
9058                && toY == 7 && toX == 5) {
9059         board[fromY][fromX] = EmptySquare;
9060         board[toY][toX] = BlackKing;
9061         board[fromY][7] = EmptySquare;
9062         board[toY][4] = BlackRook;
9063     } else if (fromY == 7 && fromX == 3
9064                && board[fromY][fromX] == BlackKing
9065                && toY == 7 && toX == 1) {
9066         board[fromY][fromX] = EmptySquare;
9067         board[toY][toX] = BlackKing;
9068         board[fromY][0] = EmptySquare;
9069         board[toY][2] = BlackRook;
9070     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9071                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9072                && toY < promoRank
9073                ) {
9074         /* black pawn promotion */
9075         board[toY][toX] = CharToPiece(ToLower(promoChar));
9076         if (board[toY][toX] == EmptySquare) {
9077             board[toY][toX] = BlackQueen;
9078         }
9079         if(gameInfo.variant==VariantBughouse ||
9080            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9081             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9082         board[fromY][fromX] = EmptySquare;
9083     } else if ((fromY == 3)
9084                && (toX != fromX)
9085                && gameInfo.variant != VariantXiangqi
9086                && gameInfo.variant != VariantBerolina
9087                && (board[fromY][fromX] == BlackPawn)
9088                && (board[toY][toX] == EmptySquare)) {
9089         board[fromY][fromX] = EmptySquare;
9090         board[toY][toX] = BlackPawn;
9091         captured = board[toY + 1][toX];
9092         board[toY + 1][toX] = EmptySquare;
9093     } else if ((fromY == 3)
9094                && (toX == fromX)
9095                && gameInfo.variant == VariantBerolina
9096                && (board[fromY][fromX] == BlackPawn)
9097                && (board[toY][toX] == EmptySquare)) {
9098         board[fromY][fromX] = EmptySquare;
9099         board[toY][toX] = BlackPawn;
9100         if(oldEP & EP_BEROLIN_A) {
9101                 captured = board[fromY][fromX-1];
9102                 board[fromY][fromX-1] = EmptySquare;
9103         }else{  captured = board[fromY][fromX+1];
9104                 board[fromY][fromX+1] = EmptySquare;
9105         }
9106     } else {
9107         board[toY][toX] = board[fromY][fromX];
9108         board[fromY][fromX] = EmptySquare;
9109     }
9110   }
9111
9112     if (gameInfo.holdingsWidth != 0) {
9113
9114       /* !!A lot more code needs to be written to support holdings  */
9115       /* [HGM] OK, so I have written it. Holdings are stored in the */
9116       /* penultimate board files, so they are automaticlly stored   */
9117       /* in the game history.                                       */
9118       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9119                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9120         /* Delete from holdings, by decreasing count */
9121         /* and erasing image if necessary            */
9122         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9123         if(p < (int) BlackPawn) { /* white drop */
9124              p -= (int)WhitePawn;
9125                  p = PieceToNumber((ChessSquare)p);
9126              if(p >= gameInfo.holdingsSize) p = 0;
9127              if(--board[p][BOARD_WIDTH-2] <= 0)
9128                   board[p][BOARD_WIDTH-1] = EmptySquare;
9129              if((int)board[p][BOARD_WIDTH-2] < 0)
9130                         board[p][BOARD_WIDTH-2] = 0;
9131         } else {                  /* black drop */
9132              p -= (int)BlackPawn;
9133                  p = PieceToNumber((ChessSquare)p);
9134              if(p >= gameInfo.holdingsSize) p = 0;
9135              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9136                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9137              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9138                         board[BOARD_HEIGHT-1-p][1] = 0;
9139         }
9140       }
9141       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9142           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9143         /* [HGM] holdings: Add to holdings, if holdings exist */
9144         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9145                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9146                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9147         }
9148         p = (int) captured;
9149         if (p >= (int) BlackPawn) {
9150           p -= (int)BlackPawn;
9151           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9152                   /* in Shogi restore piece to its original  first */
9153                   captured = (ChessSquare) (DEMOTED captured);
9154                   p = DEMOTED p;
9155           }
9156           p = PieceToNumber((ChessSquare)p);
9157           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9158           board[p][BOARD_WIDTH-2]++;
9159           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9160         } else {
9161           p -= (int)WhitePawn;
9162           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9163                   captured = (ChessSquare) (DEMOTED captured);
9164                   p = DEMOTED p;
9165           }
9166           p = PieceToNumber((ChessSquare)p);
9167           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9168           board[BOARD_HEIGHT-1-p][1]++;
9169           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9170         }
9171       }
9172     } else if (gameInfo.variant == VariantAtomic) {
9173       if (captured != EmptySquare) {
9174         int y, x;
9175         for (y = toY-1; y <= toY+1; y++) {
9176           for (x = toX-1; x <= toX+1; x++) {
9177             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9178                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9179               board[y][x] = EmptySquare;
9180             }
9181           }
9182         }
9183         board[toY][toX] = EmptySquare;
9184       }
9185     }
9186     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9187         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9188     } else
9189     if(promoChar == '+') {
9190         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9191         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9192     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9193         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9194     }
9195     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9196                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9197         // [HGM] superchess: take promotion piece out of holdings
9198         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9199         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9200             if(!--board[k][BOARD_WIDTH-2])
9201                 board[k][BOARD_WIDTH-1] = EmptySquare;
9202         } else {
9203             if(!--board[BOARD_HEIGHT-1-k][1])
9204                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9205         }
9206     }
9207
9208 }
9209
9210 /* Updates forwardMostMove */
9211 void
9212 MakeMove(fromX, fromY, toX, toY, promoChar)
9213      int fromX, fromY, toX, toY;
9214      int promoChar;
9215 {
9216 //    forwardMostMove++; // [HGM] bare: moved downstream
9217
9218     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9219         int timeLeft; static int lastLoadFlag=0; int king, piece;
9220         piece = boards[forwardMostMove][fromY][fromX];
9221         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9222         if(gameInfo.variant == VariantKnightmate)
9223             king += (int) WhiteUnicorn - (int) WhiteKing;
9224         if(forwardMostMove == 0) {
9225             if(blackPlaysFirst)
9226                 fprintf(serverMoves, "%s;", second.tidy);
9227             fprintf(serverMoves, "%s;", first.tidy);
9228             if(!blackPlaysFirst)
9229                 fprintf(serverMoves, "%s;", second.tidy);
9230         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9231         lastLoadFlag = loadFlag;
9232         // print base move
9233         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9234         // print castling suffix
9235         if( toY == fromY && piece == king ) {
9236             if(toX-fromX > 1)
9237                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9238             if(fromX-toX >1)
9239                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9240         }
9241         // e.p. suffix
9242         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9243              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9244              boards[forwardMostMove][toY][toX] == EmptySquare
9245              && fromX != toX && fromY != toY)
9246                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9247         // promotion suffix
9248         if(promoChar != NULLCHAR)
9249                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9250         if(!loadFlag) {
9251             fprintf(serverMoves, "/%d/%d",
9252                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9253             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9254             else                      timeLeft = blackTimeRemaining/1000;
9255             fprintf(serverMoves, "/%d", timeLeft);
9256         }
9257         fflush(serverMoves);
9258     }
9259
9260     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9261       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9262                         0, 1);
9263       return;
9264     }
9265     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9266     if (commentList[forwardMostMove+1] != NULL) {
9267         free(commentList[forwardMostMove+1]);
9268         commentList[forwardMostMove+1] = NULL;
9269     }
9270     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9271     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9272     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9273     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9274     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9275     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9276     gameInfo.result = GameUnfinished;
9277     if (gameInfo.resultDetails != NULL) {
9278         free(gameInfo.resultDetails);
9279         gameInfo.resultDetails = NULL;
9280     }
9281     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9282                               moveList[forwardMostMove - 1]);
9283     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9284                              PosFlags(forwardMostMove - 1),
9285                              fromY, fromX, toY, toX, promoChar,
9286                              parseList[forwardMostMove - 1]);
9287     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9288       case MT_NONE:
9289       case MT_STALEMATE:
9290       default:
9291         break;
9292       case MT_CHECK:
9293         if(gameInfo.variant != VariantShogi)
9294             strcat(parseList[forwardMostMove - 1], "+");
9295         break;
9296       case MT_CHECKMATE:
9297       case MT_STAINMATE:
9298         strcat(parseList[forwardMostMove - 1], "#");
9299         break;
9300     }
9301     if (appData.debugMode) {
9302         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9303     }
9304
9305 }
9306
9307 /* Updates currentMove if not pausing */
9308 void
9309 ShowMove(fromX, fromY, toX, toY)
9310 {
9311     int instant = (gameMode == PlayFromGameFile) ?
9312         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9313     if(appData.noGUI) return;
9314     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9315         if (!instant) {
9316             if (forwardMostMove == currentMove + 1) {
9317                 AnimateMove(boards[forwardMostMove - 1],
9318                             fromX, fromY, toX, toY);
9319             }
9320             if (appData.highlightLastMove) {
9321                 SetHighlights(fromX, fromY, toX, toY);
9322             }
9323         }
9324         currentMove = forwardMostMove;
9325     }
9326
9327     if (instant) return;
9328
9329     DisplayMove(currentMove - 1);
9330     DrawPosition(FALSE, boards[currentMove]);
9331     DisplayBothClocks();
9332     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9333     DisplayBook(currentMove);
9334 }
9335
9336 void SendEgtPath(ChessProgramState *cps)
9337 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9338         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9339
9340         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9341
9342         while(*p) {
9343             char c, *q = name+1, *r, *s;
9344
9345             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9346             while(*p && *p != ',') *q++ = *p++;
9347             *q++ = ':'; *q = 0;
9348             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9349                 strcmp(name, ",nalimov:") == 0 ) {
9350                 // take nalimov path from the menu-changeable option first, if it is defined
9351               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9352                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9353             } else
9354             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9355                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9356                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9357                 s = r = StrStr(s, ":") + 1; // beginning of path info
9358                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9359                 c = *r; *r = 0;             // temporarily null-terminate path info
9360                     *--q = 0;               // strip of trailig ':' from name
9361                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9362                 *r = c;
9363                 SendToProgram(buf,cps);     // send egtbpath command for this format
9364             }
9365             if(*p == ',') p++; // read away comma to position for next format name
9366         }
9367 }
9368
9369 void
9370 InitChessProgram(cps, setup)
9371      ChessProgramState *cps;
9372      int setup; /* [HGM] needed to setup FRC opening position */
9373 {
9374     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9375     if (appData.noChessProgram) return;
9376     hintRequested = FALSE;
9377     bookRequested = FALSE;
9378
9379     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9380     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9381     if(cps->memSize) { /* [HGM] memory */
9382       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9383         SendToProgram(buf, cps);
9384     }
9385     SendEgtPath(cps); /* [HGM] EGT */
9386     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9387       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9388         SendToProgram(buf, cps);
9389     }
9390
9391     SendToProgram(cps->initString, cps);
9392     if (gameInfo.variant != VariantNormal &&
9393         gameInfo.variant != VariantLoadable
9394         /* [HGM] also send variant if board size non-standard */
9395         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9396                                             ) {
9397       char *v = VariantName(gameInfo.variant);
9398       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9399         /* [HGM] in protocol 1 we have to assume all variants valid */
9400         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9401         DisplayFatalError(buf, 0, 1);
9402         return;
9403       }
9404
9405       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9406       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9407       if( gameInfo.variant == VariantXiangqi )
9408            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9409       if( gameInfo.variant == VariantShogi )
9410            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9411       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9412            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9413       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9414           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9415            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9416       if( gameInfo.variant == VariantCourier )
9417            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9418       if( gameInfo.variant == VariantSuper )
9419            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9420       if( gameInfo.variant == VariantGreat )
9421            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9422       if( gameInfo.variant == VariantSChess )
9423            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9424
9425       if(overruled) {
9426         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9427                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9428            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9429            if(StrStr(cps->variants, b) == NULL) {
9430                // specific sized variant not known, check if general sizing allowed
9431                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9432                    if(StrStr(cps->variants, "boardsize") == NULL) {
9433                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9434                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9435                        DisplayFatalError(buf, 0, 1);
9436                        return;
9437                    }
9438                    /* [HGM] here we really should compare with the maximum supported board size */
9439                }
9440            }
9441       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9442       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9443       SendToProgram(buf, cps);
9444     }
9445     currentlyInitializedVariant = gameInfo.variant;
9446
9447     /* [HGM] send opening position in FRC to first engine */
9448     if(setup) {
9449           SendToProgram("force\n", cps);
9450           SendBoard(cps, 0);
9451           /* engine is now in force mode! Set flag to wake it up after first move. */
9452           setboardSpoiledMachineBlack = 1;
9453     }
9454
9455     if (cps->sendICS) {
9456       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9457       SendToProgram(buf, cps);
9458     }
9459     cps->maybeThinking = FALSE;
9460     cps->offeredDraw = 0;
9461     if (!appData.icsActive) {
9462         SendTimeControl(cps, movesPerSession, timeControl,
9463                         timeIncrement, appData.searchDepth,
9464                         searchTime);
9465     }
9466     if (appData.showThinking
9467         // [HGM] thinking: four options require thinking output to be sent
9468         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9469                                 ) {
9470         SendToProgram("post\n", cps);
9471     }
9472     SendToProgram("hard\n", cps);
9473     if (!appData.ponderNextMove) {
9474         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9475            it without being sure what state we are in first.  "hard"
9476            is not a toggle, so that one is OK.
9477          */
9478         SendToProgram("easy\n", cps);
9479     }
9480     if (cps->usePing) {
9481       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9482       SendToProgram(buf, cps);
9483     }
9484     cps->initDone = TRUE;
9485 }
9486
9487
9488 void
9489 StartChessProgram(cps)
9490      ChessProgramState *cps;
9491 {
9492     char buf[MSG_SIZ];
9493     int err;
9494
9495     if (appData.noChessProgram) return;
9496     cps->initDone = FALSE;
9497
9498     if (strcmp(cps->host, "localhost") == 0) {
9499         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9500     } else if (*appData.remoteShell == NULLCHAR) {
9501         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9502     } else {
9503         if (*appData.remoteUser == NULLCHAR) {
9504           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9505                     cps->program);
9506         } else {
9507           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9508                     cps->host, appData.remoteUser, cps->program);
9509         }
9510         err = StartChildProcess(buf, "", &cps->pr);
9511     }
9512
9513     if (err != 0) {
9514       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9515         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9516         if(cps != &first) return;
9517         appData.noChessProgram = TRUE;
9518         ThawUI();
9519         SetNCPMode();
9520 //      DisplayFatalError(buf, err, 1);
9521 //      cps->pr = NoProc;
9522 //      cps->isr = NULL;
9523         return;
9524     }
9525
9526     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9527     if (cps->protocolVersion > 1) {
9528       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9529       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9530       cps->comboCnt = 0;  //                and values of combo boxes
9531       SendToProgram(buf, cps);
9532     } else {
9533       SendToProgram("xboard\n", cps);
9534     }
9535 }
9536
9537 void
9538 TwoMachinesEventIfReady P((void))
9539 {
9540   static int curMess = 0;
9541   if (first.lastPing != first.lastPong) {
9542     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9543     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9544     return;
9545   }
9546   if (second.lastPing != second.lastPong) {
9547     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9548     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9549     return;
9550   }
9551   DisplayMessage("", ""); curMess = 0;
9552   ThawUI();
9553   TwoMachinesEvent();
9554 }
9555
9556 char *
9557 MakeName(char *template)
9558 {
9559     time_t clock;
9560     struct tm *tm;
9561     static char buf[MSG_SIZ];
9562     char *p = buf;
9563     int i;
9564
9565     clock = time((time_t *)NULL);
9566     tm = localtime(&clock);
9567
9568     while(*p++ = *template++) if(p[-1] == '%') {
9569         switch(*template++) {
9570           case 0:   *p = 0; return buf;
9571           case 'Y': i = tm->tm_year+1900; break;
9572           case 'y': i = tm->tm_year-100; break;
9573           case 'M': i = tm->tm_mon+1; break;
9574           case 'd': i = tm->tm_mday; break;
9575           case 'h': i = tm->tm_hour; break;
9576           case 'm': i = tm->tm_min; break;
9577           case 's': i = tm->tm_sec; break;
9578           default:  i = 0;
9579         }
9580         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9581     }
9582     return buf;
9583 }
9584
9585 int
9586 CountPlayers(char *p)
9587 {
9588     int n = 0;
9589     while(p = strchr(p, '\n')) p++, n++; // count participants
9590     return n;
9591 }
9592
9593 FILE *
9594 WriteTourneyFile(char *results)
9595 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9596     FILE *f = fopen(appData.tourneyFile, "w");
9597     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9598         // create a file with tournament description
9599         fprintf(f, "-participants {%s}\n", appData.participants);
9600         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9601         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9602         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9603         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9604         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9605         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9606         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9607         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9608         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9609         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9610         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9611         if(searchTime > 0)
9612                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9613         else {
9614                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9615                 fprintf(f, "-tc %s\n", appData.timeControl);
9616                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9617         }
9618         fprintf(f, "-results \"%s\"\n", results);
9619     }
9620     return f;
9621 }
9622
9623 int
9624 CreateTourney(char *name)
9625 {
9626         FILE *f;
9627         if(name[0] == NULLCHAR) {
9628             if(appData.participants[0])
9629                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9630             return 0;
9631         }
9632         f = fopen(name, "r");
9633         if(f) { // file exists
9634             ASSIGN(appData.tourneyFile, name);
9635             ParseArgsFromFile(f); // parse it
9636         } else {
9637             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9638             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9639                 DisplayError(_("Not enough participants"), 0);
9640                 return 0;
9641             }
9642             ASSIGN(appData.tourneyFile, name);
9643             if((f = WriteTourneyFile("")) == NULL) return 0;
9644         }
9645         fclose(f);
9646         appData.noChessProgram = FALSE;
9647         appData.clockMode = TRUE;
9648         SetGNUMode();
9649         return 1;
9650 }
9651
9652 #define MAXENGINES 1000
9653 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9654
9655 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9656 {
9657     char buf[MSG_SIZ], *p, *q;
9658     int i=1;
9659     while(*names) {
9660         p = names; q = buf;
9661         while(*p && *p != '\n') *q++ = *p++;
9662         *q = 0;
9663         if(engineList[i]) free(engineList[i]);
9664         engineList[i] = strdup(buf);
9665         if(*p == '\n') p++;
9666         TidyProgramName(engineList[i], "localhost", buf);
9667         if(engineMnemonic[i]) free(engineMnemonic[i]);
9668         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9669             strcat(buf, " (");
9670             sscanf(q + 8, "%s", buf + strlen(buf));
9671             strcat(buf, ")");
9672         }
9673         engineMnemonic[i] = strdup(buf);
9674         names = p; i++;
9675       if(i > MAXENGINES - 2) break;
9676     }
9677     engineList[i] = NULL;
9678 }
9679
9680 // following implemented as macro to avoid type limitations
9681 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9682
9683 void SwapEngines(int n)
9684 {   // swap settings for first engine and other engine (so far only some selected options)
9685     int h;
9686     char *p;
9687     if(n == 0) return;
9688     SWAP(directory, p)
9689     SWAP(chessProgram, p)
9690     SWAP(isUCI, h)
9691     SWAP(hasOwnBookUCI, h)
9692     SWAP(protocolVersion, h)
9693     SWAP(reuse, h)
9694     SWAP(scoreIsAbsolute, h)
9695     SWAP(timeOdds, h)
9696     SWAP(logo, p)
9697     SWAP(pgnName, p)
9698 }
9699
9700 void
9701 SetPlayer(int player)
9702 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9703     int i;
9704     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9705     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9706     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9707     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9708     if(mnemonic[i]) {
9709         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9710         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9711         ParseArgsFromString(buf);
9712     }
9713     free(engineName);
9714 }
9715
9716 int
9717 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9718 {   // determine players from game number
9719     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9720
9721     if(appData.tourneyType == 0) {
9722         roundsPerCycle = (nPlayers - 1) | 1;
9723         pairingsPerRound = nPlayers / 2;
9724     } else if(appData.tourneyType > 0) {
9725         roundsPerCycle = nPlayers - appData.tourneyType;
9726         pairingsPerRound = appData.tourneyType;
9727     }
9728     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9729     gamesPerCycle = gamesPerRound * roundsPerCycle;
9730     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9731     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9732     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9733     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9734     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9735     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9736
9737     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9738     if(appData.roundSync) *syncInterval = gamesPerRound;
9739
9740     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9741
9742     if(appData.tourneyType == 0) {
9743         if(curPairing == (nPlayers-1)/2 ) {
9744             *whitePlayer = curRound;
9745             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9746         } else {
9747             *whitePlayer = curRound - pairingsPerRound + curPairing;
9748             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9749             *blackPlayer = curRound + pairingsPerRound - curPairing;
9750             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9751         }
9752     } else if(appData.tourneyType > 0) {
9753         *whitePlayer = curPairing;
9754         *blackPlayer = curRound + appData.tourneyType;
9755     }
9756
9757     // take care of white/black alternation per round. 
9758     // For cycles and games this is already taken care of by default, derived from matchGame!
9759     return curRound & 1;
9760 }
9761
9762 int
9763 NextTourneyGame(int nr, int *swapColors)
9764 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9765     char *p, *q;
9766     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9767     FILE *tf;
9768     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9769     tf = fopen(appData.tourneyFile, "r");
9770     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9771     ParseArgsFromFile(tf); fclose(tf);
9772     InitTimeControls(); // TC might be altered from tourney file
9773
9774     nPlayers = CountPlayers(appData.participants); // count participants
9775     if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
9776         if(nr>=0 && !pairingReceived) {
9777             char buf[1<<16];
9778             if(pairing.pr == NoProc) StartChessProgram(&pairing);
9779             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9780             SendToProgram(buf, &pairing);
9781             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9782             SendToProgram(buf, &pairing);
9783             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9784         }
9785         pairingReceived = 0;                              // ... so we continue here 
9786         syncInterval = nPlayers/2; *swapColors = 0;
9787         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9788         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9789         matchGame = 1; roundNr = nr / syncInterval + 1;
9790     } else
9791     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9792
9793     if(syncInterval) {
9794         p = q = appData.results;
9795         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9796         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9797             DisplayMessage(_("Waiting for other game(s)"),"");
9798             waitingForGame = TRUE;
9799             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9800             return 0;
9801         }
9802         waitingForGame = FALSE;
9803     }
9804
9805     if(first.pr != NoProc) return 1; // engines already loaded
9806
9807     // redefine engines, engine dir, etc.
9808     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9809     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9810     SwapEngines(1);
9811     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9812     SwapEngines(1);         // and make that valid for second engine by swapping
9813     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9814     InitEngine(&second, 1);
9815     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9816     return 1;
9817 }
9818
9819 void
9820 NextMatchGame()
9821 {   // performs game initialization that does not invoke engines, and then tries to start the game
9822     int firstWhite, swapColors = 0;
9823     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9824     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9825     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9826     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9827     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9828     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9829     Reset(FALSE, first.pr != NoProc);
9830     appData.noChessProgram = FALSE;
9831     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9832     TwoMachinesEvent();
9833 }
9834
9835 void UserAdjudicationEvent( int result )
9836 {
9837     ChessMove gameResult = GameIsDrawn;
9838
9839     if( result > 0 ) {
9840         gameResult = WhiteWins;
9841     }
9842     else if( result < 0 ) {
9843         gameResult = BlackWins;
9844     }
9845
9846     if( gameMode == TwoMachinesPlay ) {
9847         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9848     }
9849 }
9850
9851
9852 // [HGM] save: calculate checksum of game to make games easily identifiable
9853 int StringCheckSum(char *s)
9854 {
9855         int i = 0;
9856         if(s==NULL) return 0;
9857         while(*s) i = i*259 + *s++;
9858         return i;
9859 }
9860
9861 int GameCheckSum()
9862 {
9863         int i, sum=0;
9864         for(i=backwardMostMove; i<forwardMostMove; i++) {
9865                 sum += pvInfoList[i].depth;
9866                 sum += StringCheckSum(parseList[i]);
9867                 sum += StringCheckSum(commentList[i]);
9868                 sum *= 261;
9869         }
9870         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9871         return sum + StringCheckSum(commentList[i]);
9872 } // end of save patch
9873
9874 void
9875 GameEnds(result, resultDetails, whosays)
9876      ChessMove result;
9877      char *resultDetails;
9878      int whosays;
9879 {
9880     GameMode nextGameMode;
9881     int isIcsGame;
9882     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9883
9884     if(endingGame) return; /* [HGM] crash: forbid recursion */
9885     endingGame = 1;
9886     if(twoBoards) { // [HGM] dual: switch back to one board
9887         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9888         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9889     }
9890     if (appData.debugMode) {
9891       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9892               result, resultDetails ? resultDetails : "(null)", whosays);
9893     }
9894
9895     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9896
9897     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9898         /* If we are playing on ICS, the server decides when the
9899            game is over, but the engine can offer to draw, claim
9900            a draw, or resign.
9901          */
9902 #if ZIPPY
9903         if (appData.zippyPlay && first.initDone) {
9904             if (result == GameIsDrawn) {
9905                 /* In case draw still needs to be claimed */
9906                 SendToICS(ics_prefix);
9907                 SendToICS("draw\n");
9908             } else if (StrCaseStr(resultDetails, "resign")) {
9909                 SendToICS(ics_prefix);
9910                 SendToICS("resign\n");
9911             }
9912         }
9913 #endif
9914         endingGame = 0; /* [HGM] crash */
9915         return;
9916     }
9917
9918     /* If we're loading the game from a file, stop */
9919     if (whosays == GE_FILE) {
9920       (void) StopLoadGameTimer();
9921       gameFileFP = NULL;
9922     }
9923
9924     /* Cancel draw offers */
9925     first.offeredDraw = second.offeredDraw = 0;
9926
9927     /* If this is an ICS game, only ICS can really say it's done;
9928        if not, anyone can. */
9929     isIcsGame = (gameMode == IcsPlayingWhite ||
9930                  gameMode == IcsPlayingBlack ||
9931                  gameMode == IcsObserving    ||
9932                  gameMode == IcsExamining);
9933
9934     if (!isIcsGame || whosays == GE_ICS) {
9935         /* OK -- not an ICS game, or ICS said it was done */
9936         StopClocks();
9937         if (!isIcsGame && !appData.noChessProgram)
9938           SetUserThinkingEnables();
9939
9940         /* [HGM] if a machine claims the game end we verify this claim */
9941         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9942             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9943                 char claimer;
9944                 ChessMove trueResult = (ChessMove) -1;
9945
9946                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9947                                             first.twoMachinesColor[0] :
9948                                             second.twoMachinesColor[0] ;
9949
9950                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9951                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9952                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9953                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9954                 } else
9955                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9956                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9957                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9958                 } else
9959                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9960                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9961                 }
9962
9963                 // now verify win claims, but not in drop games, as we don't understand those yet
9964                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9965                                                  || gameInfo.variant == VariantGreat) &&
9966                     (result == WhiteWins && claimer == 'w' ||
9967                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9968                       if (appData.debugMode) {
9969                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9970                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9971                       }
9972                       if(result != trueResult) {
9973                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9974                               result = claimer == 'w' ? BlackWins : WhiteWins;
9975                               resultDetails = buf;
9976                       }
9977                 } else
9978                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9979                     && (forwardMostMove <= backwardMostMove ||
9980                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9981                         (claimer=='b')==(forwardMostMove&1))
9982                                                                                   ) {
9983                       /* [HGM] verify: draws that were not flagged are false claims */
9984                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9985                       result = claimer == 'w' ? BlackWins : WhiteWins;
9986                       resultDetails = buf;
9987                 }
9988                 /* (Claiming a loss is accepted no questions asked!) */
9989             }
9990             /* [HGM] bare: don't allow bare King to win */
9991             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9992                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9993                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9994                && result != GameIsDrawn)
9995             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9996                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9997                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9998                         if(p >= 0 && p <= (int)WhiteKing) k++;
9999                 }
10000                 if (appData.debugMode) {
10001                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10002                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10003                 }
10004                 if(k <= 1) {
10005                         result = GameIsDrawn;
10006                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10007                         resultDetails = buf;
10008                 }
10009             }
10010         }
10011
10012
10013         if(serverMoves != NULL && !loadFlag) { char c = '=';
10014             if(result==WhiteWins) c = '+';
10015             if(result==BlackWins) c = '-';
10016             if(resultDetails != NULL)
10017                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10018         }
10019         if (resultDetails != NULL) {
10020             gameInfo.result = result;
10021             gameInfo.resultDetails = StrSave(resultDetails);
10022
10023             /* display last move only if game was not loaded from file */
10024             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10025                 DisplayMove(currentMove - 1);
10026
10027             if (forwardMostMove != 0) {
10028                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10029                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10030                                                                 ) {
10031                     if (*appData.saveGameFile != NULLCHAR) {
10032                         SaveGameToFile(appData.saveGameFile, TRUE);
10033                     } else if (appData.autoSaveGames) {
10034                         AutoSaveGame();
10035                     }
10036                     if (*appData.savePositionFile != NULLCHAR) {
10037                         SavePositionToFile(appData.savePositionFile);
10038                     }
10039                 }
10040             }
10041
10042             /* Tell program how game ended in case it is learning */
10043             /* [HGM] Moved this to after saving the PGN, just in case */
10044             /* engine died and we got here through time loss. In that */
10045             /* case we will get a fatal error writing the pipe, which */
10046             /* would otherwise lose us the PGN.                       */
10047             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10048             /* output during GameEnds should never be fatal anymore   */
10049             if (gameMode == MachinePlaysWhite ||
10050                 gameMode == MachinePlaysBlack ||
10051                 gameMode == TwoMachinesPlay ||
10052                 gameMode == IcsPlayingWhite ||
10053                 gameMode == IcsPlayingBlack ||
10054                 gameMode == BeginningOfGame) {
10055                 char buf[MSG_SIZ];
10056                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10057                         resultDetails);
10058                 if (first.pr != NoProc) {
10059                     SendToProgram(buf, &first);
10060                 }
10061                 if (second.pr != NoProc &&
10062                     gameMode == TwoMachinesPlay) {
10063                     SendToProgram(buf, &second);
10064                 }
10065             }
10066         }
10067
10068         if (appData.icsActive) {
10069             if (appData.quietPlay &&
10070                 (gameMode == IcsPlayingWhite ||
10071                  gameMode == IcsPlayingBlack)) {
10072                 SendToICS(ics_prefix);
10073                 SendToICS("set shout 1\n");
10074             }
10075             nextGameMode = IcsIdle;
10076             ics_user_moved = FALSE;
10077             /* clean up premove.  It's ugly when the game has ended and the
10078              * premove highlights are still on the board.
10079              */
10080             if (gotPremove) {
10081               gotPremove = FALSE;
10082               ClearPremoveHighlights();
10083               DrawPosition(FALSE, boards[currentMove]);
10084             }
10085             if (whosays == GE_ICS) {
10086                 switch (result) {
10087                 case WhiteWins:
10088                     if (gameMode == IcsPlayingWhite)
10089                         PlayIcsWinSound();
10090                     else if(gameMode == IcsPlayingBlack)
10091                         PlayIcsLossSound();
10092                     break;
10093                 case BlackWins:
10094                     if (gameMode == IcsPlayingBlack)
10095                         PlayIcsWinSound();
10096                     else if(gameMode == IcsPlayingWhite)
10097                         PlayIcsLossSound();
10098                     break;
10099                 case GameIsDrawn:
10100                     PlayIcsDrawSound();
10101                     break;
10102                 default:
10103                     PlayIcsUnfinishedSound();
10104                 }
10105             }
10106         } else if (gameMode == EditGame ||
10107                    gameMode == PlayFromGameFile ||
10108                    gameMode == AnalyzeMode ||
10109                    gameMode == AnalyzeFile) {
10110             nextGameMode = gameMode;
10111         } else {
10112             nextGameMode = EndOfGame;
10113         }
10114         pausing = FALSE;
10115         ModeHighlight();
10116     } else {
10117         nextGameMode = gameMode;
10118     }
10119
10120     if (appData.noChessProgram) {
10121         gameMode = nextGameMode;
10122         ModeHighlight();
10123         endingGame = 0; /* [HGM] crash */
10124         return;
10125     }
10126
10127     if (first.reuse) {
10128         /* Put first chess program into idle state */
10129         if (first.pr != NoProc &&
10130             (gameMode == MachinePlaysWhite ||
10131              gameMode == MachinePlaysBlack ||
10132              gameMode == TwoMachinesPlay ||
10133              gameMode == IcsPlayingWhite ||
10134              gameMode == IcsPlayingBlack ||
10135              gameMode == BeginningOfGame)) {
10136             SendToProgram("force\n", &first);
10137             if (first.usePing) {
10138               char buf[MSG_SIZ];
10139               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10140               SendToProgram(buf, &first);
10141             }
10142         }
10143     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10144         /* Kill off first chess program */
10145         if (first.isr != NULL)
10146           RemoveInputSource(first.isr);
10147         first.isr = NULL;
10148
10149         if (first.pr != NoProc) {
10150             ExitAnalyzeMode();
10151             DoSleep( appData.delayBeforeQuit );
10152             SendToProgram("quit\n", &first);
10153             DoSleep( appData.delayAfterQuit );
10154             DestroyChildProcess(first.pr, first.useSigterm);
10155         }
10156         first.pr = NoProc;
10157     }
10158     if (second.reuse) {
10159         /* Put second chess program into idle state */
10160         if (second.pr != NoProc &&
10161             gameMode == TwoMachinesPlay) {
10162             SendToProgram("force\n", &second);
10163             if (second.usePing) {
10164               char buf[MSG_SIZ];
10165               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10166               SendToProgram(buf, &second);
10167             }
10168         }
10169     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10170         /* Kill off second chess program */
10171         if (second.isr != NULL)
10172           RemoveInputSource(second.isr);
10173         second.isr = NULL;
10174
10175         if (second.pr != NoProc) {
10176             DoSleep( appData.delayBeforeQuit );
10177             SendToProgram("quit\n", &second);
10178             DoSleep( appData.delayAfterQuit );
10179             DestroyChildProcess(second.pr, second.useSigterm);
10180         }
10181         second.pr = NoProc;
10182     }
10183
10184     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10185         char resChar = '=';
10186         switch (result) {
10187         case WhiteWins:
10188           resChar = '+';
10189           if (first.twoMachinesColor[0] == 'w') {
10190             first.matchWins++;
10191           } else {
10192             second.matchWins++;
10193           }
10194           break;
10195         case BlackWins:
10196           resChar = '-';
10197           if (first.twoMachinesColor[0] == 'b') {
10198             first.matchWins++;
10199           } else {
10200             second.matchWins++;
10201           }
10202           break;
10203         case GameUnfinished:
10204           resChar = ' ';
10205         default:
10206           break;
10207         }
10208
10209         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10210         if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10211             ReserveGame(nextGame, resChar); // sets nextGame
10212             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10213         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10214
10215         if (nextGame <= appData.matchGames && !abortMatch) {
10216             gameMode = nextGameMode;
10217             matchGame = nextGame; // this will be overruled in tourney mode!
10218             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10219             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10220             endingGame = 0; /* [HGM] crash */
10221             return;
10222         } else {
10223             gameMode = nextGameMode;
10224             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10225                      first.tidy, second.tidy,
10226                      first.matchWins, second.matchWins,
10227                      appData.matchGames - (first.matchWins + second.matchWins));
10228             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10229             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10230                 first.twoMachinesColor = "black\n";
10231                 second.twoMachinesColor = "white\n";
10232             } else {
10233                 first.twoMachinesColor = "white\n";
10234                 second.twoMachinesColor = "black\n";
10235             }
10236         }
10237     }
10238     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10239         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10240       ExitAnalyzeMode();
10241     gameMode = nextGameMode;
10242     ModeHighlight();
10243     endingGame = 0;  /* [HGM] crash */
10244     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10245         if(matchMode == TRUE) { // match through command line: exit with or without popup
10246             if(ranking) {
10247                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10248                 else ExitEvent(0);
10249             } else DisplayFatalError(buf, 0, 0);
10250         } else { // match through menu; just stop, with or without popup
10251             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10252             if(ranking){
10253                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10254             } else DisplayNote(buf);
10255       }
10256       if(ranking) free(ranking);
10257     }
10258 }
10259
10260 /* Assumes program was just initialized (initString sent).
10261    Leaves program in force mode. */
10262 void
10263 FeedMovesToProgram(cps, upto)
10264      ChessProgramState *cps;
10265      int upto;
10266 {
10267     int i;
10268
10269     if (appData.debugMode)
10270       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10271               startedFromSetupPosition ? "position and " : "",
10272               backwardMostMove, upto, cps->which);
10273     if(currentlyInitializedVariant != gameInfo.variant) {
10274       char buf[MSG_SIZ];
10275         // [HGM] variantswitch: make engine aware of new variant
10276         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10277                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10278         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10279         SendToProgram(buf, cps);
10280         currentlyInitializedVariant = gameInfo.variant;
10281     }
10282     SendToProgram("force\n", cps);
10283     if (startedFromSetupPosition) {
10284         SendBoard(cps, backwardMostMove);
10285     if (appData.debugMode) {
10286         fprintf(debugFP, "feedMoves\n");
10287     }
10288     }
10289     for (i = backwardMostMove; i < upto; i++) {
10290         SendMoveToProgram(i, cps);
10291     }
10292 }
10293
10294
10295 int
10296 ResurrectChessProgram()
10297 {
10298      /* The chess program may have exited.
10299         If so, restart it and feed it all the moves made so far. */
10300     static int doInit = 0;
10301
10302     if (appData.noChessProgram) return 1;
10303
10304     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10305         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10306         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10307         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10308     } else {
10309         if (first.pr != NoProc) return 1;
10310         StartChessProgram(&first);
10311     }
10312     InitChessProgram(&first, FALSE);
10313     FeedMovesToProgram(&first, currentMove);
10314
10315     if (!first.sendTime) {
10316         /* can't tell gnuchess what its clock should read,
10317            so we bow to its notion. */
10318         ResetClocks();
10319         timeRemaining[0][currentMove] = whiteTimeRemaining;
10320         timeRemaining[1][currentMove] = blackTimeRemaining;
10321     }
10322
10323     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10324                 appData.icsEngineAnalyze) && first.analysisSupport) {
10325       SendToProgram("analyze\n", &first);
10326       first.analyzing = TRUE;
10327     }
10328     return 1;
10329 }
10330
10331 /*
10332  * Button procedures
10333  */
10334 void
10335 Reset(redraw, init)
10336      int redraw, init;
10337 {
10338     int i;
10339
10340     if (appData.debugMode) {
10341         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10342                 redraw, init, gameMode);
10343     }
10344     CleanupTail(); // [HGM] vari: delete any stored variations
10345     pausing = pauseExamInvalid = FALSE;
10346     startedFromSetupPosition = blackPlaysFirst = FALSE;
10347     firstMove = TRUE;
10348     whiteFlag = blackFlag = FALSE;
10349     userOfferedDraw = FALSE;
10350     hintRequested = bookRequested = FALSE;
10351     first.maybeThinking = FALSE;
10352     second.maybeThinking = FALSE;
10353     first.bookSuspend = FALSE; // [HGM] book
10354     second.bookSuspend = FALSE;
10355     thinkOutput[0] = NULLCHAR;
10356     lastHint[0] = NULLCHAR;
10357     ClearGameInfo(&gameInfo);
10358     gameInfo.variant = StringToVariant(appData.variant);
10359     ics_user_moved = ics_clock_paused = FALSE;
10360     ics_getting_history = H_FALSE;
10361     ics_gamenum = -1;
10362     white_holding[0] = black_holding[0] = NULLCHAR;
10363     ClearProgramStats();
10364     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10365
10366     ResetFrontEnd();
10367     ClearHighlights();
10368     flipView = appData.flipView;
10369     ClearPremoveHighlights();
10370     gotPremove = FALSE;
10371     alarmSounded = FALSE;
10372
10373     GameEnds(EndOfFile, NULL, GE_PLAYER);
10374     if(appData.serverMovesName != NULL) {
10375         /* [HGM] prepare to make moves file for broadcasting */
10376         clock_t t = clock();
10377         if(serverMoves != NULL) fclose(serverMoves);
10378         serverMoves = fopen(appData.serverMovesName, "r");
10379         if(serverMoves != NULL) {
10380             fclose(serverMoves);
10381             /* delay 15 sec before overwriting, so all clients can see end */
10382             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10383         }
10384         serverMoves = fopen(appData.serverMovesName, "w");
10385     }
10386
10387     ExitAnalyzeMode();
10388     gameMode = BeginningOfGame;
10389     ModeHighlight();
10390     if(appData.icsActive) gameInfo.variant = VariantNormal;
10391     currentMove = forwardMostMove = backwardMostMove = 0;
10392     InitPosition(redraw);
10393     for (i = 0; i < MAX_MOVES; i++) {
10394         if (commentList[i] != NULL) {
10395             free(commentList[i]);
10396             commentList[i] = NULL;
10397         }
10398     }
10399     ResetClocks();
10400     timeRemaining[0][0] = whiteTimeRemaining;
10401     timeRemaining[1][0] = blackTimeRemaining;
10402
10403     if (first.pr == NULL) {
10404         StartChessProgram(&first);
10405     }
10406     if (init) {
10407             InitChessProgram(&first, startedFromSetupPosition);
10408     }
10409     DisplayTitle("");
10410     DisplayMessage("", "");
10411     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10412     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10413 }
10414
10415 void
10416 AutoPlayGameLoop()
10417 {
10418     for (;;) {
10419         if (!AutoPlayOneMove())
10420           return;
10421         if (matchMode || appData.timeDelay == 0)
10422           continue;
10423         if (appData.timeDelay < 0)
10424           return;
10425         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10426         break;
10427     }
10428 }
10429
10430
10431 int
10432 AutoPlayOneMove()
10433 {
10434     int fromX, fromY, toX, toY;
10435
10436     if (appData.debugMode) {
10437       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10438     }
10439
10440     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10441       return FALSE;
10442
10443     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10444       pvInfoList[currentMove].depth = programStats.depth;
10445       pvInfoList[currentMove].score = programStats.score;
10446       pvInfoList[currentMove].time  = 0;
10447       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10448     }
10449
10450     if (currentMove >= forwardMostMove) {
10451       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10452       gameMode = EditGame;
10453       ModeHighlight();
10454
10455       /* [AS] Clear current move marker at the end of a game */
10456       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10457
10458       return FALSE;
10459     }
10460
10461     toX = moveList[currentMove][2] - AAA;
10462     toY = moveList[currentMove][3] - ONE;
10463
10464     if (moveList[currentMove][1] == '@') {
10465         if (appData.highlightLastMove) {
10466             SetHighlights(-1, -1, toX, toY);
10467         }
10468     } else {
10469         fromX = moveList[currentMove][0] - AAA;
10470         fromY = moveList[currentMove][1] - ONE;
10471
10472         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10473
10474         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10475
10476         if (appData.highlightLastMove) {
10477             SetHighlights(fromX, fromY, toX, toY);
10478         }
10479     }
10480     DisplayMove(currentMove);
10481     SendMoveToProgram(currentMove++, &first);
10482     DisplayBothClocks();
10483     DrawPosition(FALSE, boards[currentMove]);
10484     // [HGM] PV info: always display, routine tests if empty
10485     DisplayComment(currentMove - 1, commentList[currentMove]);
10486     return TRUE;
10487 }
10488
10489
10490 int
10491 LoadGameOneMove(readAhead)
10492      ChessMove readAhead;
10493 {
10494     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10495     char promoChar = NULLCHAR;
10496     ChessMove moveType;
10497     char move[MSG_SIZ];
10498     char *p, *q;
10499
10500     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10501         gameMode != AnalyzeMode && gameMode != Training) {
10502         gameFileFP = NULL;
10503         return FALSE;
10504     }
10505
10506     yyboardindex = forwardMostMove;
10507     if (readAhead != EndOfFile) {
10508       moveType = readAhead;
10509     } else {
10510       if (gameFileFP == NULL)
10511           return FALSE;
10512       moveType = (ChessMove) Myylex();
10513     }
10514
10515     done = FALSE;
10516     switch (moveType) {
10517       case Comment:
10518         if (appData.debugMode)
10519           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10520         p = yy_text;
10521
10522         /* append the comment but don't display it */
10523         AppendComment(currentMove, p, FALSE);
10524         return TRUE;
10525
10526       case WhiteCapturesEnPassant:
10527       case BlackCapturesEnPassant:
10528       case WhitePromotion:
10529       case BlackPromotion:
10530       case WhiteNonPromotion:
10531       case BlackNonPromotion:
10532       case NormalMove:
10533       case WhiteKingSideCastle:
10534       case WhiteQueenSideCastle:
10535       case BlackKingSideCastle:
10536       case BlackQueenSideCastle:
10537       case WhiteKingSideCastleWild:
10538       case WhiteQueenSideCastleWild:
10539       case BlackKingSideCastleWild:
10540       case BlackQueenSideCastleWild:
10541       /* PUSH Fabien */
10542       case WhiteHSideCastleFR:
10543       case WhiteASideCastleFR:
10544       case BlackHSideCastleFR:
10545       case BlackASideCastleFR:
10546       /* POP Fabien */
10547         if (appData.debugMode)
10548           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10549         fromX = currentMoveString[0] - AAA;
10550         fromY = currentMoveString[1] - ONE;
10551         toX = currentMoveString[2] - AAA;
10552         toY = currentMoveString[3] - ONE;
10553         promoChar = currentMoveString[4];
10554         break;
10555
10556       case WhiteDrop:
10557       case BlackDrop:
10558         if (appData.debugMode)
10559           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10560         fromX = moveType == WhiteDrop ?
10561           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10562         (int) CharToPiece(ToLower(currentMoveString[0]));
10563         fromY = DROP_RANK;
10564         toX = currentMoveString[2] - AAA;
10565         toY = currentMoveString[3] - ONE;
10566         break;
10567
10568       case WhiteWins:
10569       case BlackWins:
10570       case GameIsDrawn:
10571       case GameUnfinished:
10572         if (appData.debugMode)
10573           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10574         p = strchr(yy_text, '{');
10575         if (p == NULL) p = strchr(yy_text, '(');
10576         if (p == NULL) {
10577             p = yy_text;
10578             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10579         } else {
10580             q = strchr(p, *p == '{' ? '}' : ')');
10581             if (q != NULL) *q = NULLCHAR;
10582             p++;
10583         }
10584         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10585         GameEnds(moveType, p, GE_FILE);
10586         done = TRUE;
10587         if (cmailMsgLoaded) {
10588             ClearHighlights();
10589             flipView = WhiteOnMove(currentMove);
10590             if (moveType == GameUnfinished) flipView = !flipView;
10591             if (appData.debugMode)
10592               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10593         }
10594         break;
10595
10596       case EndOfFile:
10597         if (appData.debugMode)
10598           fprintf(debugFP, "Parser hit end of file\n");
10599         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10600           case MT_NONE:
10601           case MT_CHECK:
10602             break;
10603           case MT_CHECKMATE:
10604           case MT_STAINMATE:
10605             if (WhiteOnMove(currentMove)) {
10606                 GameEnds(BlackWins, "Black mates", GE_FILE);
10607             } else {
10608                 GameEnds(WhiteWins, "White mates", GE_FILE);
10609             }
10610             break;
10611           case MT_STALEMATE:
10612             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10613             break;
10614         }
10615         done = TRUE;
10616         break;
10617
10618       case MoveNumberOne:
10619         if (lastLoadGameStart == GNUChessGame) {
10620             /* GNUChessGames have numbers, but they aren't move numbers */
10621             if (appData.debugMode)
10622               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10623                       yy_text, (int) moveType);
10624             return LoadGameOneMove(EndOfFile); /* tail recursion */
10625         }
10626         /* else fall thru */
10627
10628       case XBoardGame:
10629       case GNUChessGame:
10630       case PGNTag:
10631         /* Reached start of next game in file */
10632         if (appData.debugMode)
10633           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10634         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10635           case MT_NONE:
10636           case MT_CHECK:
10637             break;
10638           case MT_CHECKMATE:
10639           case MT_STAINMATE:
10640             if (WhiteOnMove(currentMove)) {
10641                 GameEnds(BlackWins, "Black mates", GE_FILE);
10642             } else {
10643                 GameEnds(WhiteWins, "White mates", GE_FILE);
10644             }
10645             break;
10646           case MT_STALEMATE:
10647             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10648             break;
10649         }
10650         done = TRUE;
10651         break;
10652
10653       case PositionDiagram:     /* should not happen; ignore */
10654       case ElapsedTime:         /* ignore */
10655       case NAG:                 /* ignore */
10656         if (appData.debugMode)
10657           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10658                   yy_text, (int) moveType);
10659         return LoadGameOneMove(EndOfFile); /* tail recursion */
10660
10661       case IllegalMove:
10662         if (appData.testLegality) {
10663             if (appData.debugMode)
10664               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10665             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10666                     (forwardMostMove / 2) + 1,
10667                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10668             DisplayError(move, 0);
10669             done = TRUE;
10670         } else {
10671             if (appData.debugMode)
10672               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10673                       yy_text, currentMoveString);
10674             fromX = currentMoveString[0] - AAA;
10675             fromY = currentMoveString[1] - ONE;
10676             toX = currentMoveString[2] - AAA;
10677             toY = currentMoveString[3] - ONE;
10678             promoChar = currentMoveString[4];
10679         }
10680         break;
10681
10682       case AmbiguousMove:
10683         if (appData.debugMode)
10684           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10685         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10686                 (forwardMostMove / 2) + 1,
10687                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10688         DisplayError(move, 0);
10689         done = TRUE;
10690         break;
10691
10692       default:
10693       case ImpossibleMove:
10694         if (appData.debugMode)
10695           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10696         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10697                 (forwardMostMove / 2) + 1,
10698                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10699         DisplayError(move, 0);
10700         done = TRUE;
10701         break;
10702     }
10703
10704     if (done) {
10705         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10706             DrawPosition(FALSE, boards[currentMove]);
10707             DisplayBothClocks();
10708             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10709               DisplayComment(currentMove - 1, commentList[currentMove]);
10710         }
10711         (void) StopLoadGameTimer();
10712         gameFileFP = NULL;
10713         cmailOldMove = forwardMostMove;
10714         return FALSE;
10715     } else {
10716         /* currentMoveString is set as a side-effect of yylex */
10717
10718         thinkOutput[0] = NULLCHAR;
10719         MakeMove(fromX, fromY, toX, toY, promoChar);
10720         currentMove = forwardMostMove;
10721         return TRUE;
10722     }
10723 }
10724
10725 /* Load the nth game from the given file */
10726 int
10727 LoadGameFromFile(filename, n, title, useList)
10728      char *filename;
10729      int n;
10730      char *title;
10731      /*Boolean*/ int useList;
10732 {
10733     FILE *f;
10734     char buf[MSG_SIZ];
10735
10736     if (strcmp(filename, "-") == 0) {
10737         f = stdin;
10738         title = "stdin";
10739     } else {
10740         f = fopen(filename, "rb");
10741         if (f == NULL) {
10742           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10743             DisplayError(buf, errno);
10744             return FALSE;
10745         }
10746     }
10747     if (fseek(f, 0, 0) == -1) {
10748         /* f is not seekable; probably a pipe */
10749         useList = FALSE;
10750     }
10751     if (useList && n == 0) {
10752         int error = GameListBuild(f);
10753         if (error) {
10754             DisplayError(_("Cannot build game list"), error);
10755         } else if (!ListEmpty(&gameList) &&
10756                    ((ListGame *) gameList.tailPred)->number > 1) {
10757             GameListPopUp(f, title);
10758             return TRUE;
10759         }
10760         GameListDestroy();
10761         n = 1;
10762     }
10763     if (n == 0) n = 1;
10764     return LoadGame(f, n, title, FALSE);
10765 }
10766
10767
10768 void
10769 MakeRegisteredMove()
10770 {
10771     int fromX, fromY, toX, toY;
10772     char promoChar;
10773     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10774         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10775           case CMAIL_MOVE:
10776           case CMAIL_DRAW:
10777             if (appData.debugMode)
10778               fprintf(debugFP, "Restoring %s for game %d\n",
10779                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10780
10781             thinkOutput[0] = NULLCHAR;
10782             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10783             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10784             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10785             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10786             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10787             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10788             MakeMove(fromX, fromY, toX, toY, promoChar);
10789             ShowMove(fromX, fromY, toX, toY);
10790
10791             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10792               case MT_NONE:
10793               case MT_CHECK:
10794                 break;
10795
10796               case MT_CHECKMATE:
10797               case MT_STAINMATE:
10798                 if (WhiteOnMove(currentMove)) {
10799                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10800                 } else {
10801                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10802                 }
10803                 break;
10804
10805               case MT_STALEMATE:
10806                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10807                 break;
10808             }
10809
10810             break;
10811
10812           case CMAIL_RESIGN:
10813             if (WhiteOnMove(currentMove)) {
10814                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10815             } else {
10816                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10817             }
10818             break;
10819
10820           case CMAIL_ACCEPT:
10821             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10822             break;
10823
10824           default:
10825             break;
10826         }
10827     }
10828
10829     return;
10830 }
10831
10832 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10833 int
10834 CmailLoadGame(f, gameNumber, title, useList)
10835      FILE *f;
10836      int gameNumber;
10837      char *title;
10838      int useList;
10839 {
10840     int retVal;
10841
10842     if (gameNumber > nCmailGames) {
10843         DisplayError(_("No more games in this message"), 0);
10844         return FALSE;
10845     }
10846     if (f == lastLoadGameFP) {
10847         int offset = gameNumber - lastLoadGameNumber;
10848         if (offset == 0) {
10849             cmailMsg[0] = NULLCHAR;
10850             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10851                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10852                 nCmailMovesRegistered--;
10853             }
10854             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10855             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10856                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10857             }
10858         } else {
10859             if (! RegisterMove()) return FALSE;
10860         }
10861     }
10862
10863     retVal = LoadGame(f, gameNumber, title, useList);
10864
10865     /* Make move registered during previous look at this game, if any */
10866     MakeRegisteredMove();
10867
10868     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10869         commentList[currentMove]
10870           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10871         DisplayComment(currentMove - 1, commentList[currentMove]);
10872     }
10873
10874     return retVal;
10875 }
10876
10877 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10878 int
10879 ReloadGame(offset)
10880      int offset;
10881 {
10882     int gameNumber = lastLoadGameNumber + offset;
10883     if (lastLoadGameFP == NULL) {
10884         DisplayError(_("No game has been loaded yet"), 0);
10885         return FALSE;
10886     }
10887     if (gameNumber <= 0) {
10888         DisplayError(_("Can't back up any further"), 0);
10889         return FALSE;
10890     }
10891     if (cmailMsgLoaded) {
10892         return CmailLoadGame(lastLoadGameFP, gameNumber,
10893                              lastLoadGameTitle, lastLoadGameUseList);
10894     } else {
10895         return LoadGame(lastLoadGameFP, gameNumber,
10896                         lastLoadGameTitle, lastLoadGameUseList);
10897     }
10898 }
10899
10900
10901
10902 /* Load the nth game from open file f */
10903 int
10904 LoadGame(f, gameNumber, title, useList)
10905      FILE *f;
10906      int gameNumber;
10907      char *title;
10908      int useList;
10909 {
10910     ChessMove cm;
10911     char buf[MSG_SIZ];
10912     int gn = gameNumber;
10913     ListGame *lg = NULL;
10914     int numPGNTags = 0;
10915     int err;
10916     GameMode oldGameMode;
10917     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10918
10919     if (appData.debugMode)
10920         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10921
10922     if (gameMode == Training )
10923         SetTrainingModeOff();
10924
10925     oldGameMode = gameMode;
10926     if (gameMode != BeginningOfGame) {
10927       Reset(FALSE, TRUE);
10928     }
10929
10930     gameFileFP = f;
10931     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10932         fclose(lastLoadGameFP);
10933     }
10934
10935     if (useList) {
10936         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10937
10938         if (lg) {
10939             fseek(f, lg->offset, 0);
10940             GameListHighlight(gameNumber);
10941             gn = 1;
10942         }
10943         else {
10944             DisplayError(_("Game number out of range"), 0);
10945             return FALSE;
10946         }
10947     } else {
10948         GameListDestroy();
10949         if (fseek(f, 0, 0) == -1) {
10950             if (f == lastLoadGameFP ?
10951                 gameNumber == lastLoadGameNumber + 1 :
10952                 gameNumber == 1) {
10953                 gn = 1;
10954             } else {
10955                 DisplayError(_("Can't seek on game file"), 0);
10956                 return FALSE;
10957             }
10958         }
10959     }
10960     lastLoadGameFP = f;
10961     lastLoadGameNumber = gameNumber;
10962     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10963     lastLoadGameUseList = useList;
10964
10965     yynewfile(f);
10966
10967     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10968       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10969                 lg->gameInfo.black);
10970             DisplayTitle(buf);
10971     } else if (*title != NULLCHAR) {
10972         if (gameNumber > 1) {
10973           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10974             DisplayTitle(buf);
10975         } else {
10976             DisplayTitle(title);
10977         }
10978     }
10979
10980     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10981         gameMode = PlayFromGameFile;
10982         ModeHighlight();
10983     }
10984
10985     currentMove = forwardMostMove = backwardMostMove = 0;
10986     CopyBoard(boards[0], initialPosition);
10987     StopClocks();
10988
10989     /*
10990      * Skip the first gn-1 games in the file.
10991      * Also skip over anything that precedes an identifiable
10992      * start of game marker, to avoid being confused by
10993      * garbage at the start of the file.  Currently
10994      * recognized start of game markers are the move number "1",
10995      * the pattern "gnuchess .* game", the pattern
10996      * "^[#;%] [^ ]* game file", and a PGN tag block.
10997      * A game that starts with one of the latter two patterns
10998      * will also have a move number 1, possibly
10999      * following a position diagram.
11000      * 5-4-02: Let's try being more lenient and allowing a game to
11001      * start with an unnumbered move.  Does that break anything?
11002      */
11003     cm = lastLoadGameStart = EndOfFile;
11004     while (gn > 0) {
11005         yyboardindex = forwardMostMove;
11006         cm = (ChessMove) Myylex();
11007         switch (cm) {
11008           case EndOfFile:
11009             if (cmailMsgLoaded) {
11010                 nCmailGames = CMAIL_MAX_GAMES - gn;
11011             } else {
11012                 Reset(TRUE, TRUE);
11013                 DisplayError(_("Game not found in file"), 0);
11014             }
11015             return FALSE;
11016
11017           case GNUChessGame:
11018           case XBoardGame:
11019             gn--;
11020             lastLoadGameStart = cm;
11021             break;
11022
11023           case MoveNumberOne:
11024             switch (lastLoadGameStart) {
11025               case GNUChessGame:
11026               case XBoardGame:
11027               case PGNTag:
11028                 break;
11029               case MoveNumberOne:
11030               case EndOfFile:
11031                 gn--;           /* count this game */
11032                 lastLoadGameStart = cm;
11033                 break;
11034               default:
11035                 /* impossible */
11036                 break;
11037             }
11038             break;
11039
11040           case PGNTag:
11041             switch (lastLoadGameStart) {
11042               case GNUChessGame:
11043               case PGNTag:
11044               case MoveNumberOne:
11045               case EndOfFile:
11046                 gn--;           /* count this game */
11047                 lastLoadGameStart = cm;
11048                 break;
11049               case XBoardGame:
11050                 lastLoadGameStart = cm; /* game counted already */
11051                 break;
11052               default:
11053                 /* impossible */
11054                 break;
11055             }
11056             if (gn > 0) {
11057                 do {
11058                     yyboardindex = forwardMostMove;
11059                     cm = (ChessMove) Myylex();
11060                 } while (cm == PGNTag || cm == Comment);
11061             }
11062             break;
11063
11064           case WhiteWins:
11065           case BlackWins:
11066           case GameIsDrawn:
11067             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11068                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11069                     != CMAIL_OLD_RESULT) {
11070                     nCmailResults ++ ;
11071                     cmailResult[  CMAIL_MAX_GAMES
11072                                 - gn - 1] = CMAIL_OLD_RESULT;
11073                 }
11074             }
11075             break;
11076
11077           case NormalMove:
11078             /* Only a NormalMove can be at the start of a game
11079              * without a position diagram. */
11080             if (lastLoadGameStart == EndOfFile ) {
11081               gn--;
11082               lastLoadGameStart = MoveNumberOne;
11083             }
11084             break;
11085
11086           default:
11087             break;
11088         }
11089     }
11090
11091     if (appData.debugMode)
11092       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11093
11094     if (cm == XBoardGame) {
11095         /* Skip any header junk before position diagram and/or move 1 */
11096         for (;;) {
11097             yyboardindex = forwardMostMove;
11098             cm = (ChessMove) Myylex();
11099
11100             if (cm == EndOfFile ||
11101                 cm == GNUChessGame || cm == XBoardGame) {
11102                 /* Empty game; pretend end-of-file and handle later */
11103                 cm = EndOfFile;
11104                 break;
11105             }
11106
11107             if (cm == MoveNumberOne || cm == PositionDiagram ||
11108                 cm == PGNTag || cm == Comment)
11109               break;
11110         }
11111     } else if (cm == GNUChessGame) {
11112         if (gameInfo.event != NULL) {
11113             free(gameInfo.event);
11114         }
11115         gameInfo.event = StrSave(yy_text);
11116     }
11117
11118     startedFromSetupPosition = FALSE;
11119     while (cm == PGNTag) {
11120         if (appData.debugMode)
11121           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11122         err = ParsePGNTag(yy_text, &gameInfo);
11123         if (!err) numPGNTags++;
11124
11125         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11126         if(gameInfo.variant != oldVariant) {
11127             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11128             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11129             InitPosition(TRUE);
11130             oldVariant = gameInfo.variant;
11131             if (appData.debugMode)
11132               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11133         }
11134
11135
11136         if (gameInfo.fen != NULL) {
11137           Board initial_position;
11138           startedFromSetupPosition = TRUE;
11139           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11140             Reset(TRUE, TRUE);
11141             DisplayError(_("Bad FEN position in file"), 0);
11142             return FALSE;
11143           }
11144           CopyBoard(boards[0], initial_position);
11145           if (blackPlaysFirst) {
11146             currentMove = forwardMostMove = backwardMostMove = 1;
11147             CopyBoard(boards[1], initial_position);
11148             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11149             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11150             timeRemaining[0][1] = whiteTimeRemaining;
11151             timeRemaining[1][1] = blackTimeRemaining;
11152             if (commentList[0] != NULL) {
11153               commentList[1] = commentList[0];
11154               commentList[0] = NULL;
11155             }
11156           } else {
11157             currentMove = forwardMostMove = backwardMostMove = 0;
11158           }
11159           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11160           {   int i;
11161               initialRulePlies = FENrulePlies;
11162               for( i=0; i< nrCastlingRights; i++ )
11163                   initialRights[i] = initial_position[CASTLING][i];
11164           }
11165           yyboardindex = forwardMostMove;
11166           free(gameInfo.fen);
11167           gameInfo.fen = NULL;
11168         }
11169
11170         yyboardindex = forwardMostMove;
11171         cm = (ChessMove) Myylex();
11172
11173         /* Handle comments interspersed among the tags */
11174         while (cm == Comment) {
11175             char *p;
11176             if (appData.debugMode)
11177               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11178             p = yy_text;
11179             AppendComment(currentMove, p, FALSE);
11180             yyboardindex = forwardMostMove;
11181             cm = (ChessMove) Myylex();
11182         }
11183     }
11184
11185     /* don't rely on existence of Event tag since if game was
11186      * pasted from clipboard the Event tag may not exist
11187      */
11188     if (numPGNTags > 0){
11189         char *tags;
11190         if (gameInfo.variant == VariantNormal) {
11191           VariantClass v = StringToVariant(gameInfo.event);
11192           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11193           if(v < VariantShogi) gameInfo.variant = v;
11194         }
11195         if (!matchMode) {
11196           if( appData.autoDisplayTags ) {
11197             tags = PGNTags(&gameInfo);
11198             TagsPopUp(tags, CmailMsg());
11199             free(tags);
11200           }
11201         }
11202     } else {
11203         /* Make something up, but don't display it now */
11204         SetGameInfo();
11205         TagsPopDown();
11206     }
11207
11208     if (cm == PositionDiagram) {
11209         int i, j;
11210         char *p;
11211         Board initial_position;
11212
11213         if (appData.debugMode)
11214           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11215
11216         if (!startedFromSetupPosition) {
11217             p = yy_text;
11218             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11219               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11220                 switch (*p) {
11221                   case '{':
11222                   case '[':
11223                   case '-':
11224                   case ' ':
11225                   case '\t':
11226                   case '\n':
11227                   case '\r':
11228                     break;
11229                   default:
11230                     initial_position[i][j++] = CharToPiece(*p);
11231                     break;
11232                 }
11233             while (*p == ' ' || *p == '\t' ||
11234                    *p == '\n' || *p == '\r') p++;
11235
11236             if (strncmp(p, "black", strlen("black"))==0)
11237               blackPlaysFirst = TRUE;
11238             else
11239               blackPlaysFirst = FALSE;
11240             startedFromSetupPosition = TRUE;
11241
11242             CopyBoard(boards[0], initial_position);
11243             if (blackPlaysFirst) {
11244                 currentMove = forwardMostMove = backwardMostMove = 1;
11245                 CopyBoard(boards[1], initial_position);
11246                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11247                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11248                 timeRemaining[0][1] = whiteTimeRemaining;
11249                 timeRemaining[1][1] = blackTimeRemaining;
11250                 if (commentList[0] != NULL) {
11251                     commentList[1] = commentList[0];
11252                     commentList[0] = NULL;
11253                 }
11254             } else {
11255                 currentMove = forwardMostMove = backwardMostMove = 0;
11256             }
11257         }
11258         yyboardindex = forwardMostMove;
11259         cm = (ChessMove) Myylex();
11260     }
11261
11262     if (first.pr == NoProc) {
11263         StartChessProgram(&first);
11264     }
11265     InitChessProgram(&first, FALSE);
11266     SendToProgram("force\n", &first);
11267     if (startedFromSetupPosition) {
11268         SendBoard(&first, forwardMostMove);
11269     if (appData.debugMode) {
11270         fprintf(debugFP, "Load Game\n");
11271     }
11272         DisplayBothClocks();
11273     }
11274
11275     /* [HGM] server: flag to write setup moves in broadcast file as one */
11276     loadFlag = appData.suppressLoadMoves;
11277
11278     while (cm == Comment) {
11279         char *p;
11280         if (appData.debugMode)
11281           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11282         p = yy_text;
11283         AppendComment(currentMove, p, FALSE);
11284         yyboardindex = forwardMostMove;
11285         cm = (ChessMove) Myylex();
11286     }
11287
11288     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11289         cm == WhiteWins || cm == BlackWins ||
11290         cm == GameIsDrawn || cm == GameUnfinished) {
11291         DisplayMessage("", _("No moves in game"));
11292         if (cmailMsgLoaded) {
11293             if (appData.debugMode)
11294               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11295             ClearHighlights();
11296             flipView = FALSE;
11297         }
11298         DrawPosition(FALSE, boards[currentMove]);
11299         DisplayBothClocks();
11300         gameMode = EditGame;
11301         ModeHighlight();
11302         gameFileFP = NULL;
11303         cmailOldMove = 0;
11304         return TRUE;
11305     }
11306
11307     // [HGM] PV info: routine tests if comment empty
11308     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11309         DisplayComment(currentMove - 1, commentList[currentMove]);
11310     }
11311     if (!matchMode && appData.timeDelay != 0)
11312       DrawPosition(FALSE, boards[currentMove]);
11313
11314     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11315       programStats.ok_to_send = 1;
11316     }
11317
11318     /* if the first token after the PGN tags is a move
11319      * and not move number 1, retrieve it from the parser
11320      */
11321     if (cm != MoveNumberOne)
11322         LoadGameOneMove(cm);
11323
11324     /* load the remaining moves from the file */
11325     while (LoadGameOneMove(EndOfFile)) {
11326       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11327       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11328     }
11329
11330     /* rewind to the start of the game */
11331     currentMove = backwardMostMove;
11332
11333     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11334
11335     if (oldGameMode == AnalyzeFile ||
11336         oldGameMode == AnalyzeMode) {
11337       AnalyzeFileEvent();
11338     }
11339
11340     if (matchMode || appData.timeDelay == 0) {
11341       ToEndEvent();
11342       gameMode = EditGame;
11343       ModeHighlight();
11344     } else if (appData.timeDelay > 0) {
11345       AutoPlayGameLoop();
11346     }
11347
11348     if (appData.debugMode)
11349         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11350
11351     loadFlag = 0; /* [HGM] true game starts */
11352     return TRUE;
11353 }
11354
11355 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11356 int
11357 ReloadPosition(offset)
11358      int offset;
11359 {
11360     int positionNumber = lastLoadPositionNumber + offset;
11361     if (lastLoadPositionFP == NULL) {
11362         DisplayError(_("No position has been loaded yet"), 0);
11363         return FALSE;
11364     }
11365     if (positionNumber <= 0) {
11366         DisplayError(_("Can't back up any further"), 0);
11367         return FALSE;
11368     }
11369     return LoadPosition(lastLoadPositionFP, positionNumber,
11370                         lastLoadPositionTitle);
11371 }
11372
11373 /* Load the nth position from the given file */
11374 int
11375 LoadPositionFromFile(filename, n, title)
11376      char *filename;
11377      int n;
11378      char *title;
11379 {
11380     FILE *f;
11381     char buf[MSG_SIZ];
11382
11383     if (strcmp(filename, "-") == 0) {
11384         return LoadPosition(stdin, n, "stdin");
11385     } else {
11386         f = fopen(filename, "rb");
11387         if (f == NULL) {
11388             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11389             DisplayError(buf, errno);
11390             return FALSE;
11391         } else {
11392             return LoadPosition(f, n, title);
11393         }
11394     }
11395 }
11396
11397 /* Load the nth position from the given open file, and close it */
11398 int
11399 LoadPosition(f, positionNumber, title)
11400      FILE *f;
11401      int positionNumber;
11402      char *title;
11403 {
11404     char *p, line[MSG_SIZ];
11405     Board initial_position;
11406     int i, j, fenMode, pn;
11407
11408     if (gameMode == Training )
11409         SetTrainingModeOff();
11410
11411     if (gameMode != BeginningOfGame) {
11412         Reset(FALSE, TRUE);
11413     }
11414     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11415         fclose(lastLoadPositionFP);
11416     }
11417     if (positionNumber == 0) positionNumber = 1;
11418     lastLoadPositionFP = f;
11419     lastLoadPositionNumber = positionNumber;
11420     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11421     if (first.pr == NoProc) {
11422       StartChessProgram(&first);
11423       InitChessProgram(&first, FALSE);
11424     }
11425     pn = positionNumber;
11426     if (positionNumber < 0) {
11427         /* Negative position number means to seek to that byte offset */
11428         if (fseek(f, -positionNumber, 0) == -1) {
11429             DisplayError(_("Can't seek on position file"), 0);
11430             return FALSE;
11431         };
11432         pn = 1;
11433     } else {
11434         if (fseek(f, 0, 0) == -1) {
11435             if (f == lastLoadPositionFP ?
11436                 positionNumber == lastLoadPositionNumber + 1 :
11437                 positionNumber == 1) {
11438                 pn = 1;
11439             } else {
11440                 DisplayError(_("Can't seek on position file"), 0);
11441                 return FALSE;
11442             }
11443         }
11444     }
11445     /* See if this file is FEN or old-style xboard */
11446     if (fgets(line, MSG_SIZ, f) == NULL) {
11447         DisplayError(_("Position not found in file"), 0);
11448         return FALSE;
11449     }
11450     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11451     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11452
11453     if (pn >= 2) {
11454         if (fenMode || line[0] == '#') pn--;
11455         while (pn > 0) {
11456             /* skip positions before number pn */
11457             if (fgets(line, MSG_SIZ, f) == NULL) {
11458                 Reset(TRUE, TRUE);
11459                 DisplayError(_("Position not found in file"), 0);
11460                 return FALSE;
11461             }
11462             if (fenMode || line[0] == '#') pn--;
11463         }
11464     }
11465
11466     if (fenMode) {
11467         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11468             DisplayError(_("Bad FEN position in file"), 0);
11469             return FALSE;
11470         }
11471     } else {
11472         (void) fgets(line, MSG_SIZ, f);
11473         (void) fgets(line, MSG_SIZ, f);
11474
11475         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11476             (void) fgets(line, MSG_SIZ, f);
11477             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11478                 if (*p == ' ')
11479                   continue;
11480                 initial_position[i][j++] = CharToPiece(*p);
11481             }
11482         }
11483
11484         blackPlaysFirst = FALSE;
11485         if (!feof(f)) {
11486             (void) fgets(line, MSG_SIZ, f);
11487             if (strncmp(line, "black", strlen("black"))==0)
11488               blackPlaysFirst = TRUE;
11489         }
11490     }
11491     startedFromSetupPosition = TRUE;
11492
11493     SendToProgram("force\n", &first);
11494     CopyBoard(boards[0], initial_position);
11495     if (blackPlaysFirst) {
11496         currentMove = forwardMostMove = backwardMostMove = 1;
11497         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11498         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11499         CopyBoard(boards[1], initial_position);
11500         DisplayMessage("", _("Black to play"));
11501     } else {
11502         currentMove = forwardMostMove = backwardMostMove = 0;
11503         DisplayMessage("", _("White to play"));
11504     }
11505     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11506     SendBoard(&first, forwardMostMove);
11507     if (appData.debugMode) {
11508 int i, j;
11509   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11510   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11511         fprintf(debugFP, "Load Position\n");
11512     }
11513
11514     if (positionNumber > 1) {
11515       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11516         DisplayTitle(line);
11517     } else {
11518         DisplayTitle(title);
11519     }
11520     gameMode = EditGame;
11521     ModeHighlight();
11522     ResetClocks();
11523     timeRemaining[0][1] = whiteTimeRemaining;
11524     timeRemaining[1][1] = blackTimeRemaining;
11525     DrawPosition(FALSE, boards[currentMove]);
11526
11527     return TRUE;
11528 }
11529
11530
11531 void
11532 CopyPlayerNameIntoFileName(dest, src)
11533      char **dest, *src;
11534 {
11535     while (*src != NULLCHAR && *src != ',') {
11536         if (*src == ' ') {
11537             *(*dest)++ = '_';
11538             src++;
11539         } else {
11540             *(*dest)++ = *src++;
11541         }
11542     }
11543 }
11544
11545 char *DefaultFileName(ext)
11546      char *ext;
11547 {
11548     static char def[MSG_SIZ];
11549     char *p;
11550
11551     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11552         p = def;
11553         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11554         *p++ = '-';
11555         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11556         *p++ = '.';
11557         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11558     } else {
11559         def[0] = NULLCHAR;
11560     }
11561     return def;
11562 }
11563
11564 /* Save the current game to the given file */
11565 int
11566 SaveGameToFile(filename, append)
11567      char *filename;
11568      int append;
11569 {
11570     FILE *f;
11571     char buf[MSG_SIZ];
11572     int result;
11573
11574     if (strcmp(filename, "-") == 0) {
11575         return SaveGame(stdout, 0, NULL);
11576     } else {
11577         f = fopen(filename, append ? "a" : "w");
11578         if (f == NULL) {
11579             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11580             DisplayError(buf, errno);
11581             return FALSE;
11582         } else {
11583             safeStrCpy(buf, lastMsg, MSG_SIZ);
11584             DisplayMessage(_("Waiting for access to save file"), "");
11585             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11586             DisplayMessage(_("Saving game"), "");
11587             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11588             result = SaveGame(f, 0, NULL);
11589             DisplayMessage(buf, "");
11590             return result;
11591         }
11592     }
11593 }
11594
11595 char *
11596 SavePart(str)
11597      char *str;
11598 {
11599     static char buf[MSG_SIZ];
11600     char *p;
11601
11602     p = strchr(str, ' ');
11603     if (p == NULL) return str;
11604     strncpy(buf, str, p - str);
11605     buf[p - str] = NULLCHAR;
11606     return buf;
11607 }
11608
11609 #define PGN_MAX_LINE 75
11610
11611 #define PGN_SIDE_WHITE  0
11612 #define PGN_SIDE_BLACK  1
11613
11614 /* [AS] */
11615 static int FindFirstMoveOutOfBook( int side )
11616 {
11617     int result = -1;
11618
11619     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11620         int index = backwardMostMove;
11621         int has_book_hit = 0;
11622
11623         if( (index % 2) != side ) {
11624             index++;
11625         }
11626
11627         while( index < forwardMostMove ) {
11628             /* Check to see if engine is in book */
11629             int depth = pvInfoList[index].depth;
11630             int score = pvInfoList[index].score;
11631             int in_book = 0;
11632
11633             if( depth <= 2 ) {
11634                 in_book = 1;
11635             }
11636             else if( score == 0 && depth == 63 ) {
11637                 in_book = 1; /* Zappa */
11638             }
11639             else if( score == 2 && depth == 99 ) {
11640                 in_book = 1; /* Abrok */
11641             }
11642
11643             has_book_hit += in_book;
11644
11645             if( ! in_book ) {
11646                 result = index;
11647
11648                 break;
11649             }
11650
11651             index += 2;
11652         }
11653     }
11654
11655     return result;
11656 }
11657
11658 /* [AS] */
11659 void GetOutOfBookInfo( char * buf )
11660 {
11661     int oob[2];
11662     int i;
11663     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11664
11665     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11666     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11667
11668     *buf = '\0';
11669
11670     if( oob[0] >= 0 || oob[1] >= 0 ) {
11671         for( i=0; i<2; i++ ) {
11672             int idx = oob[i];
11673
11674             if( idx >= 0 ) {
11675                 if( i > 0 && oob[0] >= 0 ) {
11676                     strcat( buf, "   " );
11677                 }
11678
11679                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11680                 sprintf( buf+strlen(buf), "%s%.2f",
11681                     pvInfoList[idx].score >= 0 ? "+" : "",
11682                     pvInfoList[idx].score / 100.0 );
11683             }
11684         }
11685     }
11686 }
11687
11688 /* Save game in PGN style and close the file */
11689 int
11690 SaveGamePGN(f)
11691      FILE *f;
11692 {
11693     int i, offset, linelen, newblock;
11694     time_t tm;
11695 //    char *movetext;
11696     char numtext[32];
11697     int movelen, numlen, blank;
11698     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11699
11700     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11701
11702     tm = time((time_t *) NULL);
11703
11704     PrintPGNTags(f, &gameInfo);
11705
11706     if (backwardMostMove > 0 || startedFromSetupPosition) {
11707         char *fen = PositionToFEN(backwardMostMove, NULL);
11708         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11709         fprintf(f, "\n{--------------\n");
11710         PrintPosition(f, backwardMostMove);
11711         fprintf(f, "--------------}\n");
11712         free(fen);
11713     }
11714     else {
11715         /* [AS] Out of book annotation */
11716         if( appData.saveOutOfBookInfo ) {
11717             char buf[64];
11718
11719             GetOutOfBookInfo( buf );
11720
11721             if( buf[0] != '\0' ) {
11722                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11723             }
11724         }
11725
11726         fprintf(f, "\n");
11727     }
11728
11729     i = backwardMostMove;
11730     linelen = 0;
11731     newblock = TRUE;
11732
11733     while (i < forwardMostMove) {
11734         /* Print comments preceding this move */
11735         if (commentList[i] != NULL) {
11736             if (linelen > 0) fprintf(f, "\n");
11737             fprintf(f, "%s", commentList[i]);
11738             linelen = 0;
11739             newblock = TRUE;
11740         }
11741
11742         /* Format move number */
11743         if ((i % 2) == 0)
11744           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11745         else
11746           if (newblock)
11747             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11748           else
11749             numtext[0] = NULLCHAR;
11750
11751         numlen = strlen(numtext);
11752         newblock = FALSE;
11753
11754         /* Print move number */
11755         blank = linelen > 0 && numlen > 0;
11756         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11757             fprintf(f, "\n");
11758             linelen = 0;
11759             blank = 0;
11760         }
11761         if (blank) {
11762             fprintf(f, " ");
11763             linelen++;
11764         }
11765         fprintf(f, "%s", numtext);
11766         linelen += numlen;
11767
11768         /* Get move */
11769         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11770         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11771
11772         /* Print move */
11773         blank = linelen > 0 && movelen > 0;
11774         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11775             fprintf(f, "\n");
11776             linelen = 0;
11777             blank = 0;
11778         }
11779         if (blank) {
11780             fprintf(f, " ");
11781             linelen++;
11782         }
11783         fprintf(f, "%s", move_buffer);
11784         linelen += movelen;
11785
11786         /* [AS] Add PV info if present */
11787         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11788             /* [HGM] add time */
11789             char buf[MSG_SIZ]; int seconds;
11790
11791             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11792
11793             if( seconds <= 0)
11794               buf[0] = 0;
11795             else
11796               if( seconds < 30 )
11797                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11798               else
11799                 {
11800                   seconds = (seconds + 4)/10; // round to full seconds
11801                   if( seconds < 60 )
11802                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11803                   else
11804                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11805                 }
11806
11807             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11808                       pvInfoList[i].score >= 0 ? "+" : "",
11809                       pvInfoList[i].score / 100.0,
11810                       pvInfoList[i].depth,
11811                       buf );
11812
11813             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11814
11815             /* Print score/depth */
11816             blank = linelen > 0 && movelen > 0;
11817             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11818                 fprintf(f, "\n");
11819                 linelen = 0;
11820                 blank = 0;
11821             }
11822             if (blank) {
11823                 fprintf(f, " ");
11824                 linelen++;
11825             }
11826             fprintf(f, "%s", move_buffer);
11827             linelen += movelen;
11828         }
11829
11830         i++;
11831     }
11832
11833     /* Start a new line */
11834     if (linelen > 0) fprintf(f, "\n");
11835
11836     /* Print comments after last move */
11837     if (commentList[i] != NULL) {
11838         fprintf(f, "%s\n", commentList[i]);
11839     }
11840
11841     /* Print result */
11842     if (gameInfo.resultDetails != NULL &&
11843         gameInfo.resultDetails[0] != NULLCHAR) {
11844         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11845                 PGNResult(gameInfo.result));
11846     } else {
11847         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11848     }
11849
11850     fclose(f);
11851     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11852     return TRUE;
11853 }
11854
11855 /* Save game in old style and close the file */
11856 int
11857 SaveGameOldStyle(f)
11858      FILE *f;
11859 {
11860     int i, offset;
11861     time_t tm;
11862
11863     tm = time((time_t *) NULL);
11864
11865     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11866     PrintOpponents(f);
11867
11868     if (backwardMostMove > 0 || startedFromSetupPosition) {
11869         fprintf(f, "\n[--------------\n");
11870         PrintPosition(f, backwardMostMove);
11871         fprintf(f, "--------------]\n");
11872     } else {
11873         fprintf(f, "\n");
11874     }
11875
11876     i = backwardMostMove;
11877     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11878
11879     while (i < forwardMostMove) {
11880         if (commentList[i] != NULL) {
11881             fprintf(f, "[%s]\n", commentList[i]);
11882         }
11883
11884         if ((i % 2) == 1) {
11885             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11886             i++;
11887         } else {
11888             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11889             i++;
11890             if (commentList[i] != NULL) {
11891                 fprintf(f, "\n");
11892                 continue;
11893             }
11894             if (i >= forwardMostMove) {
11895                 fprintf(f, "\n");
11896                 break;
11897             }
11898             fprintf(f, "%s\n", parseList[i]);
11899             i++;
11900         }
11901     }
11902
11903     if (commentList[i] != NULL) {
11904         fprintf(f, "[%s]\n", commentList[i]);
11905     }
11906
11907     /* This isn't really the old style, but it's close enough */
11908     if (gameInfo.resultDetails != NULL &&
11909         gameInfo.resultDetails[0] != NULLCHAR) {
11910         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11911                 gameInfo.resultDetails);
11912     } else {
11913         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11914     }
11915
11916     fclose(f);
11917     return TRUE;
11918 }
11919
11920 /* Save the current game to open file f and close the file */
11921 int
11922 SaveGame(f, dummy, dummy2)
11923      FILE *f;
11924      int dummy;
11925      char *dummy2;
11926 {
11927     if (gameMode == EditPosition) EditPositionDone(TRUE);
11928     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11929     if (appData.oldSaveStyle)
11930       return SaveGameOldStyle(f);
11931     else
11932       return SaveGamePGN(f);
11933 }
11934
11935 /* Save the current position to the given file */
11936 int
11937 SavePositionToFile(filename)
11938      char *filename;
11939 {
11940     FILE *f;
11941     char buf[MSG_SIZ];
11942
11943     if (strcmp(filename, "-") == 0) {
11944         return SavePosition(stdout, 0, NULL);
11945     } else {
11946         f = fopen(filename, "a");
11947         if (f == NULL) {
11948             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11949             DisplayError(buf, errno);
11950             return FALSE;
11951         } else {
11952             safeStrCpy(buf, lastMsg, MSG_SIZ);
11953             DisplayMessage(_("Waiting for access to save file"), "");
11954             flock(fileno(f), LOCK_EX); // [HGM] lock
11955             DisplayMessage(_("Saving position"), "");
11956             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11957             SavePosition(f, 0, NULL);
11958             DisplayMessage(buf, "");
11959             return TRUE;
11960         }
11961     }
11962 }
11963
11964 /* Save the current position to the given open file and close the file */
11965 int
11966 SavePosition(f, dummy, dummy2)
11967      FILE *f;
11968      int dummy;
11969      char *dummy2;
11970 {
11971     time_t tm;
11972     char *fen;
11973
11974     if (gameMode == EditPosition) EditPositionDone(TRUE);
11975     if (appData.oldSaveStyle) {
11976         tm = time((time_t *) NULL);
11977
11978         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11979         PrintOpponents(f);
11980         fprintf(f, "[--------------\n");
11981         PrintPosition(f, currentMove);
11982         fprintf(f, "--------------]\n");
11983     } else {
11984         fen = PositionToFEN(currentMove, NULL);
11985         fprintf(f, "%s\n", fen);
11986         free(fen);
11987     }
11988     fclose(f);
11989     return TRUE;
11990 }
11991
11992 void
11993 ReloadCmailMsgEvent(unregister)
11994      int unregister;
11995 {
11996 #if !WIN32
11997     static char *inFilename = NULL;
11998     static char *outFilename;
11999     int i;
12000     struct stat inbuf, outbuf;
12001     int status;
12002
12003     /* Any registered moves are unregistered if unregister is set, */
12004     /* i.e. invoked by the signal handler */
12005     if (unregister) {
12006         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12007             cmailMoveRegistered[i] = FALSE;
12008             if (cmailCommentList[i] != NULL) {
12009                 free(cmailCommentList[i]);
12010                 cmailCommentList[i] = NULL;
12011             }
12012         }
12013         nCmailMovesRegistered = 0;
12014     }
12015
12016     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12017         cmailResult[i] = CMAIL_NOT_RESULT;
12018     }
12019     nCmailResults = 0;
12020
12021     if (inFilename == NULL) {
12022         /* Because the filenames are static they only get malloced once  */
12023         /* and they never get freed                                      */
12024         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12025         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12026
12027         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12028         sprintf(outFilename, "%s.out", appData.cmailGameName);
12029     }
12030
12031     status = stat(outFilename, &outbuf);
12032     if (status < 0) {
12033         cmailMailedMove = FALSE;
12034     } else {
12035         status = stat(inFilename, &inbuf);
12036         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12037     }
12038
12039     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12040        counts the games, notes how each one terminated, etc.
12041
12042        It would be nice to remove this kludge and instead gather all
12043        the information while building the game list.  (And to keep it
12044        in the game list nodes instead of having a bunch of fixed-size
12045        parallel arrays.)  Note this will require getting each game's
12046        termination from the PGN tags, as the game list builder does
12047        not process the game moves.  --mann
12048        */
12049     cmailMsgLoaded = TRUE;
12050     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12051
12052     /* Load first game in the file or popup game menu */
12053     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12054
12055 #endif /* !WIN32 */
12056     return;
12057 }
12058
12059 int
12060 RegisterMove()
12061 {
12062     FILE *f;
12063     char string[MSG_SIZ];
12064
12065     if (   cmailMailedMove
12066         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12067         return TRUE;            /* Allow free viewing  */
12068     }
12069
12070     /* Unregister move to ensure that we don't leave RegisterMove        */
12071     /* with the move registered when the conditions for registering no   */
12072     /* longer hold                                                       */
12073     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12074         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12075         nCmailMovesRegistered --;
12076
12077         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12078           {
12079               free(cmailCommentList[lastLoadGameNumber - 1]);
12080               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12081           }
12082     }
12083
12084     if (cmailOldMove == -1) {
12085         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12086         return FALSE;
12087     }
12088
12089     if (currentMove > cmailOldMove + 1) {
12090         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12091         return FALSE;
12092     }
12093
12094     if (currentMove < cmailOldMove) {
12095         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12096         return FALSE;
12097     }
12098
12099     if (forwardMostMove > currentMove) {
12100         /* Silently truncate extra moves */
12101         TruncateGame();
12102     }
12103
12104     if (   (currentMove == cmailOldMove + 1)
12105         || (   (currentMove == cmailOldMove)
12106             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12107                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12108         if (gameInfo.result != GameUnfinished) {
12109             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12110         }
12111
12112         if (commentList[currentMove] != NULL) {
12113             cmailCommentList[lastLoadGameNumber - 1]
12114               = StrSave(commentList[currentMove]);
12115         }
12116         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12117
12118         if (appData.debugMode)
12119           fprintf(debugFP, "Saving %s for game %d\n",
12120                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12121
12122         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12123
12124         f = fopen(string, "w");
12125         if (appData.oldSaveStyle) {
12126             SaveGameOldStyle(f); /* also closes the file */
12127
12128             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12129             f = fopen(string, "w");
12130             SavePosition(f, 0, NULL); /* also closes the file */
12131         } else {
12132             fprintf(f, "{--------------\n");
12133             PrintPosition(f, currentMove);
12134             fprintf(f, "--------------}\n\n");
12135
12136             SaveGame(f, 0, NULL); /* also closes the file*/
12137         }
12138
12139         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12140         nCmailMovesRegistered ++;
12141     } else if (nCmailGames == 1) {
12142         DisplayError(_("You have not made a move yet"), 0);
12143         return FALSE;
12144     }
12145
12146     return TRUE;
12147 }
12148
12149 void
12150 MailMoveEvent()
12151 {
12152 #if !WIN32
12153     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12154     FILE *commandOutput;
12155     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12156     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12157     int nBuffers;
12158     int i;
12159     int archived;
12160     char *arcDir;
12161
12162     if (! cmailMsgLoaded) {
12163         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12164         return;
12165     }
12166
12167     if (nCmailGames == nCmailResults) {
12168         DisplayError(_("No unfinished games"), 0);
12169         return;
12170     }
12171
12172 #if CMAIL_PROHIBIT_REMAIL
12173     if (cmailMailedMove) {
12174       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);
12175         DisplayError(msg, 0);
12176         return;
12177     }
12178 #endif
12179
12180     if (! (cmailMailedMove || RegisterMove())) return;
12181
12182     if (   cmailMailedMove
12183         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12184       snprintf(string, MSG_SIZ, partCommandString,
12185                appData.debugMode ? " -v" : "", appData.cmailGameName);
12186         commandOutput = popen(string, "r");
12187
12188         if (commandOutput == NULL) {
12189             DisplayError(_("Failed to invoke cmail"), 0);
12190         } else {
12191             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12192                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12193             }
12194             if (nBuffers > 1) {
12195                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12196                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12197                 nBytes = MSG_SIZ - 1;
12198             } else {
12199                 (void) memcpy(msg, buffer, nBytes);
12200             }
12201             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12202
12203             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12204                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12205
12206                 archived = TRUE;
12207                 for (i = 0; i < nCmailGames; i ++) {
12208                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12209                         archived = FALSE;
12210                     }
12211                 }
12212                 if (   archived
12213                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12214                         != NULL)) {
12215                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12216                            arcDir,
12217                            appData.cmailGameName,
12218                            gameInfo.date);
12219                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12220                     cmailMsgLoaded = FALSE;
12221                 }
12222             }
12223
12224             DisplayInformation(msg);
12225             pclose(commandOutput);
12226         }
12227     } else {
12228         if ((*cmailMsg) != '\0') {
12229             DisplayInformation(cmailMsg);
12230         }
12231     }
12232
12233     return;
12234 #endif /* !WIN32 */
12235 }
12236
12237 char *
12238 CmailMsg()
12239 {
12240 #if WIN32
12241     return NULL;
12242 #else
12243     int  prependComma = 0;
12244     char number[5];
12245     char string[MSG_SIZ];       /* Space for game-list */
12246     int  i;
12247
12248     if (!cmailMsgLoaded) return "";
12249
12250     if (cmailMailedMove) {
12251       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12252     } else {
12253         /* Create a list of games left */
12254       snprintf(string, MSG_SIZ, "[");
12255         for (i = 0; i < nCmailGames; i ++) {
12256             if (! (   cmailMoveRegistered[i]
12257                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12258                 if (prependComma) {
12259                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12260                 } else {
12261                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12262                     prependComma = 1;
12263                 }
12264
12265                 strcat(string, number);
12266             }
12267         }
12268         strcat(string, "]");
12269
12270         if (nCmailMovesRegistered + nCmailResults == 0) {
12271             switch (nCmailGames) {
12272               case 1:
12273                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12274                 break;
12275
12276               case 2:
12277                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12278                 break;
12279
12280               default:
12281                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12282                          nCmailGames);
12283                 break;
12284             }
12285         } else {
12286             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12287               case 1:
12288                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12289                          string);
12290                 break;
12291
12292               case 0:
12293                 if (nCmailResults == nCmailGames) {
12294                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12295                 } else {
12296                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12297                 }
12298                 break;
12299
12300               default:
12301                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12302                          string);
12303             }
12304         }
12305     }
12306     return cmailMsg;
12307 #endif /* WIN32 */
12308 }
12309
12310 void
12311 ResetGameEvent()
12312 {
12313     if (gameMode == Training)
12314       SetTrainingModeOff();
12315
12316     Reset(TRUE, TRUE);
12317     cmailMsgLoaded = FALSE;
12318     if (appData.icsActive) {
12319       SendToICS(ics_prefix);
12320       SendToICS("refresh\n");
12321     }
12322 }
12323
12324 void
12325 ExitEvent(status)
12326      int status;
12327 {
12328     exiting++;
12329     if (exiting > 2) {
12330       /* Give up on clean exit */
12331       exit(status);
12332     }
12333     if (exiting > 1) {
12334       /* Keep trying for clean exit */
12335       return;
12336     }
12337
12338     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12339
12340     if (telnetISR != NULL) {
12341       RemoveInputSource(telnetISR);
12342     }
12343     if (icsPR != NoProc) {
12344       DestroyChildProcess(icsPR, TRUE);
12345     }
12346
12347     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12348     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12349
12350     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12351     /* make sure this other one finishes before killing it!                  */
12352     if(endingGame) { int count = 0;
12353         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12354         while(endingGame && count++ < 10) DoSleep(1);
12355         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12356     }
12357
12358     /* Kill off chess programs */
12359     if (first.pr != NoProc) {
12360         ExitAnalyzeMode();
12361
12362         DoSleep( appData.delayBeforeQuit );
12363         SendToProgram("quit\n", &first);
12364         DoSleep( appData.delayAfterQuit );
12365         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12366     }
12367     if (second.pr != NoProc) {
12368         DoSleep( appData.delayBeforeQuit );
12369         SendToProgram("quit\n", &second);
12370         DoSleep( appData.delayAfterQuit );
12371         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12372     }
12373     if (first.isr != NULL) {
12374         RemoveInputSource(first.isr);
12375     }
12376     if (second.isr != NULL) {
12377         RemoveInputSource(second.isr);
12378     }
12379
12380     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12381     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12382
12383     ShutDownFrontEnd();
12384     exit(status);
12385 }
12386
12387 void
12388 PauseEvent()
12389 {
12390     if (appData.debugMode)
12391         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12392     if (pausing) {
12393         pausing = FALSE;
12394         ModeHighlight();
12395         if (gameMode == MachinePlaysWhite ||
12396             gameMode == MachinePlaysBlack) {
12397             StartClocks();
12398         } else {
12399             DisplayBothClocks();
12400         }
12401         if (gameMode == PlayFromGameFile) {
12402             if (appData.timeDelay >= 0)
12403                 AutoPlayGameLoop();
12404         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12405             Reset(FALSE, TRUE);
12406             SendToICS(ics_prefix);
12407             SendToICS("refresh\n");
12408         } else if (currentMove < forwardMostMove) {
12409             ForwardInner(forwardMostMove);
12410         }
12411         pauseExamInvalid = FALSE;
12412     } else {
12413         switch (gameMode) {
12414           default:
12415             return;
12416           case IcsExamining:
12417             pauseExamForwardMostMove = forwardMostMove;
12418             pauseExamInvalid = FALSE;
12419             /* fall through */
12420           case IcsObserving:
12421           case IcsPlayingWhite:
12422           case IcsPlayingBlack:
12423             pausing = TRUE;
12424             ModeHighlight();
12425             return;
12426           case PlayFromGameFile:
12427             (void) StopLoadGameTimer();
12428             pausing = TRUE;
12429             ModeHighlight();
12430             break;
12431           case BeginningOfGame:
12432             if (appData.icsActive) return;
12433             /* else fall through */
12434           case MachinePlaysWhite:
12435           case MachinePlaysBlack:
12436           case TwoMachinesPlay:
12437             if (forwardMostMove == 0)
12438               return;           /* don't pause if no one has moved */
12439             if ((gameMode == MachinePlaysWhite &&
12440                  !WhiteOnMove(forwardMostMove)) ||
12441                 (gameMode == MachinePlaysBlack &&
12442                  WhiteOnMove(forwardMostMove))) {
12443                 StopClocks();
12444             }
12445             pausing = TRUE;
12446             ModeHighlight();
12447             break;
12448         }
12449     }
12450 }
12451
12452 void
12453 EditCommentEvent()
12454 {
12455     char title[MSG_SIZ];
12456
12457     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12458       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12459     } else {
12460       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12461                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12462                parseList[currentMove - 1]);
12463     }
12464
12465     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12466 }
12467
12468
12469 void
12470 EditTagsEvent()
12471 {
12472     char *tags = PGNTags(&gameInfo);
12473     bookUp = FALSE;
12474     EditTagsPopUp(tags, NULL);
12475     free(tags);
12476 }
12477
12478 void
12479 AnalyzeModeEvent()
12480 {
12481     if (appData.noChessProgram || gameMode == AnalyzeMode)
12482       return;
12483
12484     if (gameMode != AnalyzeFile) {
12485         if (!appData.icsEngineAnalyze) {
12486                EditGameEvent();
12487                if (gameMode != EditGame) return;
12488         }
12489         ResurrectChessProgram();
12490         SendToProgram("analyze\n", &first);
12491         first.analyzing = TRUE;
12492         /*first.maybeThinking = TRUE;*/
12493         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12494         EngineOutputPopUp();
12495     }
12496     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12497     pausing = FALSE;
12498     ModeHighlight();
12499     SetGameInfo();
12500
12501     StartAnalysisClock();
12502     GetTimeMark(&lastNodeCountTime);
12503     lastNodeCount = 0;
12504 }
12505
12506 void
12507 AnalyzeFileEvent()
12508 {
12509     if (appData.noChessProgram || gameMode == AnalyzeFile)
12510       return;
12511
12512     if (gameMode != AnalyzeMode) {
12513         EditGameEvent();
12514         if (gameMode != EditGame) return;
12515         ResurrectChessProgram();
12516         SendToProgram("analyze\n", &first);
12517         first.analyzing = TRUE;
12518         /*first.maybeThinking = TRUE;*/
12519         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12520         EngineOutputPopUp();
12521     }
12522     gameMode = AnalyzeFile;
12523     pausing = FALSE;
12524     ModeHighlight();
12525     SetGameInfo();
12526
12527     StartAnalysisClock();
12528     GetTimeMark(&lastNodeCountTime);
12529     lastNodeCount = 0;
12530 }
12531
12532 void
12533 MachineWhiteEvent()
12534 {
12535     char buf[MSG_SIZ];
12536     char *bookHit = NULL;
12537
12538     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12539       return;
12540
12541
12542     if (gameMode == PlayFromGameFile ||
12543         gameMode == TwoMachinesPlay  ||
12544         gameMode == Training         ||
12545         gameMode == AnalyzeMode      ||
12546         gameMode == EndOfGame)
12547         EditGameEvent();
12548
12549     if (gameMode == EditPosition)
12550         EditPositionDone(TRUE);
12551
12552     if (!WhiteOnMove(currentMove)) {
12553         DisplayError(_("It is not White's turn"), 0);
12554         return;
12555     }
12556
12557     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12558       ExitAnalyzeMode();
12559
12560     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12561         gameMode == AnalyzeFile)
12562         TruncateGame();
12563
12564     ResurrectChessProgram();    /* in case it isn't running */
12565     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12566         gameMode = MachinePlaysWhite;
12567         ResetClocks();
12568     } else
12569     gameMode = MachinePlaysWhite;
12570     pausing = FALSE;
12571     ModeHighlight();
12572     SetGameInfo();
12573     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12574     DisplayTitle(buf);
12575     if (first.sendName) {
12576       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12577       SendToProgram(buf, &first);
12578     }
12579     if (first.sendTime) {
12580       if (first.useColors) {
12581         SendToProgram("black\n", &first); /*gnu kludge*/
12582       }
12583       SendTimeRemaining(&first, TRUE);
12584     }
12585     if (first.useColors) {
12586       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12587     }
12588     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12589     SetMachineThinkingEnables();
12590     first.maybeThinking = TRUE;
12591     StartClocks();
12592     firstMove = FALSE;
12593
12594     if (appData.autoFlipView && !flipView) {
12595       flipView = !flipView;
12596       DrawPosition(FALSE, NULL);
12597       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12598     }
12599
12600     if(bookHit) { // [HGM] book: simulate book reply
12601         static char bookMove[MSG_SIZ]; // a bit generous?
12602
12603         programStats.nodes = programStats.depth = programStats.time =
12604         programStats.score = programStats.got_only_move = 0;
12605         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12606
12607         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12608         strcat(bookMove, bookHit);
12609         HandleMachineMove(bookMove, &first);
12610     }
12611 }
12612
12613 void
12614 MachineBlackEvent()
12615 {
12616   char buf[MSG_SIZ];
12617   char *bookHit = NULL;
12618
12619     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12620         return;
12621
12622
12623     if (gameMode == PlayFromGameFile ||
12624         gameMode == TwoMachinesPlay  ||
12625         gameMode == Training         ||
12626         gameMode == AnalyzeMode      ||
12627         gameMode == EndOfGame)
12628         EditGameEvent();
12629
12630     if (gameMode == EditPosition)
12631         EditPositionDone(TRUE);
12632
12633     if (WhiteOnMove(currentMove)) {
12634         DisplayError(_("It is not Black's turn"), 0);
12635         return;
12636     }
12637
12638     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12639       ExitAnalyzeMode();
12640
12641     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12642         gameMode == AnalyzeFile)
12643         TruncateGame();
12644
12645     ResurrectChessProgram();    /* in case it isn't running */
12646     gameMode = MachinePlaysBlack;
12647     pausing = FALSE;
12648     ModeHighlight();
12649     SetGameInfo();
12650     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12651     DisplayTitle(buf);
12652     if (first.sendName) {
12653       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12654       SendToProgram(buf, &first);
12655     }
12656     if (first.sendTime) {
12657       if (first.useColors) {
12658         SendToProgram("white\n", &first); /*gnu kludge*/
12659       }
12660       SendTimeRemaining(&first, FALSE);
12661     }
12662     if (first.useColors) {
12663       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12664     }
12665     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12666     SetMachineThinkingEnables();
12667     first.maybeThinking = TRUE;
12668     StartClocks();
12669
12670     if (appData.autoFlipView && flipView) {
12671       flipView = !flipView;
12672       DrawPosition(FALSE, NULL);
12673       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12674     }
12675     if(bookHit) { // [HGM] book: simulate book reply
12676         static char bookMove[MSG_SIZ]; // a bit generous?
12677
12678         programStats.nodes = programStats.depth = programStats.time =
12679         programStats.score = programStats.got_only_move = 0;
12680         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12681
12682         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12683         strcat(bookMove, bookHit);
12684         HandleMachineMove(bookMove, &first);
12685     }
12686 }
12687
12688
12689 void
12690 DisplayTwoMachinesTitle()
12691 {
12692     char buf[MSG_SIZ];
12693     if (appData.matchGames > 0) {
12694         if (first.twoMachinesColor[0] == 'w') {
12695           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12696                    gameInfo.white, gameInfo.black,
12697                    first.matchWins, second.matchWins,
12698                    matchGame - 1 - (first.matchWins + second.matchWins));
12699         } else {
12700           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12701                    gameInfo.white, gameInfo.black,
12702                    second.matchWins, first.matchWins,
12703                    matchGame - 1 - (first.matchWins + second.matchWins));
12704         }
12705     } else {
12706       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12707     }
12708     DisplayTitle(buf);
12709 }
12710
12711 void
12712 SettingsMenuIfReady()
12713 {
12714   if (second.lastPing != second.lastPong) {
12715     DisplayMessage("", _("Waiting for second chess program"));
12716     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12717     return;
12718   }
12719   ThawUI();
12720   DisplayMessage("", "");
12721   SettingsPopUp(&second);
12722 }
12723
12724 int
12725 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12726 {
12727     char buf[MSG_SIZ];
12728     if (cps->pr == NULL) {
12729         StartChessProgram(cps);
12730         if (cps->protocolVersion == 1) {
12731           retry();
12732         } else {
12733           /* kludge: allow timeout for initial "feature" command */
12734           FreezeUI();
12735           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12736           DisplayMessage("", buf);
12737           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12738         }
12739         return 1;
12740     }
12741     return 0;
12742 }
12743
12744 void
12745 TwoMachinesEvent P((void))
12746 {
12747     int i;
12748     char buf[MSG_SIZ];
12749     ChessProgramState *onmove;
12750     char *bookHit = NULL;
12751     static int stalling = 0;
12752     TimeMark now;
12753     long wait;
12754
12755     if (appData.noChessProgram) return;
12756
12757     switch (gameMode) {
12758       case TwoMachinesPlay:
12759         return;
12760       case MachinePlaysWhite:
12761       case MachinePlaysBlack:
12762         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12763             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12764             return;
12765         }
12766         /* fall through */
12767       case BeginningOfGame:
12768       case PlayFromGameFile:
12769       case EndOfGame:
12770         EditGameEvent();
12771         if (gameMode != EditGame) return;
12772         break;
12773       case EditPosition:
12774         EditPositionDone(TRUE);
12775         break;
12776       case AnalyzeMode:
12777       case AnalyzeFile:
12778         ExitAnalyzeMode();
12779         break;
12780       case EditGame:
12781       default:
12782         break;
12783     }
12784
12785 //    forwardMostMove = currentMove;
12786     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12787
12788     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12789
12790     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12791     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12792       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12793       return;
12794     }
12795     if(!stalling) {
12796       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12797       SendToProgram("force\n", &second);
12798       stalling = 1;
12799       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12800       return;
12801     }
12802     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12803     if(appData.matchPause>10000 || appData.matchPause<10)
12804                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12805     wait = SubtractTimeMarks(&now, &pauseStart);
12806     if(wait < appData.matchPause) {
12807         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12808         return;
12809     }
12810     stalling = 0;
12811     DisplayMessage("", "");
12812     if (startedFromSetupPosition) {
12813         SendBoard(&second, backwardMostMove);
12814     if (appData.debugMode) {
12815         fprintf(debugFP, "Two Machines\n");
12816     }
12817     }
12818     for (i = backwardMostMove; i < forwardMostMove; i++) {
12819         SendMoveToProgram(i, &second);
12820     }
12821
12822     gameMode = TwoMachinesPlay;
12823     pausing = FALSE;
12824     ModeHighlight();
12825     SetGameInfo();
12826     DisplayTwoMachinesTitle();
12827     firstMove = TRUE;
12828     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12829         onmove = &first;
12830     } else {
12831         onmove = &second;
12832     }
12833     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12834     SendToProgram(first.computerString, &first);
12835     if (first.sendName) {
12836       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12837       SendToProgram(buf, &first);
12838     }
12839     SendToProgram(second.computerString, &second);
12840     if (second.sendName) {
12841       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12842       SendToProgram(buf, &second);
12843     }
12844
12845     ResetClocks();
12846     if (!first.sendTime || !second.sendTime) {
12847         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12848         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12849     }
12850     if (onmove->sendTime) {
12851       if (onmove->useColors) {
12852         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12853       }
12854       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12855     }
12856     if (onmove->useColors) {
12857       SendToProgram(onmove->twoMachinesColor, onmove);
12858     }
12859     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12860 //    SendToProgram("go\n", onmove);
12861     onmove->maybeThinking = TRUE;
12862     SetMachineThinkingEnables();
12863
12864     StartClocks();
12865
12866     if(bookHit) { // [HGM] book: simulate book reply
12867         static char bookMove[MSG_SIZ]; // a bit generous?
12868
12869         programStats.nodes = programStats.depth = programStats.time =
12870         programStats.score = programStats.got_only_move = 0;
12871         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12872
12873         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12874         strcat(bookMove, bookHit);
12875         savedMessage = bookMove; // args for deferred call
12876         savedState = onmove;
12877         ScheduleDelayedEvent(DeferredBookMove, 1);
12878     }
12879 }
12880
12881 void
12882 TrainingEvent()
12883 {
12884     if (gameMode == Training) {
12885       SetTrainingModeOff();
12886       gameMode = PlayFromGameFile;
12887       DisplayMessage("", _("Training mode off"));
12888     } else {
12889       gameMode = Training;
12890       animateTraining = appData.animate;
12891
12892       /* make sure we are not already at the end of the game */
12893       if (currentMove < forwardMostMove) {
12894         SetTrainingModeOn();
12895         DisplayMessage("", _("Training mode on"));
12896       } else {
12897         gameMode = PlayFromGameFile;
12898         DisplayError(_("Already at end of game"), 0);
12899       }
12900     }
12901     ModeHighlight();
12902 }
12903
12904 void
12905 IcsClientEvent()
12906 {
12907     if (!appData.icsActive) return;
12908     switch (gameMode) {
12909       case IcsPlayingWhite:
12910       case IcsPlayingBlack:
12911       case IcsObserving:
12912       case IcsIdle:
12913       case BeginningOfGame:
12914       case IcsExamining:
12915         return;
12916
12917       case EditGame:
12918         break;
12919
12920       case EditPosition:
12921         EditPositionDone(TRUE);
12922         break;
12923
12924       case AnalyzeMode:
12925       case AnalyzeFile:
12926         ExitAnalyzeMode();
12927         break;
12928
12929       default:
12930         EditGameEvent();
12931         break;
12932     }
12933
12934     gameMode = IcsIdle;
12935     ModeHighlight();
12936     return;
12937 }
12938
12939
12940 void
12941 EditGameEvent()
12942 {
12943     int i;
12944
12945     switch (gameMode) {
12946       case Training:
12947         SetTrainingModeOff();
12948         break;
12949       case MachinePlaysWhite:
12950       case MachinePlaysBlack:
12951       case BeginningOfGame:
12952         SendToProgram("force\n", &first);
12953         SetUserThinkingEnables();
12954         break;
12955       case PlayFromGameFile:
12956         (void) StopLoadGameTimer();
12957         if (gameFileFP != NULL) {
12958             gameFileFP = NULL;
12959         }
12960         break;
12961       case EditPosition:
12962         EditPositionDone(TRUE);
12963         break;
12964       case AnalyzeMode:
12965       case AnalyzeFile:
12966         ExitAnalyzeMode();
12967         SendToProgram("force\n", &first);
12968         break;
12969       case TwoMachinesPlay:
12970         GameEnds(EndOfFile, NULL, GE_PLAYER);
12971         ResurrectChessProgram();
12972         SetUserThinkingEnables();
12973         break;
12974       case EndOfGame:
12975         ResurrectChessProgram();
12976         break;
12977       case IcsPlayingBlack:
12978       case IcsPlayingWhite:
12979         DisplayError(_("Warning: You are still playing a game"), 0);
12980         break;
12981       case IcsObserving:
12982         DisplayError(_("Warning: You are still observing a game"), 0);
12983         break;
12984       case IcsExamining:
12985         DisplayError(_("Warning: You are still examining a game"), 0);
12986         break;
12987       case IcsIdle:
12988         break;
12989       case EditGame:
12990       default:
12991         return;
12992     }
12993
12994     pausing = FALSE;
12995     StopClocks();
12996     first.offeredDraw = second.offeredDraw = 0;
12997
12998     if (gameMode == PlayFromGameFile) {
12999         whiteTimeRemaining = timeRemaining[0][currentMove];
13000         blackTimeRemaining = timeRemaining[1][currentMove];
13001         DisplayTitle("");
13002     }
13003
13004     if (gameMode == MachinePlaysWhite ||
13005         gameMode == MachinePlaysBlack ||
13006         gameMode == TwoMachinesPlay ||
13007         gameMode == EndOfGame) {
13008         i = forwardMostMove;
13009         while (i > currentMove) {
13010             SendToProgram("undo\n", &first);
13011             i--;
13012         }
13013         whiteTimeRemaining = timeRemaining[0][currentMove];
13014         blackTimeRemaining = timeRemaining[1][currentMove];
13015         DisplayBothClocks();
13016         if (whiteFlag || blackFlag) {
13017             whiteFlag = blackFlag = 0;
13018         }
13019         DisplayTitle("");
13020     }
13021
13022     gameMode = EditGame;
13023     ModeHighlight();
13024     SetGameInfo();
13025 }
13026
13027
13028 void
13029 EditPositionEvent()
13030 {
13031     if (gameMode == EditPosition) {
13032         EditGameEvent();
13033         return;
13034     }
13035
13036     EditGameEvent();
13037     if (gameMode != EditGame) return;
13038
13039     gameMode = EditPosition;
13040     ModeHighlight();
13041     SetGameInfo();
13042     if (currentMove > 0)
13043       CopyBoard(boards[0], boards[currentMove]);
13044
13045     blackPlaysFirst = !WhiteOnMove(currentMove);
13046     ResetClocks();
13047     currentMove = forwardMostMove = backwardMostMove = 0;
13048     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13049     DisplayMove(-1);
13050 }
13051
13052 void
13053 ExitAnalyzeMode()
13054 {
13055     /* [DM] icsEngineAnalyze - possible call from other functions */
13056     if (appData.icsEngineAnalyze) {
13057         appData.icsEngineAnalyze = FALSE;
13058
13059         DisplayMessage("",_("Close ICS engine analyze..."));
13060     }
13061     if (first.analysisSupport && first.analyzing) {
13062       SendToProgram("exit\n", &first);
13063       first.analyzing = FALSE;
13064     }
13065     thinkOutput[0] = NULLCHAR;
13066 }
13067
13068 void
13069 EditPositionDone(Boolean fakeRights)
13070 {
13071     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13072
13073     startedFromSetupPosition = TRUE;
13074     InitChessProgram(&first, FALSE);
13075     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13076       boards[0][EP_STATUS] = EP_NONE;
13077       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13078     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13079         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13080         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13081       } else boards[0][CASTLING][2] = NoRights;
13082     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13083         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13084         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13085       } else boards[0][CASTLING][5] = NoRights;
13086     }
13087     SendToProgram("force\n", &first);
13088     if (blackPlaysFirst) {
13089         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13090         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13091         currentMove = forwardMostMove = backwardMostMove = 1;
13092         CopyBoard(boards[1], boards[0]);
13093     } else {
13094         currentMove = forwardMostMove = backwardMostMove = 0;
13095     }
13096     SendBoard(&first, forwardMostMove);
13097     if (appData.debugMode) {
13098         fprintf(debugFP, "EditPosDone\n");
13099     }
13100     DisplayTitle("");
13101     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13102     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13103     gameMode = EditGame;
13104     ModeHighlight();
13105     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13106     ClearHighlights(); /* [AS] */
13107 }
13108
13109 /* Pause for `ms' milliseconds */
13110 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13111 void
13112 TimeDelay(ms)
13113      long ms;
13114 {
13115     TimeMark m1, m2;
13116
13117     GetTimeMark(&m1);
13118     do {
13119         GetTimeMark(&m2);
13120     } while (SubtractTimeMarks(&m2, &m1) < ms);
13121 }
13122
13123 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13124 void
13125 SendMultiLineToICS(buf)
13126      char *buf;
13127 {
13128     char temp[MSG_SIZ+1], *p;
13129     int len;
13130
13131     len = strlen(buf);
13132     if (len > MSG_SIZ)
13133       len = MSG_SIZ;
13134
13135     strncpy(temp, buf, len);
13136     temp[len] = 0;
13137
13138     p = temp;
13139     while (*p) {
13140         if (*p == '\n' || *p == '\r')
13141           *p = ' ';
13142         ++p;
13143     }
13144
13145     strcat(temp, "\n");
13146     SendToICS(temp);
13147     SendToPlayer(temp, strlen(temp));
13148 }
13149
13150 void
13151 SetWhiteToPlayEvent()
13152 {
13153     if (gameMode == EditPosition) {
13154         blackPlaysFirst = FALSE;
13155         DisplayBothClocks();    /* works because currentMove is 0 */
13156     } else if (gameMode == IcsExamining) {
13157         SendToICS(ics_prefix);
13158         SendToICS("tomove white\n");
13159     }
13160 }
13161
13162 void
13163 SetBlackToPlayEvent()
13164 {
13165     if (gameMode == EditPosition) {
13166         blackPlaysFirst = TRUE;
13167         currentMove = 1;        /* kludge */
13168         DisplayBothClocks();
13169         currentMove = 0;
13170     } else if (gameMode == IcsExamining) {
13171         SendToICS(ics_prefix);
13172         SendToICS("tomove black\n");
13173     }
13174 }
13175
13176 void
13177 EditPositionMenuEvent(selection, x, y)
13178      ChessSquare selection;
13179      int x, y;
13180 {
13181     char buf[MSG_SIZ];
13182     ChessSquare piece = boards[0][y][x];
13183
13184     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13185
13186     switch (selection) {
13187       case ClearBoard:
13188         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13189             SendToICS(ics_prefix);
13190             SendToICS("bsetup clear\n");
13191         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13192             SendToICS(ics_prefix);
13193             SendToICS("clearboard\n");
13194         } else {
13195             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13196                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13197                 for (y = 0; y < BOARD_HEIGHT; y++) {
13198                     if (gameMode == IcsExamining) {
13199                         if (boards[currentMove][y][x] != EmptySquare) {
13200                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13201                                     AAA + x, ONE + y);
13202                             SendToICS(buf);
13203                         }
13204                     } else {
13205                         boards[0][y][x] = p;
13206                     }
13207                 }
13208             }
13209         }
13210         if (gameMode == EditPosition) {
13211             DrawPosition(FALSE, boards[0]);
13212         }
13213         break;
13214
13215       case WhitePlay:
13216         SetWhiteToPlayEvent();
13217         break;
13218
13219       case BlackPlay:
13220         SetBlackToPlayEvent();
13221         break;
13222
13223       case EmptySquare:
13224         if (gameMode == IcsExamining) {
13225             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13226             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13227             SendToICS(buf);
13228         } else {
13229             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13230                 if(x == BOARD_LEFT-2) {
13231                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13232                     boards[0][y][1] = 0;
13233                 } else
13234                 if(x == BOARD_RGHT+1) {
13235                     if(y >= gameInfo.holdingsSize) break;
13236                     boards[0][y][BOARD_WIDTH-2] = 0;
13237                 } else break;
13238             }
13239             boards[0][y][x] = EmptySquare;
13240             DrawPosition(FALSE, boards[0]);
13241         }
13242         break;
13243
13244       case PromotePiece:
13245         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13246            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13247             selection = (ChessSquare) (PROMOTED piece);
13248         } else if(piece == EmptySquare) selection = WhiteSilver;
13249         else selection = (ChessSquare)((int)piece - 1);
13250         goto defaultlabel;
13251
13252       case DemotePiece:
13253         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13254            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13255             selection = (ChessSquare) (DEMOTED piece);
13256         } else if(piece == EmptySquare) selection = BlackSilver;
13257         else selection = (ChessSquare)((int)piece + 1);
13258         goto defaultlabel;
13259
13260       case WhiteQueen:
13261       case BlackQueen:
13262         if(gameInfo.variant == VariantShatranj ||
13263            gameInfo.variant == VariantXiangqi  ||
13264            gameInfo.variant == VariantCourier  ||
13265            gameInfo.variant == VariantMakruk     )
13266             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13267         goto defaultlabel;
13268
13269       case WhiteKing:
13270       case BlackKing:
13271         if(gameInfo.variant == VariantXiangqi)
13272             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13273         if(gameInfo.variant == VariantKnightmate)
13274             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13275       default:
13276         defaultlabel:
13277         if (gameMode == IcsExamining) {
13278             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13279             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13280                      PieceToChar(selection), AAA + x, ONE + y);
13281             SendToICS(buf);
13282         } else {
13283             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13284                 int n;
13285                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13286                     n = PieceToNumber(selection - BlackPawn);
13287                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13288                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13289                     boards[0][BOARD_HEIGHT-1-n][1]++;
13290                 } else
13291                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13292                     n = PieceToNumber(selection);
13293                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13294                     boards[0][n][BOARD_WIDTH-1] = selection;
13295                     boards[0][n][BOARD_WIDTH-2]++;
13296                 }
13297             } else
13298             boards[0][y][x] = selection;
13299             DrawPosition(TRUE, boards[0]);
13300         }
13301         break;
13302     }
13303 }
13304
13305
13306 void
13307 DropMenuEvent(selection, x, y)
13308      ChessSquare selection;
13309      int x, y;
13310 {
13311     ChessMove moveType;
13312
13313     switch (gameMode) {
13314       case IcsPlayingWhite:
13315       case MachinePlaysBlack:
13316         if (!WhiteOnMove(currentMove)) {
13317             DisplayMoveError(_("It is Black's turn"));
13318             return;
13319         }
13320         moveType = WhiteDrop;
13321         break;
13322       case IcsPlayingBlack:
13323       case MachinePlaysWhite:
13324         if (WhiteOnMove(currentMove)) {
13325             DisplayMoveError(_("It is White's turn"));
13326             return;
13327         }
13328         moveType = BlackDrop;
13329         break;
13330       case EditGame:
13331         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13332         break;
13333       default:
13334         return;
13335     }
13336
13337     if (moveType == BlackDrop && selection < BlackPawn) {
13338       selection = (ChessSquare) ((int) selection
13339                                  + (int) BlackPawn - (int) WhitePawn);
13340     }
13341     if (boards[currentMove][y][x] != EmptySquare) {
13342         DisplayMoveError(_("That square is occupied"));
13343         return;
13344     }
13345
13346     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13347 }
13348
13349 void
13350 AcceptEvent()
13351 {
13352     /* Accept a pending offer of any kind from opponent */
13353
13354     if (appData.icsActive) {
13355         SendToICS(ics_prefix);
13356         SendToICS("accept\n");
13357     } else if (cmailMsgLoaded) {
13358         if (currentMove == cmailOldMove &&
13359             commentList[cmailOldMove] != NULL &&
13360             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13361                    "Black offers a draw" : "White offers a draw")) {
13362             TruncateGame();
13363             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13364             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13365         } else {
13366             DisplayError(_("There is no pending offer on this move"), 0);
13367             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13368         }
13369     } else {
13370         /* Not used for offers from chess program */
13371     }
13372 }
13373
13374 void
13375 DeclineEvent()
13376 {
13377     /* Decline a pending offer of any kind from opponent */
13378
13379     if (appData.icsActive) {
13380         SendToICS(ics_prefix);
13381         SendToICS("decline\n");
13382     } else if (cmailMsgLoaded) {
13383         if (currentMove == cmailOldMove &&
13384             commentList[cmailOldMove] != NULL &&
13385             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13386                    "Black offers a draw" : "White offers a draw")) {
13387 #ifdef NOTDEF
13388             AppendComment(cmailOldMove, "Draw declined", TRUE);
13389             DisplayComment(cmailOldMove - 1, "Draw declined");
13390 #endif /*NOTDEF*/
13391         } else {
13392             DisplayError(_("There is no pending offer on this move"), 0);
13393         }
13394     } else {
13395         /* Not used for offers from chess program */
13396     }
13397 }
13398
13399 void
13400 RematchEvent()
13401 {
13402     /* Issue ICS rematch command */
13403     if (appData.icsActive) {
13404         SendToICS(ics_prefix);
13405         SendToICS("rematch\n");
13406     }
13407 }
13408
13409 void
13410 CallFlagEvent()
13411 {
13412     /* Call your opponent's flag (claim a win on time) */
13413     if (appData.icsActive) {
13414         SendToICS(ics_prefix);
13415         SendToICS("flag\n");
13416     } else {
13417         switch (gameMode) {
13418           default:
13419             return;
13420           case MachinePlaysWhite:
13421             if (whiteFlag) {
13422                 if (blackFlag)
13423                   GameEnds(GameIsDrawn, "Both players ran out of time",
13424                            GE_PLAYER);
13425                 else
13426                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13427             } else {
13428                 DisplayError(_("Your opponent is not out of time"), 0);
13429             }
13430             break;
13431           case MachinePlaysBlack:
13432             if (blackFlag) {
13433                 if (whiteFlag)
13434                   GameEnds(GameIsDrawn, "Both players ran out of time",
13435                            GE_PLAYER);
13436                 else
13437                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13438             } else {
13439                 DisplayError(_("Your opponent is not out of time"), 0);
13440             }
13441             break;
13442         }
13443     }
13444 }
13445
13446 void
13447 ClockClick(int which)
13448 {       // [HGM] code moved to back-end from winboard.c
13449         if(which) { // black clock
13450           if (gameMode == EditPosition || gameMode == IcsExamining) {
13451             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13452             SetBlackToPlayEvent();
13453           } else if (gameMode == EditGame || shiftKey) {
13454             AdjustClock(which, -1);
13455           } else if (gameMode == IcsPlayingWhite ||
13456                      gameMode == MachinePlaysBlack) {
13457             CallFlagEvent();
13458           }
13459         } else { // white clock
13460           if (gameMode == EditPosition || gameMode == IcsExamining) {
13461             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13462             SetWhiteToPlayEvent();
13463           } else if (gameMode == EditGame || shiftKey) {
13464             AdjustClock(which, -1);
13465           } else if (gameMode == IcsPlayingBlack ||
13466                    gameMode == MachinePlaysWhite) {
13467             CallFlagEvent();
13468           }
13469         }
13470 }
13471
13472 void
13473 DrawEvent()
13474 {
13475     /* Offer draw or accept pending draw offer from opponent */
13476
13477     if (appData.icsActive) {
13478         /* Note: tournament rules require draw offers to be
13479            made after you make your move but before you punch
13480            your clock.  Currently ICS doesn't let you do that;
13481            instead, you immediately punch your clock after making
13482            a move, but you can offer a draw at any time. */
13483
13484         SendToICS(ics_prefix);
13485         SendToICS("draw\n");
13486         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13487     } else if (cmailMsgLoaded) {
13488         if (currentMove == cmailOldMove &&
13489             commentList[cmailOldMove] != NULL &&
13490             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13491                    "Black offers a draw" : "White offers a draw")) {
13492             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13493             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13494         } else if (currentMove == cmailOldMove + 1) {
13495             char *offer = WhiteOnMove(cmailOldMove) ?
13496               "White offers a draw" : "Black offers a draw";
13497             AppendComment(currentMove, offer, TRUE);
13498             DisplayComment(currentMove - 1, offer);
13499             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13500         } else {
13501             DisplayError(_("You must make your move before offering a draw"), 0);
13502             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13503         }
13504     } else if (first.offeredDraw) {
13505         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13506     } else {
13507         if (first.sendDrawOffers) {
13508             SendToProgram("draw\n", &first);
13509             userOfferedDraw = TRUE;
13510         }
13511     }
13512 }
13513
13514 void
13515 AdjournEvent()
13516 {
13517     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13518
13519     if (appData.icsActive) {
13520         SendToICS(ics_prefix);
13521         SendToICS("adjourn\n");
13522     } else {
13523         /* Currently GNU Chess doesn't offer or accept Adjourns */
13524     }
13525 }
13526
13527
13528 void
13529 AbortEvent()
13530 {
13531     /* Offer Abort or accept pending Abort offer from opponent */
13532
13533     if (appData.icsActive) {
13534         SendToICS(ics_prefix);
13535         SendToICS("abort\n");
13536     } else {
13537         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13538     }
13539 }
13540
13541 void
13542 ResignEvent()
13543 {
13544     /* Resign.  You can do this even if it's not your turn. */
13545
13546     if (appData.icsActive) {
13547         SendToICS(ics_prefix);
13548         SendToICS("resign\n");
13549     } else {
13550         switch (gameMode) {
13551           case MachinePlaysWhite:
13552             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13553             break;
13554           case MachinePlaysBlack:
13555             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13556             break;
13557           case EditGame:
13558             if (cmailMsgLoaded) {
13559                 TruncateGame();
13560                 if (WhiteOnMove(cmailOldMove)) {
13561                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13562                 } else {
13563                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13564                 }
13565                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13566             }
13567             break;
13568           default:
13569             break;
13570         }
13571     }
13572 }
13573
13574
13575 void
13576 StopObservingEvent()
13577 {
13578     /* Stop observing current games */
13579     SendToICS(ics_prefix);
13580     SendToICS("unobserve\n");
13581 }
13582
13583 void
13584 StopExaminingEvent()
13585 {
13586     /* Stop observing current game */
13587     SendToICS(ics_prefix);
13588     SendToICS("unexamine\n");
13589 }
13590
13591 void
13592 ForwardInner(target)
13593      int target;
13594 {
13595     int limit;
13596
13597     if (appData.debugMode)
13598         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13599                 target, currentMove, forwardMostMove);
13600
13601     if (gameMode == EditPosition)
13602       return;
13603
13604     if (gameMode == PlayFromGameFile && !pausing)
13605       PauseEvent();
13606
13607     if (gameMode == IcsExamining && pausing)
13608       limit = pauseExamForwardMostMove;
13609     else
13610       limit = forwardMostMove;
13611
13612     if (target > limit) target = limit;
13613
13614     if (target > 0 && moveList[target - 1][0]) {
13615         int fromX, fromY, toX, toY;
13616         toX = moveList[target - 1][2] - AAA;
13617         toY = moveList[target - 1][3] - ONE;
13618         if (moveList[target - 1][1] == '@') {
13619             if (appData.highlightLastMove) {
13620                 SetHighlights(-1, -1, toX, toY);
13621             }
13622         } else {
13623             fromX = moveList[target - 1][0] - AAA;
13624             fromY = moveList[target - 1][1] - ONE;
13625             if (target == currentMove + 1) {
13626                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13627             }
13628             if (appData.highlightLastMove) {
13629                 SetHighlights(fromX, fromY, toX, toY);
13630             }
13631         }
13632     }
13633     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13634         gameMode == Training || gameMode == PlayFromGameFile ||
13635         gameMode == AnalyzeFile) {
13636         while (currentMove < target) {
13637             SendMoveToProgram(currentMove++, &first);
13638         }
13639     } else {
13640         currentMove = target;
13641     }
13642
13643     if (gameMode == EditGame || gameMode == EndOfGame) {
13644         whiteTimeRemaining = timeRemaining[0][currentMove];
13645         blackTimeRemaining = timeRemaining[1][currentMove];
13646     }
13647     DisplayBothClocks();
13648     DisplayMove(currentMove - 1);
13649     DrawPosition(FALSE, boards[currentMove]);
13650     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13651     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13652         DisplayComment(currentMove - 1, commentList[currentMove]);
13653     }
13654     DisplayBook(currentMove);
13655 }
13656
13657
13658 void
13659 ForwardEvent()
13660 {
13661     if (gameMode == IcsExamining && !pausing) {
13662         SendToICS(ics_prefix);
13663         SendToICS("forward\n");
13664     } else {
13665         ForwardInner(currentMove + 1);
13666     }
13667 }
13668
13669 void
13670 ToEndEvent()
13671 {
13672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13673         /* to optimze, we temporarily turn off analysis mode while we feed
13674          * the remaining moves to the engine. Otherwise we get analysis output
13675          * after each move.
13676          */
13677         if (first.analysisSupport) {
13678           SendToProgram("exit\nforce\n", &first);
13679           first.analyzing = FALSE;
13680         }
13681     }
13682
13683     if (gameMode == IcsExamining && !pausing) {
13684         SendToICS(ics_prefix);
13685         SendToICS("forward 999999\n");
13686     } else {
13687         ForwardInner(forwardMostMove);
13688     }
13689
13690     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13691         /* we have fed all the moves, so reactivate analysis mode */
13692         SendToProgram("analyze\n", &first);
13693         first.analyzing = TRUE;
13694         /*first.maybeThinking = TRUE;*/
13695         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13696     }
13697 }
13698
13699 void
13700 BackwardInner(target)
13701      int target;
13702 {
13703     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13704
13705     if (appData.debugMode)
13706         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13707                 target, currentMove, forwardMostMove);
13708
13709     if (gameMode == EditPosition) return;
13710     if (currentMove <= backwardMostMove) {
13711         ClearHighlights();
13712         DrawPosition(full_redraw, boards[currentMove]);
13713         return;
13714     }
13715     if (gameMode == PlayFromGameFile && !pausing)
13716       PauseEvent();
13717
13718     if (moveList[target][0]) {
13719         int fromX, fromY, toX, toY;
13720         toX = moveList[target][2] - AAA;
13721         toY = moveList[target][3] - ONE;
13722         if (moveList[target][1] == '@') {
13723             if (appData.highlightLastMove) {
13724                 SetHighlights(-1, -1, toX, toY);
13725             }
13726         } else {
13727             fromX = moveList[target][0] - AAA;
13728             fromY = moveList[target][1] - ONE;
13729             if (target == currentMove - 1) {
13730                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13731             }
13732             if (appData.highlightLastMove) {
13733                 SetHighlights(fromX, fromY, toX, toY);
13734             }
13735         }
13736     }
13737     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13738         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13739         while (currentMove > target) {
13740             SendToProgram("undo\n", &first);
13741             currentMove--;
13742         }
13743     } else {
13744         currentMove = target;
13745     }
13746
13747     if (gameMode == EditGame || gameMode == EndOfGame) {
13748         whiteTimeRemaining = timeRemaining[0][currentMove];
13749         blackTimeRemaining = timeRemaining[1][currentMove];
13750     }
13751     DisplayBothClocks();
13752     DisplayMove(currentMove - 1);
13753     DrawPosition(full_redraw, boards[currentMove]);
13754     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13755     // [HGM] PV info: routine tests if comment empty
13756     DisplayComment(currentMove - 1, commentList[currentMove]);
13757     DisplayBook(currentMove);
13758 }
13759
13760 void
13761 BackwardEvent()
13762 {
13763     if (gameMode == IcsExamining && !pausing) {
13764         SendToICS(ics_prefix);
13765         SendToICS("backward\n");
13766     } else {
13767         BackwardInner(currentMove - 1);
13768     }
13769 }
13770
13771 void
13772 ToStartEvent()
13773 {
13774     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13775         /* to optimize, we temporarily turn off analysis mode while we undo
13776          * all the moves. Otherwise we get analysis output after each undo.
13777          */
13778         if (first.analysisSupport) {
13779           SendToProgram("exit\nforce\n", &first);
13780           first.analyzing = FALSE;
13781         }
13782     }
13783
13784     if (gameMode == IcsExamining && !pausing) {
13785         SendToICS(ics_prefix);
13786         SendToICS("backward 999999\n");
13787     } else {
13788         BackwardInner(backwardMostMove);
13789     }
13790
13791     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13792         /* we have fed all the moves, so reactivate analysis mode */
13793         SendToProgram("analyze\n", &first);
13794         first.analyzing = TRUE;
13795         /*first.maybeThinking = TRUE;*/
13796         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13797     }
13798 }
13799
13800 void
13801 ToNrEvent(int to)
13802 {
13803   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13804   if (to >= forwardMostMove) to = forwardMostMove;
13805   if (to <= backwardMostMove) to = backwardMostMove;
13806   if (to < currentMove) {
13807     BackwardInner(to);
13808   } else {
13809     ForwardInner(to);
13810   }
13811 }
13812
13813 void
13814 RevertEvent(Boolean annotate)
13815 {
13816     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13817         return;
13818     }
13819     if (gameMode != IcsExamining) {
13820         DisplayError(_("You are not examining a game"), 0);
13821         return;
13822     }
13823     if (pausing) {
13824         DisplayError(_("You can't revert while pausing"), 0);
13825         return;
13826     }
13827     SendToICS(ics_prefix);
13828     SendToICS("revert\n");
13829 }
13830
13831 void
13832 RetractMoveEvent()
13833 {
13834     switch (gameMode) {
13835       case MachinePlaysWhite:
13836       case MachinePlaysBlack:
13837         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13838             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13839             return;
13840         }
13841         if (forwardMostMove < 2) return;
13842         currentMove = forwardMostMove = forwardMostMove - 2;
13843         whiteTimeRemaining = timeRemaining[0][currentMove];
13844         blackTimeRemaining = timeRemaining[1][currentMove];
13845         DisplayBothClocks();
13846         DisplayMove(currentMove - 1);
13847         ClearHighlights();/*!! could figure this out*/
13848         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13849         SendToProgram("remove\n", &first);
13850         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13851         break;
13852
13853       case BeginningOfGame:
13854       default:
13855         break;
13856
13857       case IcsPlayingWhite:
13858       case IcsPlayingBlack:
13859         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13860             SendToICS(ics_prefix);
13861             SendToICS("takeback 2\n");
13862         } else {
13863             SendToICS(ics_prefix);
13864             SendToICS("takeback 1\n");
13865         }
13866         break;
13867     }
13868 }
13869
13870 void
13871 MoveNowEvent()
13872 {
13873     ChessProgramState *cps;
13874
13875     switch (gameMode) {
13876       case MachinePlaysWhite:
13877         if (!WhiteOnMove(forwardMostMove)) {
13878             DisplayError(_("It is your turn"), 0);
13879             return;
13880         }
13881         cps = &first;
13882         break;
13883       case MachinePlaysBlack:
13884         if (WhiteOnMove(forwardMostMove)) {
13885             DisplayError(_("It is your turn"), 0);
13886             return;
13887         }
13888         cps = &first;
13889         break;
13890       case TwoMachinesPlay:
13891         if (WhiteOnMove(forwardMostMove) ==
13892             (first.twoMachinesColor[0] == 'w')) {
13893             cps = &first;
13894         } else {
13895             cps = &second;
13896         }
13897         break;
13898       case BeginningOfGame:
13899       default:
13900         return;
13901     }
13902     SendToProgram("?\n", cps);
13903 }
13904
13905 void
13906 TruncateGameEvent()
13907 {
13908     EditGameEvent();
13909     if (gameMode != EditGame) return;
13910     TruncateGame();
13911 }
13912
13913 void
13914 TruncateGame()
13915 {
13916     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13917     if (forwardMostMove > currentMove) {
13918         if (gameInfo.resultDetails != NULL) {
13919             free(gameInfo.resultDetails);
13920             gameInfo.resultDetails = NULL;
13921             gameInfo.result = GameUnfinished;
13922         }
13923         forwardMostMove = currentMove;
13924         HistorySet(parseList, backwardMostMove, forwardMostMove,
13925                    currentMove-1);
13926     }
13927 }
13928
13929 void
13930 HintEvent()
13931 {
13932     if (appData.noChessProgram) return;
13933     switch (gameMode) {
13934       case MachinePlaysWhite:
13935         if (WhiteOnMove(forwardMostMove)) {
13936             DisplayError(_("Wait until your turn"), 0);
13937             return;
13938         }
13939         break;
13940       case BeginningOfGame:
13941       case MachinePlaysBlack:
13942         if (!WhiteOnMove(forwardMostMove)) {
13943             DisplayError(_("Wait until your turn"), 0);
13944             return;
13945         }
13946         break;
13947       default:
13948         DisplayError(_("No hint available"), 0);
13949         return;
13950     }
13951     SendToProgram("hint\n", &first);
13952     hintRequested = TRUE;
13953 }
13954
13955 void
13956 BookEvent()
13957 {
13958     if (appData.noChessProgram) return;
13959     switch (gameMode) {
13960       case MachinePlaysWhite:
13961         if (WhiteOnMove(forwardMostMove)) {
13962             DisplayError(_("Wait until your turn"), 0);
13963             return;
13964         }
13965         break;
13966       case BeginningOfGame:
13967       case MachinePlaysBlack:
13968         if (!WhiteOnMove(forwardMostMove)) {
13969             DisplayError(_("Wait until your turn"), 0);
13970             return;
13971         }
13972         break;
13973       case EditPosition:
13974         EditPositionDone(TRUE);
13975         break;
13976       case TwoMachinesPlay:
13977         return;
13978       default:
13979         break;
13980     }
13981     SendToProgram("bk\n", &first);
13982     bookOutput[0] = NULLCHAR;
13983     bookRequested = TRUE;
13984 }
13985
13986 void
13987 AboutGameEvent()
13988 {
13989     char *tags = PGNTags(&gameInfo);
13990     TagsPopUp(tags, CmailMsg());
13991     free(tags);
13992 }
13993
13994 /* end button procedures */
13995
13996 void
13997 PrintPosition(fp, move)
13998      FILE *fp;
13999      int move;
14000 {
14001     int i, j;
14002
14003     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14004         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14005             char c = PieceToChar(boards[move][i][j]);
14006             fputc(c == 'x' ? '.' : c, fp);
14007             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14008         }
14009     }
14010     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14011       fprintf(fp, "white to play\n");
14012     else
14013       fprintf(fp, "black to play\n");
14014 }
14015
14016 void
14017 PrintOpponents(fp)
14018      FILE *fp;
14019 {
14020     if (gameInfo.white != NULL) {
14021         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14022     } else {
14023         fprintf(fp, "\n");
14024     }
14025 }
14026
14027 /* Find last component of program's own name, using some heuristics */
14028 void
14029 TidyProgramName(prog, host, buf)
14030      char *prog, *host, buf[MSG_SIZ];
14031 {
14032     char *p, *q;
14033     int local = (strcmp(host, "localhost") == 0);
14034     while (!local && (p = strchr(prog, ';')) != NULL) {
14035         p++;
14036         while (*p == ' ') p++;
14037         prog = p;
14038     }
14039     if (*prog == '"' || *prog == '\'') {
14040         q = strchr(prog + 1, *prog);
14041     } else {
14042         q = strchr(prog, ' ');
14043     }
14044     if (q == NULL) q = prog + strlen(prog);
14045     p = q;
14046     while (p >= prog && *p != '/' && *p != '\\') p--;
14047     p++;
14048     if(p == prog && *p == '"') p++;
14049     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14050     memcpy(buf, p, q - p);
14051     buf[q - p] = NULLCHAR;
14052     if (!local) {
14053         strcat(buf, "@");
14054         strcat(buf, host);
14055     }
14056 }
14057
14058 char *
14059 TimeControlTagValue()
14060 {
14061     char buf[MSG_SIZ];
14062     if (!appData.clockMode) {
14063       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14064     } else if (movesPerSession > 0) {
14065       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14066     } else if (timeIncrement == 0) {
14067       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14068     } else {
14069       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14070     }
14071     return StrSave(buf);
14072 }
14073
14074 void
14075 SetGameInfo()
14076 {
14077     /* This routine is used only for certain modes */
14078     VariantClass v = gameInfo.variant;
14079     ChessMove r = GameUnfinished;
14080     char *p = NULL;
14081
14082     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14083         r = gameInfo.result;
14084         p = gameInfo.resultDetails;
14085         gameInfo.resultDetails = NULL;
14086     }
14087     ClearGameInfo(&gameInfo);
14088     gameInfo.variant = v;
14089
14090     switch (gameMode) {
14091       case MachinePlaysWhite:
14092         gameInfo.event = StrSave( appData.pgnEventHeader );
14093         gameInfo.site = StrSave(HostName());
14094         gameInfo.date = PGNDate();
14095         gameInfo.round = StrSave("-");
14096         gameInfo.white = StrSave(first.tidy);
14097         gameInfo.black = StrSave(UserName());
14098         gameInfo.timeControl = TimeControlTagValue();
14099         break;
14100
14101       case MachinePlaysBlack:
14102         gameInfo.event = StrSave( appData.pgnEventHeader );
14103         gameInfo.site = StrSave(HostName());
14104         gameInfo.date = PGNDate();
14105         gameInfo.round = StrSave("-");
14106         gameInfo.white = StrSave(UserName());
14107         gameInfo.black = StrSave(first.tidy);
14108         gameInfo.timeControl = TimeControlTagValue();
14109         break;
14110
14111       case TwoMachinesPlay:
14112         gameInfo.event = StrSave( appData.pgnEventHeader );
14113         gameInfo.site = StrSave(HostName());
14114         gameInfo.date = PGNDate();
14115         if (roundNr > 0) {
14116             char buf[MSG_SIZ];
14117             snprintf(buf, MSG_SIZ, "%d", roundNr);
14118             gameInfo.round = StrSave(buf);
14119         } else {
14120             gameInfo.round = StrSave("-");
14121         }
14122         if (first.twoMachinesColor[0] == 'w') {
14123             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14124             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14125         } else {
14126             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14127             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14128         }
14129         gameInfo.timeControl = TimeControlTagValue();
14130         break;
14131
14132       case EditGame:
14133         gameInfo.event = StrSave("Edited game");
14134         gameInfo.site = StrSave(HostName());
14135         gameInfo.date = PGNDate();
14136         gameInfo.round = StrSave("-");
14137         gameInfo.white = StrSave("-");
14138         gameInfo.black = StrSave("-");
14139         gameInfo.result = r;
14140         gameInfo.resultDetails = p;
14141         break;
14142
14143       case EditPosition:
14144         gameInfo.event = StrSave("Edited position");
14145         gameInfo.site = StrSave(HostName());
14146         gameInfo.date = PGNDate();
14147         gameInfo.round = StrSave("-");
14148         gameInfo.white = StrSave("-");
14149         gameInfo.black = StrSave("-");
14150         break;
14151
14152       case IcsPlayingWhite:
14153       case IcsPlayingBlack:
14154       case IcsObserving:
14155       case IcsExamining:
14156         break;
14157
14158       case PlayFromGameFile:
14159         gameInfo.event = StrSave("Game from non-PGN file");
14160         gameInfo.site = StrSave(HostName());
14161         gameInfo.date = PGNDate();
14162         gameInfo.round = StrSave("-");
14163         gameInfo.white = StrSave("?");
14164         gameInfo.black = StrSave("?");
14165         break;
14166
14167       default:
14168         break;
14169     }
14170 }
14171
14172 void
14173 ReplaceComment(index, text)
14174      int index;
14175      char *text;
14176 {
14177     int len;
14178     char *p;
14179     float score;
14180
14181     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14182        pvInfoList[index-1].depth == len &&
14183        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14184        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14185     while (*text == '\n') text++;
14186     len = strlen(text);
14187     while (len > 0 && text[len - 1] == '\n') len--;
14188
14189     if (commentList[index] != NULL)
14190       free(commentList[index]);
14191
14192     if (len == 0) {
14193         commentList[index] = NULL;
14194         return;
14195     }
14196   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14197       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14198       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14199     commentList[index] = (char *) malloc(len + 2);
14200     strncpy(commentList[index], text, len);
14201     commentList[index][len] = '\n';
14202     commentList[index][len + 1] = NULLCHAR;
14203   } else {
14204     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14205     char *p;
14206     commentList[index] = (char *) malloc(len + 7);
14207     safeStrCpy(commentList[index], "{\n", 3);
14208     safeStrCpy(commentList[index]+2, text, len+1);
14209     commentList[index][len+2] = NULLCHAR;
14210     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14211     strcat(commentList[index], "\n}\n");
14212   }
14213 }
14214
14215 void
14216 CrushCRs(text)
14217      char *text;
14218 {
14219   char *p = text;
14220   char *q = text;
14221   char ch;
14222
14223   do {
14224     ch = *p++;
14225     if (ch == '\r') continue;
14226     *q++ = ch;
14227   } while (ch != '\0');
14228 }
14229
14230 void
14231 AppendComment(index, text, addBraces)
14232      int index;
14233      char *text;
14234      Boolean addBraces; // [HGM] braces: tells if we should add {}
14235 {
14236     int oldlen, len;
14237     char *old;
14238
14239 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14240     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14241
14242     CrushCRs(text);
14243     while (*text == '\n') text++;
14244     len = strlen(text);
14245     while (len > 0 && text[len - 1] == '\n') len--;
14246
14247     if (len == 0) return;
14248
14249     if (commentList[index] != NULL) {
14250         old = commentList[index];
14251         oldlen = strlen(old);
14252         while(commentList[index][oldlen-1] ==  '\n')
14253           commentList[index][--oldlen] = NULLCHAR;
14254         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14255         safeStrCpy(commentList[index], old, oldlen + len + 6);
14256         free(old);
14257         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14258         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14259           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14260           while (*text == '\n') { text++; len--; }
14261           commentList[index][--oldlen] = NULLCHAR;
14262       }
14263         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14264         else          strcat(commentList[index], "\n");
14265         strcat(commentList[index], text);
14266         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14267         else          strcat(commentList[index], "\n");
14268     } else {
14269         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14270         if(addBraces)
14271           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14272         else commentList[index][0] = NULLCHAR;
14273         strcat(commentList[index], text);
14274         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14275         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14276     }
14277 }
14278
14279 static char * FindStr( char * text, char * sub_text )
14280 {
14281     char * result = strstr( text, sub_text );
14282
14283     if( result != NULL ) {
14284         result += strlen( sub_text );
14285     }
14286
14287     return result;
14288 }
14289
14290 /* [AS] Try to extract PV info from PGN comment */
14291 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14292 char *GetInfoFromComment( int index, char * text )
14293 {
14294     char * sep = text, *p;
14295
14296     if( text != NULL && index > 0 ) {
14297         int score = 0;
14298         int depth = 0;
14299         int time = -1, sec = 0, deci;
14300         char * s_eval = FindStr( text, "[%eval " );
14301         char * s_emt = FindStr( text, "[%emt " );
14302
14303         if( s_eval != NULL || s_emt != NULL ) {
14304             /* New style */
14305             char delim;
14306
14307             if( s_eval != NULL ) {
14308                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14309                     return text;
14310                 }
14311
14312                 if( delim != ']' ) {
14313                     return text;
14314                 }
14315             }
14316
14317             if( s_emt != NULL ) {
14318             }
14319                 return text;
14320         }
14321         else {
14322             /* We expect something like: [+|-]nnn.nn/dd */
14323             int score_lo = 0;
14324
14325             if(*text != '{') return text; // [HGM] braces: must be normal comment
14326
14327             sep = strchr( text, '/' );
14328             if( sep == NULL || sep < (text+4) ) {
14329                 return text;
14330             }
14331
14332             p = text;
14333             if(p[1] == '(') { // comment starts with PV
14334                p = strchr(p, ')'); // locate end of PV
14335                if(p == NULL || sep < p+5) return text;
14336                // at this point we have something like "{(.*) +0.23/6 ..."
14337                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14338                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14339                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14340             }
14341             time = -1; sec = -1; deci = -1;
14342             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14343                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14344                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14345                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14346                 return text;
14347             }
14348
14349             if( score_lo < 0 || score_lo >= 100 ) {
14350                 return text;
14351             }
14352
14353             if(sec >= 0) time = 600*time + 10*sec; else
14354             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14355
14356             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14357
14358             /* [HGM] PV time: now locate end of PV info */
14359             while( *++sep >= '0' && *sep <= '9'); // strip depth
14360             if(time >= 0)
14361             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14362             if(sec >= 0)
14363             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14364             if(deci >= 0)
14365             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14366             while(*sep == ' ') sep++;
14367         }
14368
14369         if( depth <= 0 ) {
14370             return text;
14371         }
14372
14373         if( time < 0 ) {
14374             time = -1;
14375         }
14376
14377         pvInfoList[index-1].depth = depth;
14378         pvInfoList[index-1].score = score;
14379         pvInfoList[index-1].time  = 10*time; // centi-sec
14380         if(*sep == '}') *sep = 0; else *--sep = '{';
14381         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14382     }
14383     return sep;
14384 }
14385
14386 void
14387 SendToProgram(message, cps)
14388      char *message;
14389      ChessProgramState *cps;
14390 {
14391     int count, outCount, error;
14392     char buf[MSG_SIZ];
14393
14394     if (cps->pr == NULL) return;
14395     Attention(cps);
14396
14397     if (appData.debugMode) {
14398         TimeMark now;
14399         GetTimeMark(&now);
14400         fprintf(debugFP, "%ld >%-6s: %s",
14401                 SubtractTimeMarks(&now, &programStartTime),
14402                 cps->which, message);
14403     }
14404
14405     count = strlen(message);
14406     outCount = OutputToProcess(cps->pr, message, count, &error);
14407     if (outCount < count && !exiting
14408                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14409       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14410       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14411         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14412             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14413                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14414                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14415                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14416             } else {
14417                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14418                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14419                 gameInfo.result = res;
14420             }
14421             gameInfo.resultDetails = StrSave(buf);
14422         }
14423         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14424         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14425     }
14426 }
14427
14428 void
14429 ReceiveFromProgram(isr, closure, message, count, error)
14430      InputSourceRef isr;
14431      VOIDSTAR closure;
14432      char *message;
14433      int count;
14434      int error;
14435 {
14436     char *end_str;
14437     char buf[MSG_SIZ];
14438     ChessProgramState *cps = (ChessProgramState *)closure;
14439
14440     if (isr != cps->isr) return; /* Killed intentionally */
14441     if (count <= 0) {
14442         if (count == 0) {
14443             RemoveInputSource(cps->isr);
14444             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14445             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14446                     _(cps->which), cps->program);
14447         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14448                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14449                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14450                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14451                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14452                 } else {
14453                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14454                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14455                     gameInfo.result = res;
14456                 }
14457                 gameInfo.resultDetails = StrSave(buf);
14458             }
14459             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14460             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14461         } else {
14462             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14463                     _(cps->which), cps->program);
14464             RemoveInputSource(cps->isr);
14465
14466             /* [AS] Program is misbehaving badly... kill it */
14467             if( count == -2 ) {
14468                 DestroyChildProcess( cps->pr, 9 );
14469                 cps->pr = NoProc;
14470             }
14471
14472             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14473         }
14474         return;
14475     }
14476
14477     if ((end_str = strchr(message, '\r')) != NULL)
14478       *end_str = NULLCHAR;
14479     if ((end_str = strchr(message, '\n')) != NULL)
14480       *end_str = NULLCHAR;
14481
14482     if (appData.debugMode) {
14483         TimeMark now; int print = 1;
14484         char *quote = ""; char c; int i;
14485
14486         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14487                 char start = message[0];
14488                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14489                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14490                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14491                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14492                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14493                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14494                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14495                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14496                    sscanf(message, "hint: %c", &c)!=1 && 
14497                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14498                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14499                     print = (appData.engineComments >= 2);
14500                 }
14501                 message[0] = start; // restore original message
14502         }
14503         if(print) {
14504                 GetTimeMark(&now);
14505                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14506                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14507                         quote,
14508                         message);
14509         }
14510     }
14511
14512     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14513     if (appData.icsEngineAnalyze) {
14514         if (strstr(message, "whisper") != NULL ||
14515              strstr(message, "kibitz") != NULL ||
14516             strstr(message, "tellics") != NULL) return;
14517     }
14518
14519     HandleMachineMove(message, cps);
14520 }
14521
14522
14523 void
14524 SendTimeControl(cps, mps, tc, inc, sd, st)
14525      ChessProgramState *cps;
14526      int mps, inc, sd, st;
14527      long tc;
14528 {
14529     char buf[MSG_SIZ];
14530     int seconds;
14531
14532     if( timeControl_2 > 0 ) {
14533         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14534             tc = timeControl_2;
14535         }
14536     }
14537     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14538     inc /= cps->timeOdds;
14539     st  /= cps->timeOdds;
14540
14541     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14542
14543     if (st > 0) {
14544       /* Set exact time per move, normally using st command */
14545       if (cps->stKludge) {
14546         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14547         seconds = st % 60;
14548         if (seconds == 0) {
14549           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14550         } else {
14551           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14552         }
14553       } else {
14554         snprintf(buf, MSG_SIZ, "st %d\n", st);
14555       }
14556     } else {
14557       /* Set conventional or incremental time control, using level command */
14558       if (seconds == 0) {
14559         /* Note old gnuchess bug -- minutes:seconds used to not work.
14560            Fixed in later versions, but still avoid :seconds
14561            when seconds is 0. */
14562         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14563       } else {
14564         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14565                  seconds, inc/1000.);
14566       }
14567     }
14568     SendToProgram(buf, cps);
14569
14570     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14571     /* Orthogonally, limit search to given depth */
14572     if (sd > 0) {
14573       if (cps->sdKludge) {
14574         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14575       } else {
14576         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14577       }
14578       SendToProgram(buf, cps);
14579     }
14580
14581     if(cps->nps >= 0) { /* [HGM] nps */
14582         if(cps->supportsNPS == FALSE)
14583           cps->nps = -1; // don't use if engine explicitly says not supported!
14584         else {
14585           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14586           SendToProgram(buf, cps);
14587         }
14588     }
14589 }
14590
14591 ChessProgramState *WhitePlayer()
14592 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14593 {
14594     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14595        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14596         return &second;
14597     return &first;
14598 }
14599
14600 void
14601 SendTimeRemaining(cps, machineWhite)
14602      ChessProgramState *cps;
14603      int /*boolean*/ machineWhite;
14604 {
14605     char message[MSG_SIZ];
14606     long time, otime;
14607
14608     /* Note: this routine must be called when the clocks are stopped
14609        or when they have *just* been set or switched; otherwise
14610        it will be off by the time since the current tick started.
14611     */
14612     if (machineWhite) {
14613         time = whiteTimeRemaining / 10;
14614         otime = blackTimeRemaining / 10;
14615     } else {
14616         time = blackTimeRemaining / 10;
14617         otime = whiteTimeRemaining / 10;
14618     }
14619     /* [HGM] translate opponent's time by time-odds factor */
14620     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14621     if (appData.debugMode) {
14622         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14623     }
14624
14625     if (time <= 0) time = 1;
14626     if (otime <= 0) otime = 1;
14627
14628     snprintf(message, MSG_SIZ, "time %ld\n", time);
14629     SendToProgram(message, cps);
14630
14631     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14632     SendToProgram(message, cps);
14633 }
14634
14635 int
14636 BoolFeature(p, name, loc, cps)
14637      char **p;
14638      char *name;
14639      int *loc;
14640      ChessProgramState *cps;
14641 {
14642   char buf[MSG_SIZ];
14643   int len = strlen(name);
14644   int val;
14645
14646   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14647     (*p) += len + 1;
14648     sscanf(*p, "%d", &val);
14649     *loc = (val != 0);
14650     while (**p && **p != ' ')
14651       (*p)++;
14652     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14653     SendToProgram(buf, cps);
14654     return TRUE;
14655   }
14656   return FALSE;
14657 }
14658
14659 int
14660 IntFeature(p, name, loc, cps)
14661      char **p;
14662      char *name;
14663      int *loc;
14664      ChessProgramState *cps;
14665 {
14666   char buf[MSG_SIZ];
14667   int len = strlen(name);
14668   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14669     (*p) += len + 1;
14670     sscanf(*p, "%d", loc);
14671     while (**p && **p != ' ') (*p)++;
14672     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14673     SendToProgram(buf, cps);
14674     return TRUE;
14675   }
14676   return FALSE;
14677 }
14678
14679 int
14680 StringFeature(p, name, loc, cps)
14681      char **p;
14682      char *name;
14683      char loc[];
14684      ChessProgramState *cps;
14685 {
14686   char buf[MSG_SIZ];
14687   int len = strlen(name);
14688   if (strncmp((*p), name, len) == 0
14689       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14690     (*p) += len + 2;
14691     sscanf(*p, "%[^\"]", loc);
14692     while (**p && **p != '\"') (*p)++;
14693     if (**p == '\"') (*p)++;
14694     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14695     SendToProgram(buf, cps);
14696     return TRUE;
14697   }
14698   return FALSE;
14699 }
14700
14701 int
14702 ParseOption(Option *opt, ChessProgramState *cps)
14703 // [HGM] options: process the string that defines an engine option, and determine
14704 // name, type, default value, and allowed value range
14705 {
14706         char *p, *q, buf[MSG_SIZ];
14707         int n, min = (-1)<<31, max = 1<<31, def;
14708
14709         if(p = strstr(opt->name, " -spin ")) {
14710             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14711             if(max < min) max = min; // enforce consistency
14712             if(def < min) def = min;
14713             if(def > max) def = max;
14714             opt->value = def;
14715             opt->min = min;
14716             opt->max = max;
14717             opt->type = Spin;
14718         } else if((p = strstr(opt->name, " -slider "))) {
14719             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14720             if((n = sscanf(p, " -slider %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; // Slider;
14728         } else if((p = strstr(opt->name, " -string "))) {
14729             opt->textValue = p+9;
14730             opt->type = TextBox;
14731         } else if((p = strstr(opt->name, " -file "))) {
14732             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14733             opt->textValue = p+7;
14734             opt->type = FileName; // FileName;
14735         } else if((p = strstr(opt->name, " -path "))) {
14736             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14737             opt->textValue = p+7;
14738             opt->type = PathName; // PathName;
14739         } else if(p = strstr(opt->name, " -check ")) {
14740             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14741             opt->value = (def != 0);
14742             opt->type = CheckBox;
14743         } else if(p = strstr(opt->name, " -combo ")) {
14744             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14745             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14746             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14747             opt->value = n = 0;
14748             while(q = StrStr(q, " /// ")) {
14749                 n++; *q = 0;    // count choices, and null-terminate each of them
14750                 q += 5;
14751                 if(*q == '*') { // remember default, which is marked with * prefix
14752                     q++;
14753                     opt->value = n;
14754                 }
14755                 cps->comboList[cps->comboCnt++] = q;
14756             }
14757             cps->comboList[cps->comboCnt++] = NULL;
14758             opt->max = n + 1;
14759             opt->type = ComboBox;
14760         } else if(p = strstr(opt->name, " -button")) {
14761             opt->type = Button;
14762         } else if(p = strstr(opt->name, " -save")) {
14763             opt->type = SaveButton;
14764         } else return FALSE;
14765         *p = 0; // terminate option name
14766         // now look if the command-line options define a setting for this engine option.
14767         if(cps->optionSettings && cps->optionSettings[0])
14768             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14769         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14770           snprintf(buf, MSG_SIZ, "option %s", p);
14771                 if(p = strstr(buf, ",")) *p = 0;
14772                 if(q = strchr(buf, '=')) switch(opt->type) {
14773                     case ComboBox:
14774                         for(n=0; n<opt->max; n++)
14775                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14776                         break;
14777                     case TextBox:
14778                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14779                         break;
14780                     case Spin:
14781                     case CheckBox:
14782                         opt->value = atoi(q+1);
14783                     default:
14784                         break;
14785                 }
14786                 strcat(buf, "\n");
14787                 SendToProgram(buf, cps);
14788         }
14789         return TRUE;
14790 }
14791
14792 void
14793 FeatureDone(cps, val)
14794      ChessProgramState* cps;
14795      int val;
14796 {
14797   DelayedEventCallback cb = GetDelayedEvent();
14798   if ((cb == InitBackEnd3 && cps == &first) ||
14799       (cb == SettingsMenuIfReady && cps == &second) ||
14800       (cb == LoadEngine) ||
14801       (cb == TwoMachinesEventIfReady)) {
14802     CancelDelayedEvent();
14803     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14804   }
14805   cps->initDone = val;
14806 }
14807
14808 /* Parse feature command from engine */
14809 void
14810 ParseFeatures(args, cps)
14811      char* args;
14812      ChessProgramState *cps;
14813 {
14814   char *p = args;
14815   char *q;
14816   int val;
14817   char buf[MSG_SIZ];
14818
14819   for (;;) {
14820     while (*p == ' ') p++;
14821     if (*p == NULLCHAR) return;
14822
14823     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14824     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14825     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14826     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14827     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14828     if (BoolFeature(&p, "reuse", &val, cps)) {
14829       /* Engine can disable reuse, but can't enable it if user said no */
14830       if (!val) cps->reuse = FALSE;
14831       continue;
14832     }
14833     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14834     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14835       if (gameMode == TwoMachinesPlay) {
14836         DisplayTwoMachinesTitle();
14837       } else {
14838         DisplayTitle("");
14839       }
14840       continue;
14841     }
14842     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14843     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14844     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14845     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14846     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14847     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14848     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14849     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14850     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14851     if (IntFeature(&p, "done", &val, cps)) {
14852       FeatureDone(cps, val);
14853       continue;
14854     }
14855     /* Added by Tord: */
14856     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14857     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14858     /* End of additions by Tord */
14859
14860     /* [HGM] added features: */
14861     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14862     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14863     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14864     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14865     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14866     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14867     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14868         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14869           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14870             SendToProgram(buf, cps);
14871             continue;
14872         }
14873         if(cps->nrOptions >= MAX_OPTIONS) {
14874             cps->nrOptions--;
14875             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14876             DisplayError(buf, 0);
14877         }
14878         continue;
14879     }
14880     /* End of additions by HGM */
14881
14882     /* unknown feature: complain and skip */
14883     q = p;
14884     while (*q && *q != '=') q++;
14885     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14886     SendToProgram(buf, cps);
14887     p = q;
14888     if (*p == '=') {
14889       p++;
14890       if (*p == '\"') {
14891         p++;
14892         while (*p && *p != '\"') p++;
14893         if (*p == '\"') p++;
14894       } else {
14895         while (*p && *p != ' ') p++;
14896       }
14897     }
14898   }
14899
14900 }
14901
14902 void
14903 PeriodicUpdatesEvent(newState)
14904      int newState;
14905 {
14906     if (newState == appData.periodicUpdates)
14907       return;
14908
14909     appData.periodicUpdates=newState;
14910
14911     /* Display type changes, so update it now */
14912 //    DisplayAnalysis();
14913
14914     /* Get the ball rolling again... */
14915     if (newState) {
14916         AnalysisPeriodicEvent(1);
14917         StartAnalysisClock();
14918     }
14919 }
14920
14921 void
14922 PonderNextMoveEvent(newState)
14923      int newState;
14924 {
14925     if (newState == appData.ponderNextMove) return;
14926     if (gameMode == EditPosition) EditPositionDone(TRUE);
14927     if (newState) {
14928         SendToProgram("hard\n", &first);
14929         if (gameMode == TwoMachinesPlay) {
14930             SendToProgram("hard\n", &second);
14931         }
14932     } else {
14933         SendToProgram("easy\n", &first);
14934         thinkOutput[0] = NULLCHAR;
14935         if (gameMode == TwoMachinesPlay) {
14936             SendToProgram("easy\n", &second);
14937         }
14938     }
14939     appData.ponderNextMove = newState;
14940 }
14941
14942 void
14943 NewSettingEvent(option, feature, command, value)
14944      char *command;
14945      int option, value, *feature;
14946 {
14947     char buf[MSG_SIZ];
14948
14949     if (gameMode == EditPosition) EditPositionDone(TRUE);
14950     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14951     if(feature == NULL || *feature) SendToProgram(buf, &first);
14952     if (gameMode == TwoMachinesPlay) {
14953         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14954     }
14955 }
14956
14957 void
14958 ShowThinkingEvent()
14959 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14960 {
14961     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14962     int newState = appData.showThinking
14963         // [HGM] thinking: other features now need thinking output as well
14964         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14965
14966     if (oldState == newState) return;
14967     oldState = newState;
14968     if (gameMode == EditPosition) EditPositionDone(TRUE);
14969     if (oldState) {
14970         SendToProgram("post\n", &first);
14971         if (gameMode == TwoMachinesPlay) {
14972             SendToProgram("post\n", &second);
14973         }
14974     } else {
14975         SendToProgram("nopost\n", &first);
14976         thinkOutput[0] = NULLCHAR;
14977         if (gameMode == TwoMachinesPlay) {
14978             SendToProgram("nopost\n", &second);
14979         }
14980     }
14981 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14982 }
14983
14984 void
14985 AskQuestionEvent(title, question, replyPrefix, which)
14986      char *title; char *question; char *replyPrefix; char *which;
14987 {
14988   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14989   if (pr == NoProc) return;
14990   AskQuestion(title, question, replyPrefix, pr);
14991 }
14992
14993 void
14994 TypeInEvent(char firstChar)
14995 {
14996     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14997         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14998         gameMode == AnalyzeMode || gameMode == EditGame || \r
14999         gameMode == EditPosition || gameMode == IcsExamining ||\r
15000         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15001         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15002                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15003                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15004         gameMode == Training) PopUpMoveDialog(firstChar);
15005 }
15006
15007 void
15008 TypeInDoneEvent(char *move)
15009 {
15010         Board board;
15011         int n, fromX, fromY, toX, toY;
15012         char promoChar;
15013         ChessMove moveType;\r
15014
15015         // [HGM] FENedit\r
15016         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15017                 EditPositionPasteFEN(move);\r
15018                 return;\r
15019         }\r
15020         // [HGM] movenum: allow move number to be typed in any mode\r
15021         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15022           ToNrEvent(2*n-1);\r
15023           return;\r
15024         }\r
15025
15026       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15027         gameMode != Training) {\r
15028         DisplayMoveError(_("Displayed move is not current"));\r
15029       } else {\r
15030         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15031           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15032         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15033         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15034           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15035           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15036         } else {\r
15037           DisplayMoveError(_("Could not parse move"));\r
15038         }
15039       }\r
15040 }\r
15041
15042 void
15043 DisplayMove(moveNumber)
15044      int moveNumber;
15045 {
15046     char message[MSG_SIZ];
15047     char res[MSG_SIZ];
15048     char cpThinkOutput[MSG_SIZ];
15049
15050     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15051
15052     if (moveNumber == forwardMostMove - 1 ||
15053         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15054
15055         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15056
15057         if (strchr(cpThinkOutput, '\n')) {
15058             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15059         }
15060     } else {
15061         *cpThinkOutput = NULLCHAR;
15062     }
15063
15064     /* [AS] Hide thinking from human user */
15065     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15066         *cpThinkOutput = NULLCHAR;
15067         if( thinkOutput[0] != NULLCHAR ) {
15068             int i;
15069
15070             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15071                 cpThinkOutput[i] = '.';
15072             }
15073             cpThinkOutput[i] = NULLCHAR;
15074             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15075         }
15076     }
15077
15078     if (moveNumber == forwardMostMove - 1 &&
15079         gameInfo.resultDetails != NULL) {
15080         if (gameInfo.resultDetails[0] == NULLCHAR) {
15081           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15082         } else {
15083           snprintf(res, MSG_SIZ, " {%s} %s",
15084                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15085         }
15086     } else {
15087         res[0] = NULLCHAR;
15088     }
15089
15090     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15091         DisplayMessage(res, cpThinkOutput);
15092     } else {
15093       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15094                 WhiteOnMove(moveNumber) ? " " : ".. ",
15095                 parseList[moveNumber], res);
15096         DisplayMessage(message, cpThinkOutput);
15097     }
15098 }
15099
15100 void
15101 DisplayComment(moveNumber, text)
15102      int moveNumber;
15103      char *text;
15104 {
15105     char title[MSG_SIZ];
15106     char buf[8000]; // comment can be long!
15107     int score, depth;
15108
15109     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15110       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15111     } else {
15112       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15113               WhiteOnMove(moveNumber) ? " " : ".. ",
15114               parseList[moveNumber]);
15115     }
15116     // [HGM] PV info: display PV info together with (or as) comment
15117     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15118       if(text == NULL) text = "";
15119       score = pvInfoList[moveNumber].score;
15120       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15121               depth, (pvInfoList[moveNumber].time+50)/100, text);
15122       text = buf;
15123     }
15124     if (text != NULL && (appData.autoDisplayComment || commentUp))
15125         CommentPopUp(title, text);
15126 }
15127
15128 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15129  * might be busy thinking or pondering.  It can be omitted if your
15130  * gnuchess is configured to stop thinking immediately on any user
15131  * input.  However, that gnuchess feature depends on the FIONREAD
15132  * ioctl, which does not work properly on some flavors of Unix.
15133  */
15134 void
15135 Attention(cps)
15136      ChessProgramState *cps;
15137 {
15138 #if ATTENTION
15139     if (!cps->useSigint) return;
15140     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15141     switch (gameMode) {
15142       case MachinePlaysWhite:
15143       case MachinePlaysBlack:
15144       case TwoMachinesPlay:
15145       case IcsPlayingWhite:
15146       case IcsPlayingBlack:
15147       case AnalyzeMode:
15148       case AnalyzeFile:
15149         /* Skip if we know it isn't thinking */
15150         if (!cps->maybeThinking) return;
15151         if (appData.debugMode)
15152           fprintf(debugFP, "Interrupting %s\n", cps->which);
15153         InterruptChildProcess(cps->pr);
15154         cps->maybeThinking = FALSE;
15155         break;
15156       default:
15157         break;
15158     }
15159 #endif /*ATTENTION*/
15160 }
15161
15162 int
15163 CheckFlags()
15164 {
15165     if (whiteTimeRemaining <= 0) {
15166         if (!whiteFlag) {
15167             whiteFlag = TRUE;
15168             if (appData.icsActive) {
15169                 if (appData.autoCallFlag &&
15170                     gameMode == IcsPlayingBlack && !blackFlag) {
15171                   SendToICS(ics_prefix);
15172                   SendToICS("flag\n");
15173                 }
15174             } else {
15175                 if (blackFlag) {
15176                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15177                 } else {
15178                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15179                     if (appData.autoCallFlag) {
15180                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15181                         return TRUE;
15182                     }
15183                 }
15184             }
15185         }
15186     }
15187     if (blackTimeRemaining <= 0) {
15188         if (!blackFlag) {
15189             blackFlag = TRUE;
15190             if (appData.icsActive) {
15191                 if (appData.autoCallFlag &&
15192                     gameMode == IcsPlayingWhite && !whiteFlag) {
15193                   SendToICS(ics_prefix);
15194                   SendToICS("flag\n");
15195                 }
15196             } else {
15197                 if (whiteFlag) {
15198                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15199                 } else {
15200                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15201                     if (appData.autoCallFlag) {
15202                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15203                         return TRUE;
15204                     }
15205                 }
15206             }
15207         }
15208     }
15209     return FALSE;
15210 }
15211
15212 void
15213 CheckTimeControl()
15214 {
15215     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15216         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15217
15218     /*
15219      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15220      */
15221     if ( !WhiteOnMove(forwardMostMove) ) {
15222         /* White made time control */
15223         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15224         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15225         /* [HGM] time odds: correct new time quota for time odds! */
15226                                             / WhitePlayer()->timeOdds;
15227         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15228     } else {
15229         lastBlack -= blackTimeRemaining;
15230         /* Black made time control */
15231         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15232                                             / WhitePlayer()->other->timeOdds;
15233         lastWhite = whiteTimeRemaining;
15234     }
15235 }
15236
15237 void
15238 DisplayBothClocks()
15239 {
15240     int wom = gameMode == EditPosition ?
15241       !blackPlaysFirst : WhiteOnMove(currentMove);
15242     DisplayWhiteClock(whiteTimeRemaining, wom);
15243     DisplayBlackClock(blackTimeRemaining, !wom);
15244 }
15245
15246
15247 /* Timekeeping seems to be a portability nightmare.  I think everyone
15248    has ftime(), but I'm really not sure, so I'm including some ifdefs
15249    to use other calls if you don't.  Clocks will be less accurate if
15250    you have neither ftime nor gettimeofday.
15251 */
15252
15253 /* VS 2008 requires the #include outside of the function */
15254 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15255 #include <sys/timeb.h>
15256 #endif
15257
15258 /* Get the current time as a TimeMark */
15259 void
15260 GetTimeMark(tm)
15261      TimeMark *tm;
15262 {
15263 #if HAVE_GETTIMEOFDAY
15264
15265     struct timeval timeVal;
15266     struct timezone timeZone;
15267
15268     gettimeofday(&timeVal, &timeZone);
15269     tm->sec = (long) timeVal.tv_sec;
15270     tm->ms = (int) (timeVal.tv_usec / 1000L);
15271
15272 #else /*!HAVE_GETTIMEOFDAY*/
15273 #if HAVE_FTIME
15274
15275 // include <sys/timeb.h> / moved to just above start of function
15276     struct timeb timeB;
15277
15278     ftime(&timeB);
15279     tm->sec = (long) timeB.time;
15280     tm->ms = (int) timeB.millitm;
15281
15282 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15283     tm->sec = (long) time(NULL);
15284     tm->ms = 0;
15285 #endif
15286 #endif
15287 }
15288
15289 /* Return the difference in milliseconds between two
15290    time marks.  We assume the difference will fit in a long!
15291 */
15292 long
15293 SubtractTimeMarks(tm2, tm1)
15294      TimeMark *tm2, *tm1;
15295 {
15296     return 1000L*(tm2->sec - tm1->sec) +
15297            (long) (tm2->ms - tm1->ms);
15298 }
15299
15300
15301 /*
15302  * Code to manage the game clocks.
15303  *
15304  * In tournament play, black starts the clock and then white makes a move.
15305  * We give the human user a slight advantage if he is playing white---the
15306  * clocks don't run until he makes his first move, so it takes zero time.
15307  * Also, we don't account for network lag, so we could get out of sync
15308  * with GNU Chess's clock -- but then, referees are always right.
15309  */
15310
15311 static TimeMark tickStartTM;
15312 static long intendedTickLength;
15313
15314 long
15315 NextTickLength(timeRemaining)
15316      long timeRemaining;
15317 {
15318     long nominalTickLength, nextTickLength;
15319
15320     if (timeRemaining > 0L && timeRemaining <= 10000L)
15321       nominalTickLength = 100L;
15322     else
15323       nominalTickLength = 1000L;
15324     nextTickLength = timeRemaining % nominalTickLength;
15325     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15326
15327     return nextTickLength;
15328 }
15329
15330 /* Adjust clock one minute up or down */
15331 void
15332 AdjustClock(Boolean which, int dir)
15333 {
15334     if(which) blackTimeRemaining += 60000*dir;
15335     else      whiteTimeRemaining += 60000*dir;
15336     DisplayBothClocks();
15337 }
15338
15339 /* Stop clocks and reset to a fresh time control */
15340 void
15341 ResetClocks()
15342 {
15343     (void) StopClockTimer();
15344     if (appData.icsActive) {
15345         whiteTimeRemaining = blackTimeRemaining = 0;
15346     } else if (searchTime) {
15347         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15348         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15349     } else { /* [HGM] correct new time quote for time odds */
15350         whiteTC = blackTC = fullTimeControlString;
15351         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15352         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15353     }
15354     if (whiteFlag || blackFlag) {
15355         DisplayTitle("");
15356         whiteFlag = blackFlag = FALSE;
15357     }
15358     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15359     DisplayBothClocks();
15360 }
15361
15362 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15363
15364 /* Decrement running clock by amount of time that has passed */
15365 void
15366 DecrementClocks()
15367 {
15368     long timeRemaining;
15369     long lastTickLength, fudge;
15370     TimeMark now;
15371
15372     if (!appData.clockMode) return;
15373     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15374
15375     GetTimeMark(&now);
15376
15377     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15378
15379     /* Fudge if we woke up a little too soon */
15380     fudge = intendedTickLength - lastTickLength;
15381     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15382
15383     if (WhiteOnMove(forwardMostMove)) {
15384         if(whiteNPS >= 0) lastTickLength = 0;
15385         timeRemaining = whiteTimeRemaining -= lastTickLength;
15386         if(timeRemaining < 0 && !appData.icsActive) {
15387             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15388             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15389                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15390                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15391             }
15392         }
15393         DisplayWhiteClock(whiteTimeRemaining - fudge,
15394                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15395     } else {
15396         if(blackNPS >= 0) lastTickLength = 0;
15397         timeRemaining = blackTimeRemaining -= lastTickLength;
15398         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15399             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15400             if(suddenDeath) {
15401                 blackStartMove = forwardMostMove;
15402                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15403             }
15404         }
15405         DisplayBlackClock(blackTimeRemaining - fudge,
15406                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15407     }
15408     if (CheckFlags()) return;
15409
15410     tickStartTM = now;
15411     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15412     StartClockTimer(intendedTickLength);
15413
15414     /* if the time remaining has fallen below the alarm threshold, sound the
15415      * alarm. if the alarm has sounded and (due to a takeback or time control
15416      * with increment) the time remaining has increased to a level above the
15417      * threshold, reset the alarm so it can sound again.
15418      */
15419
15420     if (appData.icsActive && appData.icsAlarm) {
15421
15422         /* make sure we are dealing with the user's clock */
15423         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15424                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15425            )) return;
15426
15427         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15428             alarmSounded = FALSE;
15429         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15430             PlayAlarmSound();
15431             alarmSounded = TRUE;
15432         }
15433     }
15434 }
15435
15436
15437 /* A player has just moved, so stop the previously running
15438    clock and (if in clock mode) start the other one.
15439    We redisplay both clocks in case we're in ICS mode, because
15440    ICS gives us an update to both clocks after every move.
15441    Note that this routine is called *after* forwardMostMove
15442    is updated, so the last fractional tick must be subtracted
15443    from the color that is *not* on move now.
15444 */
15445 void
15446 SwitchClocks(int newMoveNr)
15447 {
15448     long lastTickLength;
15449     TimeMark now;
15450     int flagged = FALSE;
15451
15452     GetTimeMark(&now);
15453
15454     if (StopClockTimer() && appData.clockMode) {
15455         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15456         if (!WhiteOnMove(forwardMostMove)) {
15457             if(blackNPS >= 0) lastTickLength = 0;
15458             blackTimeRemaining -= lastTickLength;
15459            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15460 //         if(pvInfoList[forwardMostMove].time == -1)
15461                  pvInfoList[forwardMostMove].time =               // use GUI time
15462                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15463         } else {
15464            if(whiteNPS >= 0) lastTickLength = 0;
15465            whiteTimeRemaining -= lastTickLength;
15466            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15467 //         if(pvInfoList[forwardMostMove].time == -1)
15468                  pvInfoList[forwardMostMove].time =
15469                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15470         }
15471         flagged = CheckFlags();
15472     }
15473     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15474     CheckTimeControl();
15475
15476     if (flagged || !appData.clockMode) return;
15477
15478     switch (gameMode) {
15479       case MachinePlaysBlack:
15480       case MachinePlaysWhite:
15481       case BeginningOfGame:
15482         if (pausing) return;
15483         break;
15484
15485       case EditGame:
15486       case PlayFromGameFile:
15487       case IcsExamining:
15488         return;
15489
15490       default:
15491         break;
15492     }
15493
15494     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15495         if(WhiteOnMove(forwardMostMove))
15496              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15497         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15498     }
15499
15500     tickStartTM = now;
15501     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15502       whiteTimeRemaining : blackTimeRemaining);
15503     StartClockTimer(intendedTickLength);
15504 }
15505
15506
15507 /* Stop both clocks */
15508 void
15509 StopClocks()
15510 {
15511     long lastTickLength;
15512     TimeMark now;
15513
15514     if (!StopClockTimer()) return;
15515     if (!appData.clockMode) return;
15516
15517     GetTimeMark(&now);
15518
15519     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15520     if (WhiteOnMove(forwardMostMove)) {
15521         if(whiteNPS >= 0) lastTickLength = 0;
15522         whiteTimeRemaining -= lastTickLength;
15523         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15524     } else {
15525         if(blackNPS >= 0) lastTickLength = 0;
15526         blackTimeRemaining -= lastTickLength;
15527         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15528     }
15529     CheckFlags();
15530 }
15531
15532 /* Start clock of player on move.  Time may have been reset, so
15533    if clock is already running, stop and restart it. */
15534 void
15535 StartClocks()
15536 {
15537     (void) StopClockTimer(); /* in case it was running already */
15538     DisplayBothClocks();
15539     if (CheckFlags()) return;
15540
15541     if (!appData.clockMode) return;
15542     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15543
15544     GetTimeMark(&tickStartTM);
15545     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15546       whiteTimeRemaining : blackTimeRemaining);
15547
15548    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15549     whiteNPS = blackNPS = -1;
15550     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15551        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15552         whiteNPS = first.nps;
15553     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15554        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15555         blackNPS = first.nps;
15556     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15557         whiteNPS = second.nps;
15558     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15559         blackNPS = second.nps;
15560     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15561
15562     StartClockTimer(intendedTickLength);
15563 }
15564
15565 char *
15566 TimeString(ms)
15567      long ms;
15568 {
15569     long second, minute, hour, day;
15570     char *sign = "";
15571     static char buf[32];
15572
15573     if (ms > 0 && ms <= 9900) {
15574       /* convert milliseconds to tenths, rounding up */
15575       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15576
15577       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15578       return buf;
15579     }
15580
15581     /* convert milliseconds to seconds, rounding up */
15582     /* use floating point to avoid strangeness of integer division
15583        with negative dividends on many machines */
15584     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15585
15586     if (second < 0) {
15587         sign = "-";
15588         second = -second;
15589     }
15590
15591     day = second / (60 * 60 * 24);
15592     second = second % (60 * 60 * 24);
15593     hour = second / (60 * 60);
15594     second = second % (60 * 60);
15595     minute = second / 60;
15596     second = second % 60;
15597
15598     if (day > 0)
15599       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15600               sign, day, hour, minute, second);
15601     else if (hour > 0)
15602       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15603     else
15604       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15605
15606     return buf;
15607 }
15608
15609
15610 /*
15611  * This is necessary because some C libraries aren't ANSI C compliant yet.
15612  */
15613 char *
15614 StrStr(string, match)
15615      char *string, *match;
15616 {
15617     int i, length;
15618
15619     length = strlen(match);
15620
15621     for (i = strlen(string) - length; i >= 0; i--, string++)
15622       if (!strncmp(match, string, length))
15623         return string;
15624
15625     return NULL;
15626 }
15627
15628 char *
15629 StrCaseStr(string, match)
15630      char *string, *match;
15631 {
15632     int i, j, length;
15633
15634     length = strlen(match);
15635
15636     for (i = strlen(string) - length; i >= 0; i--, string++) {
15637         for (j = 0; j < length; j++) {
15638             if (ToLower(match[j]) != ToLower(string[j]))
15639               break;
15640         }
15641         if (j == length) return string;
15642     }
15643
15644     return NULL;
15645 }
15646
15647 #ifndef _amigados
15648 int
15649 StrCaseCmp(s1, s2)
15650      char *s1, *s2;
15651 {
15652     char c1, c2;
15653
15654     for (;;) {
15655         c1 = ToLower(*s1++);
15656         c2 = ToLower(*s2++);
15657         if (c1 > c2) return 1;
15658         if (c1 < c2) return -1;
15659         if (c1 == NULLCHAR) return 0;
15660     }
15661 }
15662
15663
15664 int
15665 ToLower(c)
15666      int c;
15667 {
15668     return isupper(c) ? tolower(c) : c;
15669 }
15670
15671
15672 int
15673 ToUpper(c)
15674      int c;
15675 {
15676     return islower(c) ? toupper(c) : c;
15677 }
15678 #endif /* !_amigados    */
15679
15680 char *
15681 StrSave(s)
15682      char *s;
15683 {
15684   char *ret;
15685
15686   if ((ret = (char *) malloc(strlen(s) + 1)))
15687     {
15688       safeStrCpy(ret, s, strlen(s)+1);
15689     }
15690   return ret;
15691 }
15692
15693 char *
15694 StrSavePtr(s, savePtr)
15695      char *s, **savePtr;
15696 {
15697     if (*savePtr) {
15698         free(*savePtr);
15699     }
15700     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15701       safeStrCpy(*savePtr, s, strlen(s)+1);
15702     }
15703     return(*savePtr);
15704 }
15705
15706 char *
15707 PGNDate()
15708 {
15709     time_t clock;
15710     struct tm *tm;
15711     char buf[MSG_SIZ];
15712
15713     clock = time((time_t *)NULL);
15714     tm = localtime(&clock);
15715     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15716             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15717     return StrSave(buf);
15718 }
15719
15720
15721 char *
15722 PositionToFEN(move, overrideCastling)
15723      int move;
15724      char *overrideCastling;
15725 {
15726     int i, j, fromX, fromY, toX, toY;
15727     int whiteToPlay;
15728     char buf[128];
15729     char *p, *q;
15730     int emptycount;
15731     ChessSquare piece;
15732
15733     whiteToPlay = (gameMode == EditPosition) ?
15734       !blackPlaysFirst : (move % 2 == 0);
15735     p = buf;
15736
15737     /* Piece placement data */
15738     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15739         emptycount = 0;
15740         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15741             if (boards[move][i][j] == EmptySquare) {
15742                 emptycount++;
15743             } else { ChessSquare piece = boards[move][i][j];
15744                 if (emptycount > 0) {
15745                     if(emptycount<10) /* [HGM] can be >= 10 */
15746                         *p++ = '0' + emptycount;
15747                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15748                     emptycount = 0;
15749                 }
15750                 if(PieceToChar(piece) == '+') {
15751                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15752                     *p++ = '+';
15753                     piece = (ChessSquare)(DEMOTED piece);
15754                 }
15755                 *p++ = PieceToChar(piece);
15756                 if(p[-1] == '~') {
15757                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15758                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15759                     *p++ = '~';
15760                 }
15761             }
15762         }
15763         if (emptycount > 0) {
15764             if(emptycount<10) /* [HGM] can be >= 10 */
15765                 *p++ = '0' + emptycount;
15766             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15767             emptycount = 0;
15768         }
15769         *p++ = '/';
15770     }
15771     *(p - 1) = ' ';
15772
15773     /* [HGM] print Crazyhouse or Shogi holdings */
15774     if( gameInfo.holdingsWidth ) {
15775         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15776         q = p;
15777         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15778             piece = boards[move][i][BOARD_WIDTH-1];
15779             if( piece != EmptySquare )
15780               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15781                   *p++ = PieceToChar(piece);
15782         }
15783         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15784             piece = boards[move][BOARD_HEIGHT-i-1][0];
15785             if( piece != EmptySquare )
15786               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15787                   *p++ = PieceToChar(piece);
15788         }
15789
15790         if( q == p ) *p++ = '-';
15791         *p++ = ']';
15792         *p++ = ' ';
15793     }
15794
15795     /* Active color */
15796     *p++ = whiteToPlay ? 'w' : 'b';
15797     *p++ = ' ';
15798
15799   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15800     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15801   } else {
15802   if(nrCastlingRights) {
15803      q = p;
15804      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15805        /* [HGM] write directly from rights */
15806            if(boards[move][CASTLING][2] != NoRights &&
15807               boards[move][CASTLING][0] != NoRights   )
15808                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15809            if(boards[move][CASTLING][2] != NoRights &&
15810               boards[move][CASTLING][1] != NoRights   )
15811                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15812            if(boards[move][CASTLING][5] != NoRights &&
15813               boards[move][CASTLING][3] != NoRights   )
15814                 *p++ = boards[move][CASTLING][3] + AAA;
15815            if(boards[move][CASTLING][5] != NoRights &&
15816               boards[move][CASTLING][4] != NoRights   )
15817                 *p++ = boards[move][CASTLING][4] + AAA;
15818      } else {
15819
15820         /* [HGM] write true castling rights */
15821         if( nrCastlingRights == 6 ) {
15822             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15823                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15824             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15825                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15826             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15827                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15828             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15829                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15830         }
15831      }
15832      if (q == p) *p++ = '-'; /* No castling rights */
15833      *p++ = ' ';
15834   }
15835
15836   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15837      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15838     /* En passant target square */
15839     if (move > backwardMostMove) {
15840         fromX = moveList[move - 1][0] - AAA;
15841         fromY = moveList[move - 1][1] - ONE;
15842         toX = moveList[move - 1][2] - AAA;
15843         toY = moveList[move - 1][3] - ONE;
15844         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15845             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15846             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15847             fromX == toX) {
15848             /* 2-square pawn move just happened */
15849             *p++ = toX + AAA;
15850             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15851         } else {
15852             *p++ = '-';
15853         }
15854     } else if(move == backwardMostMove) {
15855         // [HGM] perhaps we should always do it like this, and forget the above?
15856         if((signed char)boards[move][EP_STATUS] >= 0) {
15857             *p++ = boards[move][EP_STATUS] + AAA;
15858             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15859         } else {
15860             *p++ = '-';
15861         }
15862     } else {
15863         *p++ = '-';
15864     }
15865     *p++ = ' ';
15866   }
15867   }
15868
15869     /* [HGM] find reversible plies */
15870     {   int i = 0, j=move;
15871
15872         if (appData.debugMode) { int k;
15873             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15874             for(k=backwardMostMove; k<=forwardMostMove; k++)
15875                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15876
15877         }
15878
15879         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15880         if( j == backwardMostMove ) i += initialRulePlies;
15881         sprintf(p, "%d ", i);
15882         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15883     }
15884     /* Fullmove number */
15885     sprintf(p, "%d", (move / 2) + 1);
15886
15887     return StrSave(buf);
15888 }
15889
15890 Boolean
15891 ParseFEN(board, blackPlaysFirst, fen)
15892     Board board;
15893      int *blackPlaysFirst;
15894      char *fen;
15895 {
15896     int i, j;
15897     char *p, c;
15898     int emptycount;
15899     ChessSquare piece;
15900
15901     p = fen;
15902
15903     /* [HGM] by default clear Crazyhouse holdings, if present */
15904     if(gameInfo.holdingsWidth) {
15905        for(i=0; i<BOARD_HEIGHT; i++) {
15906            board[i][0]             = EmptySquare; /* black holdings */
15907            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15908            board[i][1]             = (ChessSquare) 0; /* black counts */
15909            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15910        }
15911     }
15912
15913     /* Piece placement data */
15914     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15915         j = 0;
15916         for (;;) {
15917             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15918                 if (*p == '/') p++;
15919                 emptycount = gameInfo.boardWidth - j;
15920                 while (emptycount--)
15921                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15922                 break;
15923 #if(BOARD_FILES >= 10)
15924             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15925                 p++; emptycount=10;
15926                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15927                 while (emptycount--)
15928                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15929 #endif
15930             } else if (isdigit(*p)) {
15931                 emptycount = *p++ - '0';
15932                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15933                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15934                 while (emptycount--)
15935                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15936             } else if (*p == '+' || isalpha(*p)) {
15937                 if (j >= gameInfo.boardWidth) return FALSE;
15938                 if(*p=='+') {
15939                     piece = CharToPiece(*++p);
15940                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15941                     piece = (ChessSquare) (PROMOTED piece ); p++;
15942                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15943                 } else piece = CharToPiece(*p++);
15944
15945                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15946                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15947                     piece = (ChessSquare) (PROMOTED piece);
15948                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15949                     p++;
15950                 }
15951                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15952             } else {
15953                 return FALSE;
15954             }
15955         }
15956     }
15957     while (*p == '/' || *p == ' ') p++;
15958
15959     /* [HGM] look for Crazyhouse holdings here */
15960     while(*p==' ') p++;
15961     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15962         if(*p == '[') p++;
15963         if(*p == '-' ) p++; /* empty holdings */ else {
15964             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15965             /* if we would allow FEN reading to set board size, we would   */
15966             /* have to add holdings and shift the board read so far here   */
15967             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15968                 p++;
15969                 if((int) piece >= (int) BlackPawn ) {
15970                     i = (int)piece - (int)BlackPawn;
15971                     i = PieceToNumber((ChessSquare)i);
15972                     if( i >= gameInfo.holdingsSize ) return FALSE;
15973                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15974                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15975                 } else {
15976                     i = (int)piece - (int)WhitePawn;
15977                     i = PieceToNumber((ChessSquare)i);
15978                     if( i >= gameInfo.holdingsSize ) return FALSE;
15979                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15980                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15981                 }
15982             }
15983         }
15984         if(*p == ']') p++;
15985     }
15986
15987     while(*p == ' ') p++;
15988
15989     /* Active color */
15990     c = *p++;
15991     if(appData.colorNickNames) {
15992       if( c == appData.colorNickNames[0] ) c = 'w'; else
15993       if( c == appData.colorNickNames[1] ) c = 'b';
15994     }
15995     switch (c) {
15996       case 'w':
15997         *blackPlaysFirst = FALSE;
15998         break;
15999       case 'b':
16000         *blackPlaysFirst = TRUE;
16001         break;
16002       default:
16003         return FALSE;
16004     }
16005
16006     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16007     /* return the extra info in global variiables             */
16008
16009     /* set defaults in case FEN is incomplete */
16010     board[EP_STATUS] = EP_UNKNOWN;
16011     for(i=0; i<nrCastlingRights; i++ ) {
16012         board[CASTLING][i] =
16013             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16014     }   /* assume possible unless obviously impossible */
16015     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16016     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16017     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16018                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16019     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16020     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16021     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16022                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16023     FENrulePlies = 0;
16024
16025     while(*p==' ') p++;
16026     if(nrCastlingRights) {
16027       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16028           /* castling indicator present, so default becomes no castlings */
16029           for(i=0; i<nrCastlingRights; i++ ) {
16030                  board[CASTLING][i] = NoRights;
16031           }
16032       }
16033       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16034              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16035              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16036              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16037         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16038
16039         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16040             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16041             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16042         }
16043         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16044             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16045         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16046                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16047         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16048                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16049         switch(c) {
16050           case'K':
16051               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16052               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16053               board[CASTLING][2] = whiteKingFile;
16054               break;
16055           case'Q':
16056               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16057               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16058               board[CASTLING][2] = whiteKingFile;
16059               break;
16060           case'k':
16061               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16062               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16063               board[CASTLING][5] = blackKingFile;
16064               break;
16065           case'q':
16066               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16067               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16068               board[CASTLING][5] = blackKingFile;
16069           case '-':
16070               break;
16071           default: /* FRC castlings */
16072               if(c >= 'a') { /* black rights */
16073                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16074                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16075                   if(i == BOARD_RGHT) break;
16076                   board[CASTLING][5] = i;
16077                   c -= AAA;
16078                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16079                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16080                   if(c > i)
16081                       board[CASTLING][3] = c;
16082                   else
16083                       board[CASTLING][4] = c;
16084               } else { /* white rights */
16085                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16086                     if(board[0][i] == WhiteKing) break;
16087                   if(i == BOARD_RGHT) break;
16088                   board[CASTLING][2] = i;
16089                   c -= AAA - 'a' + 'A';
16090                   if(board[0][c] >= WhiteKing) break;
16091                   if(c > i)
16092                       board[CASTLING][0] = c;
16093                   else
16094                       board[CASTLING][1] = c;
16095               }
16096         }
16097       }
16098       for(i=0; i<nrCastlingRights; i++)
16099         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16100     if (appData.debugMode) {
16101         fprintf(debugFP, "FEN castling rights:");
16102         for(i=0; i<nrCastlingRights; i++)
16103         fprintf(debugFP, " %d", board[CASTLING][i]);
16104         fprintf(debugFP, "\n");
16105     }
16106
16107       while(*p==' ') p++;
16108     }
16109
16110     /* read e.p. field in games that know e.p. capture */
16111     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16112        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16113       if(*p=='-') {
16114         p++; board[EP_STATUS] = EP_NONE;
16115       } else {
16116          char c = *p++ - AAA;
16117
16118          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16119          if(*p >= '0' && *p <='9') p++;
16120          board[EP_STATUS] = c;
16121       }
16122     }
16123
16124
16125     if(sscanf(p, "%d", &i) == 1) {
16126         FENrulePlies = i; /* 50-move ply counter */
16127         /* (The move number is still ignored)    */
16128     }
16129
16130     return TRUE;
16131 }
16132
16133 void
16134 EditPositionPasteFEN(char *fen)
16135 {
16136   if (fen != NULL) {
16137     Board initial_position;
16138
16139     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16140       DisplayError(_("Bad FEN position in clipboard"), 0);
16141       return ;
16142     } else {
16143       int savedBlackPlaysFirst = blackPlaysFirst;
16144       EditPositionEvent();
16145       blackPlaysFirst = savedBlackPlaysFirst;
16146       CopyBoard(boards[0], initial_position);
16147       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16148       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16149       DisplayBothClocks();
16150       DrawPosition(FALSE, boards[currentMove]);
16151     }
16152   }
16153 }
16154
16155 static char cseq[12] = "\\   ";
16156
16157 Boolean set_cont_sequence(char *new_seq)
16158 {
16159     int len;
16160     Boolean ret;
16161
16162     // handle bad attempts to set the sequence
16163         if (!new_seq)
16164                 return 0; // acceptable error - no debug
16165
16166     len = strlen(new_seq);
16167     ret = (len > 0) && (len < sizeof(cseq));
16168     if (ret)
16169       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16170     else if (appData.debugMode)
16171       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16172     return ret;
16173 }
16174
16175 /*
16176     reformat a source message so words don't cross the width boundary.  internal
16177     newlines are not removed.  returns the wrapped size (no null character unless
16178     included in source message).  If dest is NULL, only calculate the size required
16179     for the dest buffer.  lp argument indicats line position upon entry, and it's
16180     passed back upon exit.
16181 */
16182 int wrap(char *dest, char *src, int count, int width, int *lp)
16183 {
16184     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16185
16186     cseq_len = strlen(cseq);
16187     old_line = line = *lp;
16188     ansi = len = clen = 0;
16189
16190     for (i=0; i < count; i++)
16191     {
16192         if (src[i] == '\033')
16193             ansi = 1;
16194
16195         // if we hit the width, back up
16196         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16197         {
16198             // store i & len in case the word is too long
16199             old_i = i, old_len = len;
16200
16201             // find the end of the last word
16202             while (i && src[i] != ' ' && src[i] != '\n')
16203             {
16204                 i--;
16205                 len--;
16206             }
16207
16208             // word too long?  restore i & len before splitting it
16209             if ((old_i-i+clen) >= width)
16210             {
16211                 i = old_i;
16212                 len = old_len;
16213             }
16214
16215             // extra space?
16216             if (i && src[i-1] == ' ')
16217                 len--;
16218
16219             if (src[i] != ' ' && src[i] != '\n')
16220             {
16221                 i--;
16222                 if (len)
16223                     len--;
16224             }
16225
16226             // now append the newline and continuation sequence
16227             if (dest)
16228                 dest[len] = '\n';
16229             len++;
16230             if (dest)
16231                 strncpy(dest+len, cseq, cseq_len);
16232             len += cseq_len;
16233             line = cseq_len;
16234             clen = cseq_len;
16235             continue;
16236         }
16237
16238         if (dest)
16239             dest[len] = src[i];
16240         len++;
16241         if (!ansi)
16242             line++;
16243         if (src[i] == '\n')
16244             line = 0;
16245         if (src[i] == 'm')
16246             ansi = 0;
16247     }
16248     if (dest && appData.debugMode)
16249     {
16250         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16251             count, width, line, len, *lp);
16252         show_bytes(debugFP, src, count);
16253         fprintf(debugFP, "\ndest: ");
16254         show_bytes(debugFP, dest, len);
16255         fprintf(debugFP, "\n");
16256     }
16257     *lp = dest ? line : old_line;
16258
16259     return len;
16260 }
16261
16262 // [HGM] vari: routines for shelving variations
16263
16264 void
16265 PushInner(int firstMove, int lastMove)
16266 {
16267         int i, j, nrMoves = lastMove - firstMove;
16268
16269         // push current tail of game on stack
16270         savedResult[storedGames] = gameInfo.result;
16271         savedDetails[storedGames] = gameInfo.resultDetails;
16272         gameInfo.resultDetails = NULL;
16273         savedFirst[storedGames] = firstMove;
16274         savedLast [storedGames] = lastMove;
16275         savedFramePtr[storedGames] = framePtr;
16276         framePtr -= nrMoves; // reserve space for the boards
16277         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16278             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16279             for(j=0; j<MOVE_LEN; j++)
16280                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16281             for(j=0; j<2*MOVE_LEN; j++)
16282                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16283             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16284             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16285             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16286             pvInfoList[firstMove+i-1].depth = 0;
16287             commentList[framePtr+i] = commentList[firstMove+i];
16288             commentList[firstMove+i] = NULL;
16289         }
16290
16291         storedGames++;
16292         forwardMostMove = firstMove; // truncate game so we can start variation
16293 }
16294
16295 void
16296 PushTail(int firstMove, int lastMove)
16297 {
16298         if(appData.icsActive) { // only in local mode
16299                 forwardMostMove = currentMove; // mimic old ICS behavior
16300                 return;
16301         }
16302         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16303
16304         PushInner(firstMove, lastMove);
16305         if(storedGames == 1) GreyRevert(FALSE);
16306 }
16307
16308 void
16309 PopInner(Boolean annotate)
16310 {
16311         int i, j, nrMoves;
16312         char buf[8000], moveBuf[20];
16313
16314         storedGames--;
16315         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16316         nrMoves = savedLast[storedGames] - currentMove;
16317         if(annotate) {
16318                 int cnt = 10;
16319                 if(!WhiteOnMove(currentMove))
16320                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16321                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16322                 for(i=currentMove; i<forwardMostMove; i++) {
16323                         if(WhiteOnMove(i))
16324                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16325                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16326                         strcat(buf, moveBuf);
16327                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16328                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16329                 }
16330                 strcat(buf, ")");
16331         }
16332         for(i=1; i<=nrMoves; i++) { // copy last variation back
16333             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16334             for(j=0; j<MOVE_LEN; j++)
16335                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16336             for(j=0; j<2*MOVE_LEN; j++)
16337                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16338             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16339             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16340             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16341             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16342             commentList[currentMove+i] = commentList[framePtr+i];
16343             commentList[framePtr+i] = NULL;
16344         }
16345         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16346         framePtr = savedFramePtr[storedGames];
16347         gameInfo.result = savedResult[storedGames];
16348         if(gameInfo.resultDetails != NULL) {
16349             free(gameInfo.resultDetails);
16350       }
16351         gameInfo.resultDetails = savedDetails[storedGames];
16352         forwardMostMove = currentMove + nrMoves;
16353 }
16354
16355 Boolean
16356 PopTail(Boolean annotate)
16357 {
16358         if(appData.icsActive) return FALSE; // only in local mode
16359         if(!storedGames) return FALSE; // sanity
16360         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16361
16362         PopInner(annotate);
16363
16364         if(storedGames == 0) GreyRevert(TRUE);
16365         return TRUE;
16366 }
16367
16368 void
16369 CleanupTail()
16370 {       // remove all shelved variations
16371         int i;
16372         for(i=0; i<storedGames; i++) {
16373             if(savedDetails[i])
16374                 free(savedDetails[i]);
16375             savedDetails[i] = NULL;
16376         }
16377         for(i=framePtr; i<MAX_MOVES; i++) {
16378                 if(commentList[i]) free(commentList[i]);
16379                 commentList[i] = NULL;
16380         }
16381         framePtr = MAX_MOVES-1;
16382         storedGames = 0;
16383 }
16384
16385 void
16386 LoadVariation(int index, char *text)
16387 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16388         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16389         int level = 0, move;
16390
16391         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16392         // first find outermost bracketing variation
16393         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16394             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16395                 if(*p == '{') wait = '}'; else
16396                 if(*p == '[') wait = ']'; else
16397                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16398                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16399             }
16400             if(*p == wait) wait = NULLCHAR; // closing ]} found
16401             p++;
16402         }
16403         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16404         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16405         end[1] = NULLCHAR; // clip off comment beyond variation
16406         ToNrEvent(currentMove-1);
16407         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16408         // kludge: use ParsePV() to append variation to game
16409         move = currentMove;
16410         ParsePV(start, TRUE, TRUE);
16411         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16412         ClearPremoveHighlights();
16413         CommentPopDown();
16414         ToNrEvent(currentMove+1);
16415 }
16416