Fix display of logos
[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     UpdateLogos(TRUE);
865     if(n) return; // only startup first engine immediately; second can wait
866     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
867     LoadEngine();
868 }
869
870 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
871 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
872
873 static char resetOptions[] = 
874         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
875         "-firstOptions \"\" -firstNPS -1 -fn \"\"";
876
877 void
878 Load(ChessProgramState *cps, int i)
879 {
880     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
881     if(engineLine[0]) { // an engine was selected from the combo box
882         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
883         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
884         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
885         ParseArgsFromString(buf);
886         SwapEngines(i);
887         ReplaceEngine(cps, i);
888         return;
889     }
890     p = engineName;
891     while(q = strchr(p, SLASH)) p = q+1;
892     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
893     if(engineDir[0] != NULLCHAR)
894         appData.directory[i] = engineDir;
895     else if(p != engineName) { // derive directory from engine path, when not given
896         p[-1] = 0;
897         appData.directory[i] = strdup(engineName);
898         p[-1] = SLASH;
899     } else appData.directory[i] = ".";
900     if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901     if(params[0]) {
902         snprintf(command, MSG_SIZ, "%s %s", p, params);
903         p = command;
904     }
905     appData.chessProgram[i] = strdup(p);
906     appData.isUCI[i] = isUCI;
907     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
908     appData.hasOwnBookUCI[i] = hasBook;
909     if(!nickName[0]) useNick = FALSE;
910     if(useNick) ASSIGN(appData.pgnName[i], nickName);
911     if(addToList) {
912         int len;
913         char quote;
914         q = firstChessProgramNames;
915         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
916         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
917         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
918                         quote, p, quote, appData.directory[i], 
919                         useNick ? " -fn \"" : "",
920                         useNick ? nickName : "",
921                         useNick ? "\"" : "",
922                         v1 ? " -firstProtocolVersion 1" : "",
923                         hasBook ? "" : " -fNoOwnBookUCI",
924                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
925                         storeVariant ? " -variant " : "",
926                         storeVariant ? VariantName(gameInfo.variant) : "");
927         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
928         snprintf(firstChessProgramNames, len, "%s%s", q, buf);
929         if(q)   free(q);
930     }
931     ReplaceEngine(cps, i);
932 }
933
934 void
935 InitTimeControls()
936 {
937     int matched, min, sec;
938     /*
939      * Parse timeControl resource
940      */
941     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
942                           appData.movesPerSession)) {
943         char buf[MSG_SIZ];
944         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
945         DisplayFatalError(buf, 0, 2);
946     }
947
948     /*
949      * Parse searchTime resource
950      */
951     if (*appData.searchTime != NULLCHAR) {
952         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
953         if (matched == 1) {
954             searchTime = min * 60;
955         } else if (matched == 2) {
956             searchTime = min * 60 + sec;
957         } else {
958             char buf[MSG_SIZ];
959             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
960             DisplayFatalError(buf, 0, 2);
961         }
962     }
963 }
964
965 void
966 InitBackEnd1()
967 {
968
969     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
970     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
971
972     GetTimeMark(&programStartTime);
973     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
974     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
975
976     ClearProgramStats();
977     programStats.ok_to_send = 1;
978     programStats.seen_stat = 0;
979
980     /*
981      * Initialize game list
982      */
983     ListNew(&gameList);
984
985
986     /*
987      * Internet chess server status
988      */
989     if (appData.icsActive) {
990         appData.matchMode = FALSE;
991         appData.matchGames = 0;
992 #if ZIPPY
993         appData.noChessProgram = !appData.zippyPlay;
994 #else
995         appData.zippyPlay = FALSE;
996         appData.zippyTalk = FALSE;
997         appData.noChessProgram = TRUE;
998 #endif
999         if (*appData.icsHelper != NULLCHAR) {
1000             appData.useTelnet = TRUE;
1001             appData.telnetProgram = appData.icsHelper;
1002         }
1003     } else {
1004         appData.zippyTalk = appData.zippyPlay = FALSE;
1005     }
1006
1007     /* [AS] Initialize pv info list [HGM] and game state */
1008     {
1009         int i, j;
1010
1011         for( i=0; i<=framePtr; i++ ) {
1012             pvInfoList[i].depth = -1;
1013             boards[i][EP_STATUS] = EP_NONE;
1014             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015         }
1016     }
1017
1018     InitTimeControls();
1019
1020     /* [AS] Adjudication threshold */
1021     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022
1023     InitEngine(&first, 0);
1024     InitEngine(&second, 1);
1025     CommonEngineInit();
1026
1027     pairing.which = "pairing"; // pairing engine
1028     pairing.pr = NoProc;
1029     pairing.isr = NULL;
1030     pairing.program = appData.pairingEngine;
1031     pairing.host = "localhost";
1032     pairing.dir = ".";
1033
1034     if (appData.icsActive) {
1035         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1036     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037         appData.clockMode = FALSE;
1038         first.sendTime = second.sendTime = 0;
1039     }
1040
1041 #if ZIPPY
1042     /* Override some settings from environment variables, for backward
1043        compatibility.  Unfortunately it's not feasible to have the env
1044        vars just set defaults, at least in xboard.  Ugh.
1045     */
1046     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1047       ZippyInit();
1048     }
1049 #endif
1050
1051     if (!appData.icsActive) {
1052       char buf[MSG_SIZ];
1053       int len;
1054
1055       /* Check for variants that are supported only in ICS mode,
1056          or not at all.  Some that are accepted here nevertheless
1057          have bugs; see comments below.
1058       */
1059       VariantClass variant = StringToVariant(appData.variant);
1060       switch (variant) {
1061       case VariantBughouse:     /* need four players and two boards */
1062       case VariantKriegspiel:   /* need to hide pieces and move details */
1063         /* case VariantFischeRandom: (Fabien: moved below) */
1064         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065         if( (len > MSG_SIZ) && appData.debugMode )
1066           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067
1068         DisplayFatalError(buf, 0, 2);
1069         return;
1070
1071       case VariantUnknown:
1072       case VariantLoadable:
1073       case Variant29:
1074       case Variant30:
1075       case Variant31:
1076       case Variant32:
1077       case Variant33:
1078       case Variant34:
1079       case Variant35:
1080       case Variant36:
1081       default:
1082         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083         if( (len > MSG_SIZ) && appData.debugMode )
1084           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085
1086         DisplayFatalError(buf, 0, 2);
1087         return;
1088
1089       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1090       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1091       case VariantGothic:     /* [HGM] should work */
1092       case VariantCapablanca: /* [HGM] should work */
1093       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1094       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1095       case VariantKnightmate: /* [HGM] should work */
1096       case VariantCylinder:   /* [HGM] untested */
1097       case VariantFalcon:     /* [HGM] untested */
1098       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099                                  offboard interposition not understood */
1100       case VariantNormal:     /* definitely works! */
1101       case VariantWildCastle: /* pieces not automatically shuffled */
1102       case VariantNoCastle:   /* pieces not automatically shuffled */
1103       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104       case VariantLosers:     /* should work except for win condition,
1105                                  and doesn't know captures are mandatory */
1106       case VariantSuicide:    /* should work except for win condition,
1107                                  and doesn't know captures are mandatory */
1108       case VariantGiveaway:   /* should work except for win condition,
1109                                  and doesn't know captures are mandatory */
1110       case VariantTwoKings:   /* should work */
1111       case VariantAtomic:     /* should work except for win condition */
1112       case Variant3Check:     /* should work except for win condition */
1113       case VariantShatranj:   /* should work except for all win conditions */
1114       case VariantMakruk:     /* should work except for daw countdown */
1115       case VariantBerolina:   /* might work if TestLegality is off */
1116       case VariantCapaRandom: /* should work */
1117       case VariantJanus:      /* should work */
1118       case VariantSuper:      /* experimental */
1119       case VariantGreat:      /* experimental, requires legality testing to be off */
1120       case VariantSChess:     /* S-Chess, should work */
1121       case VariantSpartan:    /* should work */
1122         break;
1123       }
1124     }
1125
1126 }
1127
1128 int NextIntegerFromString( char ** str, long * value )
1129 {
1130     int result = -1;
1131     char * s = *str;
1132
1133     while( *s == ' ' || *s == '\t' ) {
1134         s++;
1135     }
1136
1137     *value = 0;
1138
1139     if( *s >= '0' && *s <= '9' ) {
1140         while( *s >= '0' && *s <= '9' ) {
1141             *value = *value * 10 + (*s - '0');
1142             s++;
1143         }
1144
1145         result = 0;
1146     }
1147
1148     *str = s;
1149
1150     return result;
1151 }
1152
1153 int NextTimeControlFromString( char ** str, long * value )
1154 {
1155     long temp;
1156     int result = NextIntegerFromString( str, &temp );
1157
1158     if( result == 0 ) {
1159         *value = temp * 60; /* Minutes */
1160         if( **str == ':' ) {
1161             (*str)++;
1162             result = NextIntegerFromString( str, &temp );
1163             *value += temp; /* Seconds */
1164         }
1165     }
1166
1167     return result;
1168 }
1169
1170 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1171 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1172     int result = -1, type = 0; long temp, temp2;
1173
1174     if(**str != ':') return -1; // old params remain in force!
1175     (*str)++;
1176     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1177     if( NextIntegerFromString( str, &temp ) ) return -1;
1178     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1179
1180     if(**str != '/') {
1181         /* time only: incremental or sudden-death time control */
1182         if(**str == '+') { /* increment follows; read it */
1183             (*str)++;
1184             if(**str == '!') type = *(*str)++; // Bronstein TC
1185             if(result = NextIntegerFromString( str, &temp2)) return -1;
1186             *inc = temp2 * 1000;
1187             if(**str == '.') { // read fraction of increment
1188                 char *start = ++(*str);
1189                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1190                 temp2 *= 1000;
1191                 while(start++ < *str) temp2 /= 10;
1192                 *inc += temp2;
1193             }
1194         } else *inc = 0;
1195         *moves = 0; *tc = temp * 1000; *incType = type;
1196         return 0;
1197     }
1198
1199     (*str)++; /* classical time control */
1200     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1201
1202     if(result == 0) {
1203         *moves = temp;
1204         *tc    = temp2 * 1000;
1205         *inc   = 0;
1206         *incType = type;
1207     }
1208     return result;
1209 }
1210
1211 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1212 {   /* [HGM] get time to add from the multi-session time-control string */
1213     int incType, moves=1; /* kludge to force reading of first session */
1214     long time, increment;
1215     char *s = tcString;
1216
1217     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1218     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1219     do {
1220         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1221         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1222         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1223         if(movenr == -1) return time;    /* last move before new session     */
1224         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1225         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1226         if(!moves) return increment;     /* current session is incremental   */
1227         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1228     } while(movenr >= -1);               /* try again for next session       */
1229
1230     return 0; // no new time quota on this move
1231 }
1232
1233 int
1234 ParseTimeControl(tc, ti, mps)
1235      char *tc;
1236      float ti;
1237      int mps;
1238 {
1239   long tc1;
1240   long tc2;
1241   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1242   int min, sec=0;
1243
1244   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1245   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1246       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1247   if(ti > 0) {
1248
1249     if(mps)
1250       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1251     else 
1252       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1253   } else {
1254     if(mps)
1255       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1256     else 
1257       snprintf(buf, MSG_SIZ, ":%s", mytc);
1258   }
1259   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1260   
1261   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1262     return FALSE;
1263   }
1264
1265   if( *tc == '/' ) {
1266     /* Parse second time control */
1267     tc++;
1268
1269     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1270       return FALSE;
1271     }
1272
1273     if( tc2 == 0 ) {
1274       return FALSE;
1275     }
1276
1277     timeControl_2 = tc2 * 1000;
1278   }
1279   else {
1280     timeControl_2 = 0;
1281   }
1282
1283   if( tc1 == 0 ) {
1284     return FALSE;
1285   }
1286
1287   timeControl = tc1 * 1000;
1288
1289   if (ti >= 0) {
1290     timeIncrement = ti * 1000;  /* convert to ms */
1291     movesPerSession = 0;
1292   } else {
1293     timeIncrement = 0;
1294     movesPerSession = mps;
1295   }
1296   return TRUE;
1297 }
1298
1299 void
1300 InitBackEnd2()
1301 {
1302     if (appData.debugMode) {
1303         fprintf(debugFP, "%s\n", programVersion);
1304     }
1305
1306     set_cont_sequence(appData.wrapContSeq);
1307     if (appData.matchGames > 0) {
1308         appData.matchMode = TRUE;
1309     } else if (appData.matchMode) {
1310         appData.matchGames = 1;
1311     }
1312     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1313         appData.matchGames = appData.sameColorGames;
1314     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1315         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1316         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1317     }
1318     Reset(TRUE, FALSE);
1319     if (appData.noChessProgram || first.protocolVersion == 1) {
1320       InitBackEnd3();
1321     } else {
1322       /* kludge: allow timeout for initial "feature" commands */
1323       FreezeUI();
1324       DisplayMessage("", _("Starting chess program"));
1325       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1326     }
1327 }
1328
1329 int
1330 CalculateIndex(int index, int gameNr)
1331 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1332     int res;
1333     if(index > 0) return index; // fixed nmber
1334     if(index == 0) return 1;
1335     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1336     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1337     return res;
1338 }
1339
1340 int
1341 LoadGameOrPosition(int gameNr)
1342 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1343     if (*appData.loadGameFile != NULLCHAR) {
1344         if (!LoadGameFromFile(appData.loadGameFile,
1345                 CalculateIndex(appData.loadGameIndex, gameNr),
1346                               appData.loadGameFile, FALSE)) {
1347             DisplayFatalError(_("Bad game file"), 0, 1);
1348             return 0;
1349         }
1350     } else if (*appData.loadPositionFile != NULLCHAR) {
1351         if (!LoadPositionFromFile(appData.loadPositionFile,
1352                 CalculateIndex(appData.loadPositionIndex, gameNr),
1353                                   appData.loadPositionFile)) {
1354             DisplayFatalError(_("Bad position file"), 0, 1);
1355             return 0;
1356         }
1357     }
1358     return 1;
1359 }
1360
1361 void
1362 ReserveGame(int gameNr, char resChar)
1363 {
1364     FILE *tf = fopen(appData.tourneyFile, "r+");
1365     char *p, *q, c, buf[MSG_SIZ];
1366     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1367     safeStrCpy(buf, lastMsg, MSG_SIZ);
1368     DisplayMessage(_("Pick new game"), "");
1369     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1370     ParseArgsFromFile(tf);
1371     p = q = appData.results;
1372     if(appData.debugMode) {
1373       char *r = appData.participants;
1374       fprintf(debugFP, "results = '%s'\n", p);
1375       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1376       fprintf(debugFP, "\n");
1377     }
1378     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1379     nextGame = q - p;
1380     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1381     safeStrCpy(q, p, strlen(p) + 2);
1382     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1383     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1384     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1385         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1386         q[nextGame] = '*';
1387     }
1388     fseek(tf, -(strlen(p)+4), SEEK_END);
1389     c = fgetc(tf);
1390     if(c != '"') // depending on DOS or Unix line endings we can be one off
1391          fseek(tf, -(strlen(p)+2), SEEK_END);
1392     else fseek(tf, -(strlen(p)+3), SEEK_END);
1393     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1394     DisplayMessage(buf, "");
1395     free(p); appData.results = q;
1396     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1397        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1398         UnloadEngine(&first);  // next game belongs to other pairing;
1399         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1400     }
1401 }
1402
1403 void
1404 MatchEvent(int mode)
1405 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1406         int dummy;
1407         if(matchMode) { // already in match mode: switch it off
1408             abortMatch = TRUE;
1409             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1410             return;
1411         }
1412 //      if(gameMode != BeginningOfGame) {
1413 //          DisplayError(_("You can only start a match from the initial position."), 0);
1414 //          return;
1415 //      }
1416         abortMatch = FALSE;
1417         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1418         /* Set up machine vs. machine match */
1419         nextGame = 0;
1420         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1421         if(appData.tourneyFile[0]) {
1422             ReserveGame(-1, 0);
1423             if(nextGame > appData.matchGames) {
1424                 char buf[MSG_SIZ];
1425                 if(strchr(appData.results, '*') == NULL) {
1426                     FILE *f;
1427                     appData.tourneyCycles++;
1428                     if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1429                         fclose(f);
1430                         NextTourneyGame(-1, &dummy);
1431                         ReserveGame(-1, 0);
1432                         if(nextGame <= appData.matchGames) {
1433                             DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1434                             matchMode = mode;
1435                             ScheduleDelayedEvent(NextMatchGame, 10000);
1436                             return;
1437                         }
1438                     }
1439                 }
1440                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1441                 DisplayError(buf, 0);
1442                 appData.tourneyFile[0] = 0;
1443                 return;
1444             }
1445         } else
1446         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1447             DisplayFatalError(_("Can't have a match with no chess programs"),
1448                               0, 2);
1449             return;
1450         }
1451         matchMode = mode;
1452         matchGame = roundNr = 1;
1453         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1454         NextMatchGame();
1455 }
1456
1457 void
1458 InitBackEnd3 P((void))
1459 {
1460     GameMode initialMode;
1461     char buf[MSG_SIZ];
1462     int err, len;
1463
1464     InitChessProgram(&first, startedFromSetupPosition);
1465
1466     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1467         free(programVersion);
1468         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1469         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1470     }
1471
1472     if (appData.icsActive) {
1473 #ifdef WIN32
1474         /* [DM] Make a console window if needed [HGM] merged ifs */
1475         ConsoleCreate();
1476 #endif
1477         err = establish();
1478         if (err != 0)
1479           {
1480             if (*appData.icsCommPort != NULLCHAR)
1481               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1482                              appData.icsCommPort);
1483             else
1484               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1485                         appData.icsHost, appData.icsPort);
1486
1487             if( (len > MSG_SIZ) && appData.debugMode )
1488               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1489
1490             DisplayFatalError(buf, err, 1);
1491             return;
1492         }
1493         SetICSMode();
1494         telnetISR =
1495           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1496         fromUserISR =
1497           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1498         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1499             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1500     } else if (appData.noChessProgram) {
1501         SetNCPMode();
1502     } else {
1503         SetGNUMode();
1504     }
1505
1506     if (*appData.cmailGameName != NULLCHAR) {
1507         SetCmailMode();
1508         OpenLoopback(&cmailPR);
1509         cmailISR =
1510           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1511     }
1512
1513     ThawUI();
1514     DisplayMessage("", "");
1515     if (StrCaseCmp(appData.initialMode, "") == 0) {
1516       initialMode = BeginningOfGame;
1517       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1518         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1519         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1520         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1521         ModeHighlight();
1522       }
1523     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1524       initialMode = TwoMachinesPlay;
1525     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1526       initialMode = AnalyzeFile;
1527     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1528       initialMode = AnalyzeMode;
1529     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1530       initialMode = MachinePlaysWhite;
1531     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1532       initialMode = MachinePlaysBlack;
1533     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1534       initialMode = EditGame;
1535     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1536       initialMode = EditPosition;
1537     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1538       initialMode = Training;
1539     } else {
1540       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1541       if( (len > MSG_SIZ) && appData.debugMode )
1542         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1543
1544       DisplayFatalError(buf, 0, 2);
1545       return;
1546     }
1547
1548     if (appData.matchMode) {
1549         if(appData.tourneyFile[0]) { // start tourney from command line
1550             FILE *f;
1551             if(f = fopen(appData.tourneyFile, "r")) {
1552                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1553                 fclose(f);
1554                 appData.clockMode = TRUE;
1555                 SetGNUMode();
1556             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1557         }
1558         MatchEvent(TRUE);
1559     } else if (*appData.cmailGameName != NULLCHAR) {
1560         /* Set up cmail mode */
1561         ReloadCmailMsgEvent(TRUE);
1562     } else {
1563         /* Set up other modes */
1564         if (initialMode == AnalyzeFile) {
1565           if (*appData.loadGameFile == NULLCHAR) {
1566             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1567             return;
1568           }
1569         }
1570         if (*appData.loadGameFile != NULLCHAR) {
1571             (void) LoadGameFromFile(appData.loadGameFile,
1572                                     appData.loadGameIndex,
1573                                     appData.loadGameFile, TRUE);
1574         } else if (*appData.loadPositionFile != NULLCHAR) {
1575             (void) LoadPositionFromFile(appData.loadPositionFile,
1576                                         appData.loadPositionIndex,
1577                                         appData.loadPositionFile);
1578             /* [HGM] try to make self-starting even after FEN load */
1579             /* to allow automatic setup of fairy variants with wtm */
1580             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1581                 gameMode = BeginningOfGame;
1582                 setboardSpoiledMachineBlack = 1;
1583             }
1584             /* [HGM] loadPos: make that every new game uses the setup */
1585             /* from file as long as we do not switch variant          */
1586             if(!blackPlaysFirst) {
1587                 startedFromPositionFile = TRUE;
1588                 CopyBoard(filePosition, boards[0]);
1589             }
1590         }
1591         if (initialMode == AnalyzeMode) {
1592           if (appData.noChessProgram) {
1593             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1594             return;
1595           }
1596           if (appData.icsActive) {
1597             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1598             return;
1599           }
1600           AnalyzeModeEvent();
1601         } else if (initialMode == AnalyzeFile) {
1602           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1603           ShowThinkingEvent();
1604           AnalyzeFileEvent();
1605           AnalysisPeriodicEvent(1);
1606         } else if (initialMode == MachinePlaysWhite) {
1607           if (appData.noChessProgram) {
1608             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1609                               0, 2);
1610             return;
1611           }
1612           if (appData.icsActive) {
1613             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1614                               0, 2);
1615             return;
1616           }
1617           MachineWhiteEvent();
1618         } else if (initialMode == MachinePlaysBlack) {
1619           if (appData.noChessProgram) {
1620             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1621                               0, 2);
1622             return;
1623           }
1624           if (appData.icsActive) {
1625             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1626                               0, 2);
1627             return;
1628           }
1629           MachineBlackEvent();
1630         } else if (initialMode == TwoMachinesPlay) {
1631           if (appData.noChessProgram) {
1632             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1633                               0, 2);
1634             return;
1635           }
1636           if (appData.icsActive) {
1637             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1638                               0, 2);
1639             return;
1640           }
1641           TwoMachinesEvent();
1642         } else if (initialMode == EditGame) {
1643           EditGameEvent();
1644         } else if (initialMode == EditPosition) {
1645           EditPositionEvent();
1646         } else if (initialMode == Training) {
1647           if (*appData.loadGameFile == NULLCHAR) {
1648             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1649             return;
1650           }
1651           TrainingEvent();
1652         }
1653     }
1654 }
1655
1656 /*
1657  * Establish will establish a contact to a remote host.port.
1658  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1659  *  used to talk to the host.
1660  * Returns 0 if okay, error code if not.
1661  */
1662 int
1663 establish()
1664 {
1665     char buf[MSG_SIZ];
1666
1667     if (*appData.icsCommPort != NULLCHAR) {
1668         /* Talk to the host through a serial comm port */
1669         return OpenCommPort(appData.icsCommPort, &icsPR);
1670
1671     } else if (*appData.gateway != NULLCHAR) {
1672         if (*appData.remoteShell == NULLCHAR) {
1673             /* Use the rcmd protocol to run telnet program on a gateway host */
1674             snprintf(buf, sizeof(buf), "%s %s %s",
1675                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1676             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1677
1678         } else {
1679             /* Use the rsh program to run telnet program on a gateway host */
1680             if (*appData.remoteUser == NULLCHAR) {
1681                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1682                         appData.gateway, appData.telnetProgram,
1683                         appData.icsHost, appData.icsPort);
1684             } else {
1685                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1686                         appData.remoteShell, appData.gateway,
1687                         appData.remoteUser, appData.telnetProgram,
1688                         appData.icsHost, appData.icsPort);
1689             }
1690             return StartChildProcess(buf, "", &icsPR);
1691
1692         }
1693     } else if (appData.useTelnet) {
1694         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1695
1696     } else {
1697         /* TCP socket interface differs somewhat between
1698            Unix and NT; handle details in the front end.
1699            */
1700         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1701     }
1702 }
1703
1704 void EscapeExpand(char *p, char *q)
1705 {       // [HGM] initstring: routine to shape up string arguments
1706         while(*p++ = *q++) if(p[-1] == '\\')
1707             switch(*q++) {
1708                 case 'n': p[-1] = '\n'; break;
1709                 case 'r': p[-1] = '\r'; break;
1710                 case 't': p[-1] = '\t'; break;
1711                 case '\\': p[-1] = '\\'; break;
1712                 case 0: *p = 0; return;
1713                 default: p[-1] = q[-1]; break;
1714             }
1715 }
1716
1717 void
1718 show_bytes(fp, buf, count)
1719      FILE *fp;
1720      char *buf;
1721      int count;
1722 {
1723     while (count--) {
1724         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1725             fprintf(fp, "\\%03o", *buf & 0xff);
1726         } else {
1727             putc(*buf, fp);
1728         }
1729         buf++;
1730     }
1731     fflush(fp);
1732 }
1733
1734 /* Returns an errno value */
1735 int
1736 OutputMaybeTelnet(pr, message, count, outError)
1737      ProcRef pr;
1738      char *message;
1739      int count;
1740      int *outError;
1741 {
1742     char buf[8192], *p, *q, *buflim;
1743     int left, newcount, outcount;
1744
1745     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1746         *appData.gateway != NULLCHAR) {
1747         if (appData.debugMode) {
1748             fprintf(debugFP, ">ICS: ");
1749             show_bytes(debugFP, message, count);
1750             fprintf(debugFP, "\n");
1751         }
1752         return OutputToProcess(pr, message, count, outError);
1753     }
1754
1755     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1756     p = message;
1757     q = buf;
1758     left = count;
1759     newcount = 0;
1760     while (left) {
1761         if (q >= buflim) {
1762             if (appData.debugMode) {
1763                 fprintf(debugFP, ">ICS: ");
1764                 show_bytes(debugFP, buf, newcount);
1765                 fprintf(debugFP, "\n");
1766             }
1767             outcount = OutputToProcess(pr, buf, newcount, outError);
1768             if (outcount < newcount) return -1; /* to be sure */
1769             q = buf;
1770             newcount = 0;
1771         }
1772         if (*p == '\n') {
1773             *q++ = '\r';
1774             newcount++;
1775         } else if (((unsigned char) *p) == TN_IAC) {
1776             *q++ = (char) TN_IAC;
1777             newcount ++;
1778         }
1779         *q++ = *p++;
1780         newcount++;
1781         left--;
1782     }
1783     if (appData.debugMode) {
1784         fprintf(debugFP, ">ICS: ");
1785         show_bytes(debugFP, buf, newcount);
1786         fprintf(debugFP, "\n");
1787     }
1788     outcount = OutputToProcess(pr, buf, newcount, outError);
1789     if (outcount < newcount) return -1; /* to be sure */
1790     return count;
1791 }
1792
1793 void
1794 read_from_player(isr, closure, message, count, error)
1795      InputSourceRef isr;
1796      VOIDSTAR closure;
1797      char *message;
1798      int count;
1799      int error;
1800 {
1801     int outError, outCount;
1802     static int gotEof = 0;
1803
1804     /* Pass data read from player on to ICS */
1805     if (count > 0) {
1806         gotEof = 0;
1807         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1808         if (outCount < count) {
1809             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1810         }
1811     } else if (count < 0) {
1812         RemoveInputSource(isr);
1813         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1814     } else if (gotEof++ > 0) {
1815         RemoveInputSource(isr);
1816         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1817     }
1818 }
1819
1820 void
1821 KeepAlive()
1822 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1823     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1824     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1825     SendToICS("date\n");
1826     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1827 }
1828
1829 /* added routine for printf style output to ics */
1830 void ics_printf(char *format, ...)
1831 {
1832     char buffer[MSG_SIZ];
1833     va_list args;
1834
1835     va_start(args, format);
1836     vsnprintf(buffer, sizeof(buffer), format, args);
1837     buffer[sizeof(buffer)-1] = '\0';
1838     SendToICS(buffer);
1839     va_end(args);
1840 }
1841
1842 void
1843 SendToICS(s)
1844      char *s;
1845 {
1846     int count, outCount, outError;
1847
1848     if (icsPR == NULL) return;
1849
1850     count = strlen(s);
1851     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1852     if (outCount < count) {
1853         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854     }
1855 }
1856
1857 /* This is used for sending logon scripts to the ICS. Sending
1858    without a delay causes problems when using timestamp on ICC
1859    (at least on my machine). */
1860 void
1861 SendToICSDelayed(s,msdelay)
1862      char *s;
1863      long msdelay;
1864 {
1865     int count, outCount, outError;
1866
1867     if (icsPR == NULL) return;
1868
1869     count = strlen(s);
1870     if (appData.debugMode) {
1871         fprintf(debugFP, ">ICS: ");
1872         show_bytes(debugFP, s, count);
1873         fprintf(debugFP, "\n");
1874     }
1875     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1876                                       msdelay);
1877     if (outCount < count) {
1878         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1879     }
1880 }
1881
1882
1883 /* Remove all highlighting escape sequences in s
1884    Also deletes any suffix starting with '('
1885    */
1886 char *
1887 StripHighlightAndTitle(s)
1888      char *s;
1889 {
1890     static char retbuf[MSG_SIZ];
1891     char *p = retbuf;
1892
1893     while (*s != NULLCHAR) {
1894         while (*s == '\033') {
1895             while (*s != NULLCHAR && !isalpha(*s)) s++;
1896             if (*s != NULLCHAR) s++;
1897         }
1898         while (*s != NULLCHAR && *s != '\033') {
1899             if (*s == '(' || *s == '[') {
1900                 *p = NULLCHAR;
1901                 return retbuf;
1902             }
1903             *p++ = *s++;
1904         }
1905     }
1906     *p = NULLCHAR;
1907     return retbuf;
1908 }
1909
1910 /* Remove all highlighting escape sequences in s */
1911 char *
1912 StripHighlight(s)
1913      char *s;
1914 {
1915     static char retbuf[MSG_SIZ];
1916     char *p = retbuf;
1917
1918     while (*s != NULLCHAR) {
1919         while (*s == '\033') {
1920             while (*s != NULLCHAR && !isalpha(*s)) s++;
1921             if (*s != NULLCHAR) s++;
1922         }
1923         while (*s != NULLCHAR && *s != '\033') {
1924             *p++ = *s++;
1925         }
1926     }
1927     *p = NULLCHAR;
1928     return retbuf;
1929 }
1930
1931 char *variantNames[] = VARIANT_NAMES;
1932 char *
1933 VariantName(v)
1934      VariantClass v;
1935 {
1936     return variantNames[v];
1937 }
1938
1939
1940 /* Identify a variant from the strings the chess servers use or the
1941    PGN Variant tag names we use. */
1942 VariantClass
1943 StringToVariant(e)
1944      char *e;
1945 {
1946     char *p;
1947     int wnum = -1;
1948     VariantClass v = VariantNormal;
1949     int i, found = FALSE;
1950     char buf[MSG_SIZ];
1951     int len;
1952
1953     if (!e) return v;
1954
1955     /* [HGM] skip over optional board-size prefixes */
1956     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1957         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1958         while( *e++ != '_');
1959     }
1960
1961     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1962         v = VariantNormal;
1963         found = TRUE;
1964     } else
1965     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1966       if (StrCaseStr(e, variantNames[i])) {
1967         v = (VariantClass) i;
1968         found = TRUE;
1969         break;
1970       }
1971     }
1972
1973     if (!found) {
1974       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1975           || StrCaseStr(e, "wild/fr")
1976           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1977         v = VariantFischeRandom;
1978       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1979                  (i = 1, p = StrCaseStr(e, "w"))) {
1980         p += i;
1981         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1982         if (isdigit(*p)) {
1983           wnum = atoi(p);
1984         } else {
1985           wnum = -1;
1986         }
1987         switch (wnum) {
1988         case 0: /* FICS only, actually */
1989         case 1:
1990           /* Castling legal even if K starts on d-file */
1991           v = VariantWildCastle;
1992           break;
1993         case 2:
1994         case 3:
1995         case 4:
1996           /* Castling illegal even if K & R happen to start in
1997              normal positions. */
1998           v = VariantNoCastle;
1999           break;
2000         case 5:
2001         case 7:
2002         case 8:
2003         case 10:
2004         case 11:
2005         case 12:
2006         case 13:
2007         case 14:
2008         case 15:
2009         case 18:
2010         case 19:
2011           /* Castling legal iff K & R start in normal positions */
2012           v = VariantNormal;
2013           break;
2014         case 6:
2015         case 20:
2016         case 21:
2017           /* Special wilds for position setup; unclear what to do here */
2018           v = VariantLoadable;
2019           break;
2020         case 9:
2021           /* Bizarre ICC game */
2022           v = VariantTwoKings;
2023           break;
2024         case 16:
2025           v = VariantKriegspiel;
2026           break;
2027         case 17:
2028           v = VariantLosers;
2029           break;
2030         case 22:
2031           v = VariantFischeRandom;
2032           break;
2033         case 23:
2034           v = VariantCrazyhouse;
2035           break;
2036         case 24:
2037           v = VariantBughouse;
2038           break;
2039         case 25:
2040           v = Variant3Check;
2041           break;
2042         case 26:
2043           /* Not quite the same as FICS suicide! */
2044           v = VariantGiveaway;
2045           break;
2046         case 27:
2047           v = VariantAtomic;
2048           break;
2049         case 28:
2050           v = VariantShatranj;
2051           break;
2052
2053         /* Temporary names for future ICC types.  The name *will* change in
2054            the next xboard/WinBoard release after ICC defines it. */
2055         case 29:
2056           v = Variant29;
2057           break;
2058         case 30:
2059           v = Variant30;
2060           break;
2061         case 31:
2062           v = Variant31;
2063           break;
2064         case 32:
2065           v = Variant32;
2066           break;
2067         case 33:
2068           v = Variant33;
2069           break;
2070         case 34:
2071           v = Variant34;
2072           break;
2073         case 35:
2074           v = Variant35;
2075           break;
2076         case 36:
2077           v = Variant36;
2078           break;
2079         case 37:
2080           v = VariantShogi;
2081           break;
2082         case 38:
2083           v = VariantXiangqi;
2084           break;
2085         case 39:
2086           v = VariantCourier;
2087           break;
2088         case 40:
2089           v = VariantGothic;
2090           break;
2091         case 41:
2092           v = VariantCapablanca;
2093           break;
2094         case 42:
2095           v = VariantKnightmate;
2096           break;
2097         case 43:
2098           v = VariantFairy;
2099           break;
2100         case 44:
2101           v = VariantCylinder;
2102           break;
2103         case 45:
2104           v = VariantFalcon;
2105           break;
2106         case 46:
2107           v = VariantCapaRandom;
2108           break;
2109         case 47:
2110           v = VariantBerolina;
2111           break;
2112         case 48:
2113           v = VariantJanus;
2114           break;
2115         case 49:
2116           v = VariantSuper;
2117           break;
2118         case 50:
2119           v = VariantGreat;
2120           break;
2121         case -1:
2122           /* Found "wild" or "w" in the string but no number;
2123              must assume it's normal chess. */
2124           v = VariantNormal;
2125           break;
2126         default:
2127           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2128           if( (len > MSG_SIZ) && appData.debugMode )
2129             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2130
2131           DisplayError(buf, 0);
2132           v = VariantUnknown;
2133           break;
2134         }
2135       }
2136     }
2137     if (appData.debugMode) {
2138       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2139               e, wnum, VariantName(v));
2140     }
2141     return v;
2142 }
2143
2144 static int leftover_start = 0, leftover_len = 0;
2145 char star_match[STAR_MATCH_N][MSG_SIZ];
2146
2147 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2148    advance *index beyond it, and set leftover_start to the new value of
2149    *index; else return FALSE.  If pattern contains the character '*', it
2150    matches any sequence of characters not containing '\r', '\n', or the
2151    character following the '*' (if any), and the matched sequence(s) are
2152    copied into star_match.
2153    */
2154 int
2155 looking_at(buf, index, pattern)
2156      char *buf;
2157      int *index;
2158      char *pattern;
2159 {
2160     char *bufp = &buf[*index], *patternp = pattern;
2161     int star_count = 0;
2162     char *matchp = star_match[0];
2163
2164     for (;;) {
2165         if (*patternp == NULLCHAR) {
2166             *index = leftover_start = bufp - buf;
2167             *matchp = NULLCHAR;
2168             return TRUE;
2169         }
2170         if (*bufp == NULLCHAR) return FALSE;
2171         if (*patternp == '*') {
2172             if (*bufp == *(patternp + 1)) {
2173                 *matchp = NULLCHAR;
2174                 matchp = star_match[++star_count];
2175                 patternp += 2;
2176                 bufp++;
2177                 continue;
2178             } else if (*bufp == '\n' || *bufp == '\r') {
2179                 patternp++;
2180                 if (*patternp == NULLCHAR)
2181                   continue;
2182                 else
2183                   return FALSE;
2184             } else {
2185                 *matchp++ = *bufp++;
2186                 continue;
2187             }
2188         }
2189         if (*patternp != *bufp) return FALSE;
2190         patternp++;
2191         bufp++;
2192     }
2193 }
2194
2195 void
2196 SendToPlayer(data, length)
2197      char *data;
2198      int length;
2199 {
2200     int error, outCount;
2201     outCount = OutputToProcess(NoProc, data, length, &error);
2202     if (outCount < length) {
2203         DisplayFatalError(_("Error writing to display"), error, 1);
2204     }
2205 }
2206
2207 void
2208 PackHolding(packed, holding)
2209      char packed[];
2210      char *holding;
2211 {
2212     char *p = holding;
2213     char *q = packed;
2214     int runlength = 0;
2215     int curr = 9999;
2216     do {
2217         if (*p == curr) {
2218             runlength++;
2219         } else {
2220             switch (runlength) {
2221               case 0:
2222                 break;
2223               case 1:
2224                 *q++ = curr;
2225                 break;
2226               case 2:
2227                 *q++ = curr;
2228                 *q++ = curr;
2229                 break;
2230               default:
2231                 sprintf(q, "%d", runlength);
2232                 while (*q) q++;
2233                 *q++ = curr;
2234                 break;
2235             }
2236             runlength = 1;
2237             curr = *p;
2238         }
2239     } while (*p++);
2240     *q = NULLCHAR;
2241 }
2242
2243 /* Telnet protocol requests from the front end */
2244 void
2245 TelnetRequest(ddww, option)
2246      unsigned char ddww, option;
2247 {
2248     unsigned char msg[3];
2249     int outCount, outError;
2250
2251     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2252
2253     if (appData.debugMode) {
2254         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2255         switch (ddww) {
2256           case TN_DO:
2257             ddwwStr = "DO";
2258             break;
2259           case TN_DONT:
2260             ddwwStr = "DONT";
2261             break;
2262           case TN_WILL:
2263             ddwwStr = "WILL";
2264             break;
2265           case TN_WONT:
2266             ddwwStr = "WONT";
2267             break;
2268           default:
2269             ddwwStr = buf1;
2270             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2271             break;
2272         }
2273         switch (option) {
2274           case TN_ECHO:
2275             optionStr = "ECHO";
2276             break;
2277           default:
2278             optionStr = buf2;
2279             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2280             break;
2281         }
2282         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2283     }
2284     msg[0] = TN_IAC;
2285     msg[1] = ddww;
2286     msg[2] = option;
2287     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2288     if (outCount < 3) {
2289         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2290     }
2291 }
2292
2293 void
2294 DoEcho()
2295 {
2296     if (!appData.icsActive) return;
2297     TelnetRequest(TN_DO, TN_ECHO);
2298 }
2299
2300 void
2301 DontEcho()
2302 {
2303     if (!appData.icsActive) return;
2304     TelnetRequest(TN_DONT, TN_ECHO);
2305 }
2306
2307 void
2308 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2309 {
2310     /* put the holdings sent to us by the server on the board holdings area */
2311     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2312     char p;
2313     ChessSquare piece;
2314
2315     if(gameInfo.holdingsWidth < 2)  return;
2316     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2317         return; // prevent overwriting by pre-board holdings
2318
2319     if( (int)lowestPiece >= BlackPawn ) {
2320         holdingsColumn = 0;
2321         countsColumn = 1;
2322         holdingsStartRow = BOARD_HEIGHT-1;
2323         direction = -1;
2324     } else {
2325         holdingsColumn = BOARD_WIDTH-1;
2326         countsColumn = BOARD_WIDTH-2;
2327         holdingsStartRow = 0;
2328         direction = 1;
2329     }
2330
2331     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2332         board[i][holdingsColumn] = EmptySquare;
2333         board[i][countsColumn]   = (ChessSquare) 0;
2334     }
2335     while( (p=*holdings++) != NULLCHAR ) {
2336         piece = CharToPiece( ToUpper(p) );
2337         if(piece == EmptySquare) continue;
2338         /*j = (int) piece - (int) WhitePawn;*/
2339         j = PieceToNumber(piece);
2340         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2341         if(j < 0) continue;               /* should not happen */
2342         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2343         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2344         board[holdingsStartRow+j*direction][countsColumn]++;
2345     }
2346 }
2347
2348
2349 void
2350 VariantSwitch(Board board, VariantClass newVariant)
2351 {
2352    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2353    static Board oldBoard;
2354
2355    startedFromPositionFile = FALSE;
2356    if(gameInfo.variant == newVariant) return;
2357
2358    /* [HGM] This routine is called each time an assignment is made to
2359     * gameInfo.variant during a game, to make sure the board sizes
2360     * are set to match the new variant. If that means adding or deleting
2361     * holdings, we shift the playing board accordingly
2362     * This kludge is needed because in ICS observe mode, we get boards
2363     * of an ongoing game without knowing the variant, and learn about the
2364     * latter only later. This can be because of the move list we requested,
2365     * in which case the game history is refilled from the beginning anyway,
2366     * but also when receiving holdings of a crazyhouse game. In the latter
2367     * case we want to add those holdings to the already received position.
2368     */
2369
2370
2371    if (appData.debugMode) {
2372      fprintf(debugFP, "Switch board from %s to %s\n",
2373              VariantName(gameInfo.variant), VariantName(newVariant));
2374      setbuf(debugFP, NULL);
2375    }
2376    shuffleOpenings = 0;       /* [HGM] shuffle */
2377    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2378    switch(newVariant)
2379      {
2380      case VariantShogi:
2381        newWidth = 9;  newHeight = 9;
2382        gameInfo.holdingsSize = 7;
2383      case VariantBughouse:
2384      case VariantCrazyhouse:
2385        newHoldingsWidth = 2; break;
2386      case VariantGreat:
2387        newWidth = 10;
2388      case VariantSuper:
2389        newHoldingsWidth = 2;
2390        gameInfo.holdingsSize = 8;
2391        break;
2392      case VariantGothic:
2393      case VariantCapablanca:
2394      case VariantCapaRandom:
2395        newWidth = 10;
2396      default:
2397        newHoldingsWidth = gameInfo.holdingsSize = 0;
2398      };
2399
2400    if(newWidth  != gameInfo.boardWidth  ||
2401       newHeight != gameInfo.boardHeight ||
2402       newHoldingsWidth != gameInfo.holdingsWidth ) {
2403
2404      /* shift position to new playing area, if needed */
2405      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2406        for(i=0; i<BOARD_HEIGHT; i++)
2407          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2408            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2409              board[i][j];
2410        for(i=0; i<newHeight; i++) {
2411          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2412          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2413        }
2414      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2415        for(i=0; i<BOARD_HEIGHT; i++)
2416          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2417            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2418              board[i][j];
2419      }
2420      gameInfo.boardWidth  = newWidth;
2421      gameInfo.boardHeight = newHeight;
2422      gameInfo.holdingsWidth = newHoldingsWidth;
2423      gameInfo.variant = newVariant;
2424      InitDrawingSizes(-2, 0);
2425    } else gameInfo.variant = newVariant;
2426    CopyBoard(oldBoard, board);   // remember correctly formatted board
2427      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2428    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2429 }
2430
2431 static int loggedOn = FALSE;
2432
2433 /*-- Game start info cache: --*/
2434 int gs_gamenum;
2435 char gs_kind[MSG_SIZ];
2436 static char player1Name[128] = "";
2437 static char player2Name[128] = "";
2438 static char cont_seq[] = "\n\\   ";
2439 static int player1Rating = -1;
2440 static int player2Rating = -1;
2441 /*----------------------------*/
2442
2443 ColorClass curColor = ColorNormal;
2444 int suppressKibitz = 0;
2445
2446 // [HGM] seekgraph
2447 Boolean soughtPending = FALSE;
2448 Boolean seekGraphUp;
2449 #define MAX_SEEK_ADS 200
2450 #define SQUARE 0x80
2451 char *seekAdList[MAX_SEEK_ADS];
2452 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2453 float tcList[MAX_SEEK_ADS];
2454 char colorList[MAX_SEEK_ADS];
2455 int nrOfSeekAds = 0;
2456 int minRating = 1010, maxRating = 2800;
2457 int hMargin = 10, vMargin = 20, h, w;
2458 extern int squareSize, lineGap;
2459
2460 void
2461 PlotSeekAd(int i)
2462 {
2463         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2464         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2465         if(r < minRating+100 && r >=0 ) r = minRating+100;
2466         if(r > maxRating) r = maxRating;
2467         if(tc < 1.) tc = 1.;
2468         if(tc > 95.) tc = 95.;
2469         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2470         y = ((double)r - minRating)/(maxRating - minRating)
2471             * (h-vMargin-squareSize/8-1) + vMargin;
2472         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2473         if(strstr(seekAdList[i], " u ")) color = 1;
2474         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2475            !strstr(seekAdList[i], "bullet") &&
2476            !strstr(seekAdList[i], "blitz") &&
2477            !strstr(seekAdList[i], "standard") ) color = 2;
2478         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2479         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2480 }
2481
2482 void
2483 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2484 {
2485         char buf[MSG_SIZ], *ext = "";
2486         VariantClass v = StringToVariant(type);
2487         if(strstr(type, "wild")) {
2488             ext = type + 4; // append wild number
2489             if(v == VariantFischeRandom) type = "chess960"; else
2490             if(v == VariantLoadable) type = "setup"; else
2491             type = VariantName(v);
2492         }
2493         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2494         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2495             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2496             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2497             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2498             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2499             seekNrList[nrOfSeekAds] = nr;
2500             zList[nrOfSeekAds] = 0;
2501             seekAdList[nrOfSeekAds++] = StrSave(buf);
2502             if(plot) PlotSeekAd(nrOfSeekAds-1);
2503         }
2504 }
2505
2506 void
2507 EraseSeekDot(int i)
2508 {
2509     int x = xList[i], y = yList[i], d=squareSize/4, k;
2510     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2511     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2512     // now replot every dot that overlapped
2513     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2514         int xx = xList[k], yy = yList[k];
2515         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2516             DrawSeekDot(xx, yy, colorList[k]);
2517     }
2518 }
2519
2520 void
2521 RemoveSeekAd(int nr)
2522 {
2523         int i;
2524         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2525             EraseSeekDot(i);
2526             if(seekAdList[i]) free(seekAdList[i]);
2527             seekAdList[i] = seekAdList[--nrOfSeekAds];
2528             seekNrList[i] = seekNrList[nrOfSeekAds];
2529             ratingList[i] = ratingList[nrOfSeekAds];
2530             colorList[i]  = colorList[nrOfSeekAds];
2531             tcList[i] = tcList[nrOfSeekAds];
2532             xList[i]  = xList[nrOfSeekAds];
2533             yList[i]  = yList[nrOfSeekAds];
2534             zList[i]  = zList[nrOfSeekAds];
2535             seekAdList[nrOfSeekAds] = NULL;
2536             break;
2537         }
2538 }
2539
2540 Boolean
2541 MatchSoughtLine(char *line)
2542 {
2543     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2544     int nr, base, inc, u=0; char dummy;
2545
2546     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2547        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2548        (u=1) &&
2549        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2550         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2551         // match: compact and save the line
2552         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2553         return TRUE;
2554     }
2555     return FALSE;
2556 }
2557
2558 int
2559 DrawSeekGraph()
2560 {
2561     int i;
2562     if(!seekGraphUp) return FALSE;
2563     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2564     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2565
2566     DrawSeekBackground(0, 0, w, h);
2567     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2568     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2569     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2570         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2571         yy = h-1-yy;
2572         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2573         if(i%500 == 0) {
2574             char buf[MSG_SIZ];
2575             snprintf(buf, MSG_SIZ, "%d", i);
2576             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2577         }
2578     }
2579     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2580     for(i=1; i<100; i+=(i<10?1:5)) {
2581         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2582         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2583         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2584             char buf[MSG_SIZ];
2585             snprintf(buf, MSG_SIZ, "%d", i);
2586             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2587         }
2588     }
2589     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2590     return TRUE;
2591 }
2592
2593 int SeekGraphClick(ClickType click, int x, int y, int moving)
2594 {
2595     static int lastDown = 0, displayed = 0, lastSecond;
2596     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2597         if(click == Release || moving) return FALSE;
2598         nrOfSeekAds = 0;
2599         soughtPending = TRUE;
2600         SendToICS(ics_prefix);
2601         SendToICS("sought\n"); // should this be "sought all"?
2602     } else { // issue challenge based on clicked ad
2603         int dist = 10000; int i, closest = 0, second = 0;
2604         for(i=0; i<nrOfSeekAds; i++) {
2605             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2606             if(d < dist) { dist = d; closest = i; }
2607             second += (d - zList[i] < 120); // count in-range ads
2608             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2609         }
2610         if(dist < 120) {
2611             char buf[MSG_SIZ];
2612             second = (second > 1);
2613             if(displayed != closest || second != lastSecond) {
2614                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2615                 lastSecond = second; displayed = closest;
2616             }
2617             if(click == Press) {
2618                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2619                 lastDown = closest;
2620                 return TRUE;
2621             } // on press 'hit', only show info
2622             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2623             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2624             SendToICS(ics_prefix);
2625             SendToICS(buf);
2626             return TRUE; // let incoming board of started game pop down the graph
2627         } else if(click == Release) { // release 'miss' is ignored
2628             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2629             if(moving == 2) { // right up-click
2630                 nrOfSeekAds = 0; // refresh graph
2631                 soughtPending = TRUE;
2632                 SendToICS(ics_prefix);
2633                 SendToICS("sought\n"); // should this be "sought all"?
2634             }
2635             return TRUE;
2636         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2637         // press miss or release hit 'pop down' seek graph
2638         seekGraphUp = FALSE;
2639         DrawPosition(TRUE, NULL);
2640     }
2641     return TRUE;
2642 }
2643
2644 void
2645 read_from_ics(isr, closure, data, count, error)
2646      InputSourceRef isr;
2647      VOIDSTAR closure;
2648      char *data;
2649      int count;
2650      int error;
2651 {
2652 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2653 #define STARTED_NONE 0
2654 #define STARTED_MOVES 1
2655 #define STARTED_BOARD 2
2656 #define STARTED_OBSERVE 3
2657 #define STARTED_HOLDINGS 4
2658 #define STARTED_CHATTER 5
2659 #define STARTED_COMMENT 6
2660 #define STARTED_MOVES_NOHIDE 7
2661
2662     static int started = STARTED_NONE;
2663     static char parse[20000];
2664     static int parse_pos = 0;
2665     static char buf[BUF_SIZE + 1];
2666     static int firstTime = TRUE, intfSet = FALSE;
2667     static ColorClass prevColor = ColorNormal;
2668     static int savingComment = FALSE;
2669     static int cmatch = 0; // continuation sequence match
2670     char *bp;
2671     char str[MSG_SIZ];
2672     int i, oldi;
2673     int buf_len;
2674     int next_out;
2675     int tkind;
2676     int backup;    /* [DM] For zippy color lines */
2677     char *p;
2678     char talker[MSG_SIZ]; // [HGM] chat
2679     int channel;
2680
2681     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2682
2683     if (appData.debugMode) {
2684       if (!error) {
2685         fprintf(debugFP, "<ICS: ");
2686         show_bytes(debugFP, data, count);
2687         fprintf(debugFP, "\n");
2688       }
2689     }
2690
2691     if (appData.debugMode) { int f = forwardMostMove;
2692         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2693                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2694                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2695     }
2696     if (count > 0) {
2697         /* If last read ended with a partial line that we couldn't parse,
2698            prepend it to the new read and try again. */
2699         if (leftover_len > 0) {
2700             for (i=0; i<leftover_len; i++)
2701               buf[i] = buf[leftover_start + i];
2702         }
2703
2704     /* copy new characters into the buffer */
2705     bp = buf + leftover_len;
2706     buf_len=leftover_len;
2707     for (i=0; i<count; i++)
2708     {
2709         // ignore these
2710         if (data[i] == '\r')
2711             continue;
2712
2713         // join lines split by ICS?
2714         if (!appData.noJoin)
2715         {
2716             /*
2717                 Joining just consists of finding matches against the
2718                 continuation sequence, and discarding that sequence
2719                 if found instead of copying it.  So, until a match
2720                 fails, there's nothing to do since it might be the
2721                 complete sequence, and thus, something we don't want
2722                 copied.
2723             */
2724             if (data[i] == cont_seq[cmatch])
2725             {
2726                 cmatch++;
2727                 if (cmatch == strlen(cont_seq))
2728                 {
2729                     cmatch = 0; // complete match.  just reset the counter
2730
2731                     /*
2732                         it's possible for the ICS to not include the space
2733                         at the end of the last word, making our [correct]
2734                         join operation fuse two separate words.  the server
2735                         does this when the space occurs at the width setting.
2736                     */
2737                     if (!buf_len || buf[buf_len-1] != ' ')
2738                     {
2739                         *bp++ = ' ';
2740                         buf_len++;
2741                     }
2742                 }
2743                 continue;
2744             }
2745             else if (cmatch)
2746             {
2747                 /*
2748                     match failed, so we have to copy what matched before
2749                     falling through and copying this character.  In reality,
2750                     this will only ever be just the newline character, but
2751                     it doesn't hurt to be precise.
2752                 */
2753                 strncpy(bp, cont_seq, cmatch);
2754                 bp += cmatch;
2755                 buf_len += cmatch;
2756                 cmatch = 0;
2757             }
2758         }
2759
2760         // copy this char
2761         *bp++ = data[i];
2762         buf_len++;
2763     }
2764
2765         buf[buf_len] = NULLCHAR;
2766 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2767         next_out = 0;
2768         leftover_start = 0;
2769
2770         i = 0;
2771         while (i < buf_len) {
2772             /* Deal with part of the TELNET option negotiation
2773                protocol.  We refuse to do anything beyond the
2774                defaults, except that we allow the WILL ECHO option,
2775                which ICS uses to turn off password echoing when we are
2776                directly connected to it.  We reject this option
2777                if localLineEditing mode is on (always on in xboard)
2778                and we are talking to port 23, which might be a real
2779                telnet server that will try to keep WILL ECHO on permanently.
2780              */
2781             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2782                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2783                 unsigned char option;
2784                 oldi = i;
2785                 switch ((unsigned char) buf[++i]) {
2786                   case TN_WILL:
2787                     if (appData.debugMode)
2788                       fprintf(debugFP, "\n<WILL ");
2789                     switch (option = (unsigned char) buf[++i]) {
2790                       case TN_ECHO:
2791                         if (appData.debugMode)
2792                           fprintf(debugFP, "ECHO ");
2793                         /* Reply only if this is a change, according
2794                            to the protocol rules. */
2795                         if (remoteEchoOption) break;
2796                         if (appData.localLineEditing &&
2797                             atoi(appData.icsPort) == TN_PORT) {
2798                             TelnetRequest(TN_DONT, TN_ECHO);
2799                         } else {
2800                             EchoOff();
2801                             TelnetRequest(TN_DO, TN_ECHO);
2802                             remoteEchoOption = TRUE;
2803                         }
2804                         break;
2805                       default:
2806                         if (appData.debugMode)
2807                           fprintf(debugFP, "%d ", option);
2808                         /* Whatever this is, we don't want it. */
2809                         TelnetRequest(TN_DONT, option);
2810                         break;
2811                     }
2812                     break;
2813                   case TN_WONT:
2814                     if (appData.debugMode)
2815                       fprintf(debugFP, "\n<WONT ");
2816                     switch (option = (unsigned char) buf[++i]) {
2817                       case TN_ECHO:
2818                         if (appData.debugMode)
2819                           fprintf(debugFP, "ECHO ");
2820                         /* Reply only if this is a change, according
2821                            to the protocol rules. */
2822                         if (!remoteEchoOption) break;
2823                         EchoOn();
2824                         TelnetRequest(TN_DONT, TN_ECHO);
2825                         remoteEchoOption = FALSE;
2826                         break;
2827                       default:
2828                         if (appData.debugMode)
2829                           fprintf(debugFP, "%d ", (unsigned char) option);
2830                         /* Whatever this is, it must already be turned
2831                            off, because we never agree to turn on
2832                            anything non-default, so according to the
2833                            protocol rules, we don't reply. */
2834                         break;
2835                     }
2836                     break;
2837                   case TN_DO:
2838                     if (appData.debugMode)
2839                       fprintf(debugFP, "\n<DO ");
2840                     switch (option = (unsigned char) buf[++i]) {
2841                       default:
2842                         /* Whatever this is, we refuse to do it. */
2843                         if (appData.debugMode)
2844                           fprintf(debugFP, "%d ", option);
2845                         TelnetRequest(TN_WONT, option);
2846                         break;
2847                     }
2848                     break;
2849                   case TN_DONT:
2850                     if (appData.debugMode)
2851                       fprintf(debugFP, "\n<DONT ");
2852                     switch (option = (unsigned char) buf[++i]) {
2853                       default:
2854                         if (appData.debugMode)
2855                           fprintf(debugFP, "%d ", option);
2856                         /* Whatever this is, we are already not doing
2857                            it, because we never agree to do anything
2858                            non-default, so according to the protocol
2859                            rules, we don't reply. */
2860                         break;
2861                     }
2862                     break;
2863                   case TN_IAC:
2864                     if (appData.debugMode)
2865                       fprintf(debugFP, "\n<IAC ");
2866                     /* Doubled IAC; pass it through */
2867                     i--;
2868                     break;
2869                   default:
2870                     if (appData.debugMode)
2871                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2872                     /* Drop all other telnet commands on the floor */
2873                     break;
2874                 }
2875                 if (oldi > next_out)
2876                   SendToPlayer(&buf[next_out], oldi - next_out);
2877                 if (++i > next_out)
2878                   next_out = i;
2879                 continue;
2880             }
2881
2882             /* OK, this at least will *usually* work */
2883             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2884                 loggedOn = TRUE;
2885             }
2886
2887             if (loggedOn && !intfSet) {
2888                 if (ics_type == ICS_ICC) {
2889                   snprintf(str, MSG_SIZ,
2890                           "/set-quietly interface %s\n/set-quietly style 12\n",
2891                           programVersion);
2892                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2893                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2894                 } else if (ics_type == ICS_CHESSNET) {
2895                   snprintf(str, MSG_SIZ, "/style 12\n");
2896                 } else {
2897                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2898                   strcat(str, programVersion);
2899                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2900                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2901                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2902 #ifdef WIN32
2903                   strcat(str, "$iset nohighlight 1\n");
2904 #endif
2905                   strcat(str, "$iset lock 1\n$style 12\n");
2906                 }
2907                 SendToICS(str);
2908                 NotifyFrontendLogin();
2909                 intfSet = TRUE;
2910             }
2911
2912             if (started == STARTED_COMMENT) {
2913                 /* Accumulate characters in comment */
2914                 parse[parse_pos++] = buf[i];
2915                 if (buf[i] == '\n') {
2916                     parse[parse_pos] = NULLCHAR;
2917                     if(chattingPartner>=0) {
2918                         char mess[MSG_SIZ];
2919                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2920                         OutputChatMessage(chattingPartner, mess);
2921                         chattingPartner = -1;
2922                         next_out = i+1; // [HGM] suppress printing in ICS window
2923                     } else
2924                     if(!suppressKibitz) // [HGM] kibitz
2925                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2926                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2927                         int nrDigit = 0, nrAlph = 0, j;
2928                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2929                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2930                         parse[parse_pos] = NULLCHAR;
2931                         // try to be smart: if it does not look like search info, it should go to
2932                         // ICS interaction window after all, not to engine-output window.
2933                         for(j=0; j<parse_pos; j++) { // count letters and digits
2934                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2935                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2936                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2937                         }
2938                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2939                             int depth=0; float score;
2940                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2941                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2942                                 pvInfoList[forwardMostMove-1].depth = depth;
2943                                 pvInfoList[forwardMostMove-1].score = 100*score;
2944                             }
2945                             OutputKibitz(suppressKibitz, parse);
2946                         } else {
2947                             char tmp[MSG_SIZ];
2948                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2949                             SendToPlayer(tmp, strlen(tmp));
2950                         }
2951                         next_out = i+1; // [HGM] suppress printing in ICS window
2952                     }
2953                     started = STARTED_NONE;
2954                 } else {
2955                     /* Don't match patterns against characters in comment */
2956                     i++;
2957                     continue;
2958                 }
2959             }
2960             if (started == STARTED_CHATTER) {
2961                 if (buf[i] != '\n') {
2962                     /* Don't match patterns against characters in chatter */
2963                     i++;
2964                     continue;
2965                 }
2966                 started = STARTED_NONE;
2967                 if(suppressKibitz) next_out = i+1;
2968             }
2969
2970             /* Kludge to deal with rcmd protocol */
2971             if (firstTime && looking_at(buf, &i, "\001*")) {
2972                 DisplayFatalError(&buf[1], 0, 1);
2973                 continue;
2974             } else {
2975                 firstTime = FALSE;
2976             }
2977
2978             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2979                 ics_type = ICS_ICC;
2980                 ics_prefix = "/";
2981                 if (appData.debugMode)
2982                   fprintf(debugFP, "ics_type %d\n", ics_type);
2983                 continue;
2984             }
2985             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2986                 ics_type = ICS_FICS;
2987                 ics_prefix = "$";
2988                 if (appData.debugMode)
2989                   fprintf(debugFP, "ics_type %d\n", ics_type);
2990                 continue;
2991             }
2992             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2993                 ics_type = ICS_CHESSNET;
2994                 ics_prefix = "/";
2995                 if (appData.debugMode)
2996                   fprintf(debugFP, "ics_type %d\n", ics_type);
2997                 continue;
2998             }
2999
3000             if (!loggedOn &&
3001                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3002                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3003                  looking_at(buf, &i, "will be \"*\""))) {
3004               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3005               continue;
3006             }
3007
3008             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3009               char buf[MSG_SIZ];
3010               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3011               DisplayIcsInteractionTitle(buf);
3012               have_set_title = TRUE;
3013             }
3014
3015             /* skip finger notes */
3016             if (started == STARTED_NONE &&
3017                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3018                  (buf[i] == '1' && buf[i+1] == '0')) &&
3019                 buf[i+2] == ':' && buf[i+3] == ' ') {
3020               started = STARTED_CHATTER;
3021               i += 3;
3022               continue;
3023             }
3024
3025             oldi = i;
3026             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3027             if(appData.seekGraph) {
3028                 if(soughtPending && MatchSoughtLine(buf+i)) {
3029                     i = strstr(buf+i, "rated") - buf;
3030                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3031                     next_out = leftover_start = i;
3032                     started = STARTED_CHATTER;
3033                     suppressKibitz = TRUE;
3034                     continue;
3035                 }
3036                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3037                         && looking_at(buf, &i, "* ads displayed")) {
3038                     soughtPending = FALSE;
3039                     seekGraphUp = TRUE;
3040                     DrawSeekGraph();
3041                     continue;
3042                 }
3043                 if(appData.autoRefresh) {
3044                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3045                         int s = (ics_type == ICS_ICC); // ICC format differs
3046                         if(seekGraphUp)
3047                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3048                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3049                         looking_at(buf, &i, "*% "); // eat prompt
3050                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3051                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052                         next_out = i; // suppress
3053                         continue;
3054                     }
3055                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3056                         char *p = star_match[0];
3057                         while(*p) {
3058                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3059                             while(*p && *p++ != ' '); // next
3060                         }
3061                         looking_at(buf, &i, "*% "); // eat prompt
3062                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063                         next_out = i;
3064                         continue;
3065                     }
3066                 }
3067             }
3068
3069             /* skip formula vars */
3070             if (started == STARTED_NONE &&
3071                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3072               started = STARTED_CHATTER;
3073               i += 3;
3074               continue;
3075             }
3076
3077             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3078             if (appData.autoKibitz && started == STARTED_NONE &&
3079                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3080                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3081                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3082                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3083                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3084                         suppressKibitz = TRUE;
3085                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3086                         next_out = i;
3087                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3088                                 && (gameMode == IcsPlayingWhite)) ||
3089                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3090                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3091                             started = STARTED_CHATTER; // own kibitz we simply discard
3092                         else {
3093                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3094                             parse_pos = 0; parse[0] = NULLCHAR;
3095                             savingComment = TRUE;
3096                             suppressKibitz = gameMode != IcsObserving ? 2 :
3097                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3098                         }
3099                         continue;
3100                 } else
3101                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3102                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3103                          && atoi(star_match[0])) {
3104                     // suppress the acknowledgements of our own autoKibitz
3105                     char *p;
3106                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3108                     SendToPlayer(star_match[0], strlen(star_match[0]));
3109                     if(looking_at(buf, &i, "*% ")) // eat prompt
3110                         suppressKibitz = FALSE;
3111                     next_out = i;
3112                     continue;
3113                 }
3114             } // [HGM] kibitz: end of patch
3115
3116             // [HGM] chat: intercept tells by users for which we have an open chat window
3117             channel = -1;
3118             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3119                                            looking_at(buf, &i, "* whispers:") ||
3120                                            looking_at(buf, &i, "* kibitzes:") ||
3121                                            looking_at(buf, &i, "* shouts:") ||
3122                                            looking_at(buf, &i, "* c-shouts:") ||
3123                                            looking_at(buf, &i, "--> * ") ||
3124                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3125                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3126                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3127                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3128                 int p;
3129                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3130                 chattingPartner = -1;
3131
3132                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3133                 for(p=0; p<MAX_CHAT; p++) {
3134                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3135                     talker[0] = '['; strcat(talker, "] ");
3136                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3137                     chattingPartner = p; break;
3138                     }
3139                 } else
3140                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3141                 for(p=0; p<MAX_CHAT; p++) {
3142                     if(!strcmp("kibitzes", chatPartner[p])) {
3143                         talker[0] = '['; strcat(talker, "] ");
3144                         chattingPartner = p; break;
3145                     }
3146                 } else
3147                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3148                 for(p=0; p<MAX_CHAT; p++) {
3149                     if(!strcmp("whispers", chatPartner[p])) {
3150                         talker[0] = '['; strcat(talker, "] ");
3151                         chattingPartner = p; break;
3152                     }
3153                 } else
3154                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3155                   if(buf[i-8] == '-' && buf[i-3] == 't')
3156                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3157                     if(!strcmp("c-shouts", chatPartner[p])) {
3158                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3159                         chattingPartner = p; break;
3160                     }
3161                   }
3162                   if(chattingPartner < 0)
3163                   for(p=0; p<MAX_CHAT; p++) {
3164                     if(!strcmp("shouts", chatPartner[p])) {
3165                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3166                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3167                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3168                         chattingPartner = p; break;
3169                     }
3170                   }
3171                 }
3172                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3173                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3174                     talker[0] = 0; Colorize(ColorTell, FALSE);
3175                     chattingPartner = p; break;
3176                 }
3177                 if(chattingPartner<0) i = oldi; else {
3178                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3179                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3180                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3181                     started = STARTED_COMMENT;
3182                     parse_pos = 0; parse[0] = NULLCHAR;
3183                     savingComment = 3 + chattingPartner; // counts as TRUE
3184                     suppressKibitz = TRUE;
3185                     continue;
3186                 }
3187             } // [HGM] chat: end of patch
3188
3189           backup = i;
3190             if (appData.zippyTalk || appData.zippyPlay) {
3191                 /* [DM] Backup address for color zippy lines */
3192 #if ZIPPY
3193                if (loggedOn == TRUE)
3194                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3195                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3196 #endif
3197             } // [DM] 'else { ' deleted
3198                 if (
3199                     /* Regular tells and says */
3200                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3201                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3202                     looking_at(buf, &i, "* says: ") ||
3203                     /* Don't color "message" or "messages" output */
3204                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3205                     looking_at(buf, &i, "*. * at *:*: ") ||
3206                     looking_at(buf, &i, "--* (*:*): ") ||
3207                     /* Message notifications (same color as tells) */
3208                     looking_at(buf, &i, "* has left a message ") ||
3209                     looking_at(buf, &i, "* just sent you a message:\n") ||
3210                     /* Whispers and kibitzes */
3211                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3212                     looking_at(buf, &i, "* kibitzes: ") ||
3213                     /* Channel tells */
3214                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3215
3216                   if (tkind == 1 && strchr(star_match[0], ':')) {
3217                       /* Avoid "tells you:" spoofs in channels */
3218                      tkind = 3;
3219                   }
3220                   if (star_match[0][0] == NULLCHAR ||
3221                       strchr(star_match[0], ' ') ||
3222                       (tkind == 3 && strchr(star_match[1], ' '))) {
3223                     /* Reject bogus matches */
3224                     i = oldi;
3225                   } else {
3226                     if (appData.colorize) {
3227                       if (oldi > next_out) {
3228                         SendToPlayer(&buf[next_out], oldi - next_out);
3229                         next_out = oldi;
3230                       }
3231                       switch (tkind) {
3232                       case 1:
3233                         Colorize(ColorTell, FALSE);
3234                         curColor = ColorTell;
3235                         break;
3236                       case 2:
3237                         Colorize(ColorKibitz, FALSE);
3238                         curColor = ColorKibitz;
3239                         break;
3240                       case 3:
3241                         p = strrchr(star_match[1], '(');
3242                         if (p == NULL) {
3243                           p = star_match[1];
3244                         } else {
3245                           p++;
3246                         }
3247                         if (atoi(p) == 1) {
3248                           Colorize(ColorChannel1, FALSE);
3249                           curColor = ColorChannel1;
3250                         } else {
3251                           Colorize(ColorChannel, FALSE);
3252                           curColor = ColorChannel;
3253                         }
3254                         break;
3255                       case 5:
3256                         curColor = ColorNormal;
3257                         break;
3258                       }
3259                     }
3260                     if (started == STARTED_NONE && appData.autoComment &&
3261                         (gameMode == IcsObserving ||
3262                          gameMode == IcsPlayingWhite ||
3263                          gameMode == IcsPlayingBlack)) {
3264                       parse_pos = i - oldi;
3265                       memcpy(parse, &buf[oldi], parse_pos);
3266                       parse[parse_pos] = NULLCHAR;
3267                       started = STARTED_COMMENT;
3268                       savingComment = TRUE;
3269                     } else {
3270                       started = STARTED_CHATTER;
3271                       savingComment = FALSE;
3272                     }
3273                     loggedOn = TRUE;
3274                     continue;
3275                   }
3276                 }
3277
3278                 if (looking_at(buf, &i, "* s-shouts: ") ||
3279                     looking_at(buf, &i, "* c-shouts: ")) {
3280                     if (appData.colorize) {
3281                         if (oldi > next_out) {
3282                             SendToPlayer(&buf[next_out], oldi - next_out);
3283                             next_out = oldi;
3284                         }
3285                         Colorize(ColorSShout, FALSE);
3286                         curColor = ColorSShout;
3287                     }
3288                     loggedOn = TRUE;
3289                     started = STARTED_CHATTER;
3290                     continue;
3291                 }
3292
3293                 if (looking_at(buf, &i, "--->")) {
3294                     loggedOn = TRUE;
3295                     continue;
3296                 }
3297
3298                 if (looking_at(buf, &i, "* shouts: ") ||
3299                     looking_at(buf, &i, "--> ")) {
3300                     if (appData.colorize) {
3301                         if (oldi > next_out) {
3302                             SendToPlayer(&buf[next_out], oldi - next_out);
3303                             next_out = oldi;
3304                         }
3305                         Colorize(ColorShout, FALSE);
3306                         curColor = ColorShout;
3307                     }
3308                     loggedOn = TRUE;
3309                     started = STARTED_CHATTER;
3310                     continue;
3311                 }
3312
3313                 if (looking_at( buf, &i, "Challenge:")) {
3314                     if (appData.colorize) {
3315                         if (oldi > next_out) {
3316                             SendToPlayer(&buf[next_out], oldi - next_out);
3317                             next_out = oldi;
3318                         }
3319                         Colorize(ColorChallenge, FALSE);
3320                         curColor = ColorChallenge;
3321                     }
3322                     loggedOn = TRUE;
3323                     continue;
3324                 }
3325
3326                 if (looking_at(buf, &i, "* offers you") ||
3327                     looking_at(buf, &i, "* offers to be") ||
3328                     looking_at(buf, &i, "* would like to") ||
3329                     looking_at(buf, &i, "* requests to") ||
3330                     looking_at(buf, &i, "Your opponent offers") ||
3331                     looking_at(buf, &i, "Your opponent requests")) {
3332
3333                     if (appData.colorize) {
3334                         if (oldi > next_out) {
3335                             SendToPlayer(&buf[next_out], oldi - next_out);
3336                             next_out = oldi;
3337                         }
3338                         Colorize(ColorRequest, FALSE);
3339                         curColor = ColorRequest;
3340                     }
3341                     continue;
3342                 }
3343
3344                 if (looking_at(buf, &i, "* (*) seeking")) {
3345                     if (appData.colorize) {
3346                         if (oldi > next_out) {
3347                             SendToPlayer(&buf[next_out], oldi - next_out);
3348                             next_out = oldi;
3349                         }
3350                         Colorize(ColorSeek, FALSE);
3351                         curColor = ColorSeek;
3352                     }
3353                     continue;
3354             }
3355
3356           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3357
3358             if (looking_at(buf, &i, "\\   ")) {
3359                 if (prevColor != ColorNormal) {
3360                     if (oldi > next_out) {
3361                         SendToPlayer(&buf[next_out], oldi - next_out);
3362                         next_out = oldi;
3363                     }
3364                     Colorize(prevColor, TRUE);
3365                     curColor = prevColor;
3366                 }
3367                 if (savingComment) {
3368                     parse_pos = i - oldi;
3369                     memcpy(parse, &buf[oldi], parse_pos);
3370                     parse[parse_pos] = NULLCHAR;
3371                     started = STARTED_COMMENT;
3372                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3373                         chattingPartner = savingComment - 3; // kludge to remember the box
3374                 } else {
3375                     started = STARTED_CHATTER;
3376                 }
3377                 continue;
3378             }
3379
3380             if (looking_at(buf, &i, "Black Strength :") ||
3381                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3382                 looking_at(buf, &i, "<10>") ||
3383                 looking_at(buf, &i, "#@#")) {
3384                 /* Wrong board style */
3385                 loggedOn = TRUE;
3386                 SendToICS(ics_prefix);
3387                 SendToICS("set style 12\n");
3388                 SendToICS(ics_prefix);
3389                 SendToICS("refresh\n");
3390                 continue;
3391             }
3392
3393             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3394                 ICSInitScript();
3395                 have_sent_ICS_logon = 1;
3396                 continue;
3397             }
3398
3399             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3400                 (looking_at(buf, &i, "\n<12> ") ||
3401                  looking_at(buf, &i, "<12> "))) {
3402                 loggedOn = TRUE;
3403                 if (oldi > next_out) {
3404                     SendToPlayer(&buf[next_out], oldi - next_out);
3405                 }
3406                 next_out = i;
3407                 started = STARTED_BOARD;
3408                 parse_pos = 0;
3409                 continue;
3410             }
3411
3412             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3413                 looking_at(buf, &i, "<b1> ")) {
3414                 if (oldi > next_out) {
3415                     SendToPlayer(&buf[next_out], oldi - next_out);
3416                 }
3417                 next_out = i;
3418                 started = STARTED_HOLDINGS;
3419                 parse_pos = 0;
3420                 continue;
3421             }
3422
3423             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3424                 loggedOn = TRUE;
3425                 /* Header for a move list -- first line */
3426
3427                 switch (ics_getting_history) {
3428                   case H_FALSE:
3429                     switch (gameMode) {
3430                       case IcsIdle:
3431                       case BeginningOfGame:
3432                         /* User typed "moves" or "oldmoves" while we
3433                            were idle.  Pretend we asked for these
3434                            moves and soak them up so user can step
3435                            through them and/or save them.
3436                            */
3437                         Reset(FALSE, TRUE);
3438                         gameMode = IcsObserving;
3439                         ModeHighlight();
3440                         ics_gamenum = -1;
3441                         ics_getting_history = H_GOT_UNREQ_HEADER;
3442                         break;
3443                       case EditGame: /*?*/
3444                       case EditPosition: /*?*/
3445                         /* Should above feature work in these modes too? */
3446                         /* For now it doesn't */
3447                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3448                         break;
3449                       default:
3450                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3451                         break;
3452                     }
3453                     break;
3454                   case H_REQUESTED:
3455                     /* Is this the right one? */
3456                     if (gameInfo.white && gameInfo.black &&
3457                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3458                         strcmp(gameInfo.black, star_match[2]) == 0) {
3459                         /* All is well */
3460                         ics_getting_history = H_GOT_REQ_HEADER;
3461                     }
3462                     break;
3463                   case H_GOT_REQ_HEADER:
3464                   case H_GOT_UNREQ_HEADER:
3465                   case H_GOT_UNWANTED_HEADER:
3466                   case H_GETTING_MOVES:
3467                     /* Should not happen */
3468                     DisplayError(_("Error gathering move list: two headers"), 0);
3469                     ics_getting_history = H_FALSE;
3470                     break;
3471                 }
3472
3473                 /* Save player ratings into gameInfo if needed */
3474                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3475                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3476                     (gameInfo.whiteRating == -1 ||
3477                      gameInfo.blackRating == -1)) {
3478
3479                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3480                     gameInfo.blackRating = string_to_rating(star_match[3]);
3481                     if (appData.debugMode)
3482                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3483                               gameInfo.whiteRating, gameInfo.blackRating);
3484                 }
3485                 continue;
3486             }
3487
3488             if (looking_at(buf, &i,
3489               "* * match, initial time: * minute*, increment: * second")) {
3490                 /* Header for a move list -- second line */
3491                 /* Initial board will follow if this is a wild game */
3492                 if (gameInfo.event != NULL) free(gameInfo.event);
3493                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3494                 gameInfo.event = StrSave(str);
3495                 /* [HGM] we switched variant. Translate boards if needed. */
3496                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "Move  ")) {
3501                 /* Beginning of a move list */
3502                 switch (ics_getting_history) {
3503                   case H_FALSE:
3504                     /* Normally should not happen */
3505                     /* Maybe user hit reset while we were parsing */
3506                     break;
3507                   case H_REQUESTED:
3508                     /* Happens if we are ignoring a move list that is not
3509                      * the one we just requested.  Common if the user
3510                      * tries to observe two games without turning off
3511                      * getMoveList */
3512                     break;
3513                   case H_GETTING_MOVES:
3514                     /* Should not happen */
3515                     DisplayError(_("Error gathering move list: nested"), 0);
3516                     ics_getting_history = H_FALSE;
3517                     break;
3518                   case H_GOT_REQ_HEADER:
3519                     ics_getting_history = H_GETTING_MOVES;
3520                     started = STARTED_MOVES;
3521                     parse_pos = 0;
3522                     if (oldi > next_out) {
3523                         SendToPlayer(&buf[next_out], oldi - next_out);
3524                     }
3525                     break;
3526                   case H_GOT_UNREQ_HEADER:
3527                     ics_getting_history = H_GETTING_MOVES;
3528                     started = STARTED_MOVES_NOHIDE;
3529                     parse_pos = 0;
3530                     break;
3531                   case H_GOT_UNWANTED_HEADER:
3532                     ics_getting_history = H_FALSE;
3533                     break;
3534                 }
3535                 continue;
3536             }
3537
3538             if (looking_at(buf, &i, "% ") ||
3539                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3540                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3541                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3542                     soughtPending = FALSE;
3543                     seekGraphUp = TRUE;
3544                     DrawSeekGraph();
3545                 }
3546                 if(suppressKibitz) next_out = i;
3547                 savingComment = FALSE;
3548                 suppressKibitz = 0;
3549                 switch (started) {
3550                   case STARTED_MOVES:
3551                   case STARTED_MOVES_NOHIDE:
3552                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3553                     parse[parse_pos + i - oldi] = NULLCHAR;
3554                     ParseGameHistory(parse);
3555 #if ZIPPY
3556                     if (appData.zippyPlay && first.initDone) {
3557                         FeedMovesToProgram(&first, forwardMostMove);
3558                         if (gameMode == IcsPlayingWhite) {
3559                             if (WhiteOnMove(forwardMostMove)) {
3560                                 if (first.sendTime) {
3561                                   if (first.useColors) {
3562                                     SendToProgram("black\n", &first);
3563                                   }
3564                                   SendTimeRemaining(&first, TRUE);
3565                                 }
3566                                 if (first.useColors) {
3567                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3568                                 }
3569                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3570                                 first.maybeThinking = TRUE;
3571                             } else {
3572                                 if (first.usePlayother) {
3573                                   if (first.sendTime) {
3574                                     SendTimeRemaining(&first, TRUE);
3575                                   }
3576                                   SendToProgram("playother\n", &first);
3577                                   firstMove = FALSE;
3578                                 } else {
3579                                   firstMove = TRUE;
3580                                 }
3581                             }
3582                         } else if (gameMode == IcsPlayingBlack) {
3583                             if (!WhiteOnMove(forwardMostMove)) {
3584                                 if (first.sendTime) {
3585                                   if (first.useColors) {
3586                                     SendToProgram("white\n", &first);
3587                                   }
3588                                   SendTimeRemaining(&first, FALSE);
3589                                 }
3590                                 if (first.useColors) {
3591                                   SendToProgram("black\n", &first);
3592                                 }
3593                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3594                                 first.maybeThinking = TRUE;
3595                             } else {
3596                                 if (first.usePlayother) {
3597                                   if (first.sendTime) {
3598                                     SendTimeRemaining(&first, FALSE);
3599                                   }
3600                                   SendToProgram("playother\n", &first);
3601                                   firstMove = FALSE;
3602                                 } else {
3603                                   firstMove = TRUE;
3604                                 }
3605                             }
3606                         }
3607                     }
3608 #endif
3609                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3610                         /* Moves came from oldmoves or moves command
3611                            while we weren't doing anything else.
3612                            */
3613                         currentMove = forwardMostMove;
3614                         ClearHighlights();/*!!could figure this out*/
3615                         flipView = appData.flipView;
3616                         DrawPosition(TRUE, boards[currentMove]);
3617                         DisplayBothClocks();
3618                         snprintf(str, MSG_SIZ, "%s vs. %s",
3619                                 gameInfo.white, gameInfo.black);
3620                         DisplayTitle(str);
3621                         gameMode = IcsIdle;
3622                     } else {
3623                         /* Moves were history of an active game */
3624                         if (gameInfo.resultDetails != NULL) {
3625                             free(gameInfo.resultDetails);
3626                             gameInfo.resultDetails = NULL;
3627                         }
3628                     }
3629                     HistorySet(parseList, backwardMostMove,
3630                                forwardMostMove, currentMove-1);
3631                     DisplayMove(currentMove - 1);
3632                     if (started == STARTED_MOVES) next_out = i;
3633                     started = STARTED_NONE;
3634                     ics_getting_history = H_FALSE;
3635                     break;
3636
3637                   case STARTED_OBSERVE:
3638                     started = STARTED_NONE;
3639                     SendToICS(ics_prefix);
3640                     SendToICS("refresh\n");
3641                     break;
3642
3643                   default:
3644                     break;
3645                 }
3646                 if(bookHit) { // [HGM] book: simulate book reply
3647                     static char bookMove[MSG_SIZ]; // a bit generous?
3648
3649                     programStats.nodes = programStats.depth = programStats.time =
3650                     programStats.score = programStats.got_only_move = 0;
3651                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3652
3653                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3654                     strcat(bookMove, bookHit);
3655                     HandleMachineMove(bookMove, &first);
3656                 }
3657                 continue;
3658             }
3659
3660             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3661                  started == STARTED_HOLDINGS ||
3662                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3663                 /* Accumulate characters in move list or board */
3664                 parse[parse_pos++] = buf[i];
3665             }
3666
3667             /* Start of game messages.  Mostly we detect start of game
3668                when the first board image arrives.  On some versions
3669                of the ICS, though, we need to do a "refresh" after starting
3670                to observe in order to get the current board right away. */
3671             if (looking_at(buf, &i, "Adding game * to observation list")) {
3672                 started = STARTED_OBSERVE;
3673                 continue;
3674             }
3675
3676             /* Handle auto-observe */
3677             if (appData.autoObserve &&
3678                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3679                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3680                 char *player;
3681                 /* Choose the player that was highlighted, if any. */
3682                 if (star_match[0][0] == '\033' ||
3683                     star_match[1][0] != '\033') {
3684                     player = star_match[0];
3685                 } else {
3686                     player = star_match[2];
3687                 }
3688                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3689                         ics_prefix, StripHighlightAndTitle(player));
3690                 SendToICS(str);
3691
3692                 /* Save ratings from notify string */
3693                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3694                 player1Rating = string_to_rating(star_match[1]);
3695                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3696                 player2Rating = string_to_rating(star_match[3]);
3697
3698                 if (appData.debugMode)
3699                   fprintf(debugFP,
3700                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3701                           player1Name, player1Rating,
3702                           player2Name, player2Rating);
3703
3704                 continue;
3705             }
3706
3707             /* Deal with automatic examine mode after a game,
3708                and with IcsObserving -> IcsExamining transition */
3709             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3710                 looking_at(buf, &i, "has made you an examiner of game *")) {
3711
3712                 int gamenum = atoi(star_match[0]);
3713                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3714                     gamenum == ics_gamenum) {
3715                     /* We were already playing or observing this game;
3716                        no need to refetch history */
3717                     gameMode = IcsExamining;
3718                     if (pausing) {
3719                         pauseExamForwardMostMove = forwardMostMove;
3720                     } else if (currentMove < forwardMostMove) {
3721                         ForwardInner(forwardMostMove);
3722                     }
3723                 } else {
3724                     /* I don't think this case really can happen */
3725                     SendToICS(ics_prefix);
3726                     SendToICS("refresh\n");
3727                 }
3728                 continue;
3729             }
3730
3731             /* Error messages */
3732 //          if (ics_user_moved) {
3733             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3734                 if (looking_at(buf, &i, "Illegal move") ||
3735                     looking_at(buf, &i, "Not a legal move") ||
3736                     looking_at(buf, &i, "Your king is in check") ||
3737                     looking_at(buf, &i, "It isn't your turn") ||
3738                     looking_at(buf, &i, "It is not your move")) {
3739                     /* Illegal move */
3740                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3741                         currentMove = forwardMostMove-1;
3742                         DisplayMove(currentMove - 1); /* before DMError */
3743                         DrawPosition(FALSE, boards[currentMove]);
3744                         SwitchClocks(forwardMostMove-1); // [HGM] race
3745                         DisplayBothClocks();
3746                     }
3747                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3748                     ics_user_moved = 0;
3749                     continue;
3750                 }
3751             }
3752
3753             if (looking_at(buf, &i, "still have time") ||
3754                 looking_at(buf, &i, "not out of time") ||
3755                 looking_at(buf, &i, "either player is out of time") ||
3756                 looking_at(buf, &i, "has timeseal; checking")) {
3757                 /* We must have called his flag a little too soon */
3758                 whiteFlag = blackFlag = FALSE;
3759                 continue;
3760             }
3761
3762             if (looking_at(buf, &i, "added * seconds to") ||
3763                 looking_at(buf, &i, "seconds were added to")) {
3764                 /* Update the clocks */
3765                 SendToICS(ics_prefix);
3766                 SendToICS("refresh\n");
3767                 continue;
3768             }
3769
3770             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3771                 ics_clock_paused = TRUE;
3772                 StopClocks();
3773                 continue;
3774             }
3775
3776             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3777                 ics_clock_paused = FALSE;
3778                 StartClocks();
3779                 continue;
3780             }
3781
3782             /* Grab player ratings from the Creating: message.
3783                Note we have to check for the special case when
3784                the ICS inserts things like [white] or [black]. */
3785             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3786                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3787                 /* star_matches:
3788                    0    player 1 name (not necessarily white)
3789                    1    player 1 rating
3790                    2    empty, white, or black (IGNORED)
3791                    3    player 2 name (not necessarily black)
3792                    4    player 2 rating
3793
3794                    The names/ratings are sorted out when the game
3795                    actually starts (below).
3796                 */
3797                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3798                 player1Rating = string_to_rating(star_match[1]);
3799                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3800                 player2Rating = string_to_rating(star_match[4]);
3801
3802                 if (appData.debugMode)
3803                   fprintf(debugFP,
3804                           "Ratings from 'Creating:' %s %d, %s %d\n",
3805                           player1Name, player1Rating,
3806                           player2Name, player2Rating);
3807
3808                 continue;
3809             }
3810
3811             /* Improved generic start/end-of-game messages */
3812             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3813                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3814                 /* If tkind == 0: */
3815                 /* star_match[0] is the game number */
3816                 /*           [1] is the white player's name */
3817                 /*           [2] is the black player's name */
3818                 /* For end-of-game: */
3819                 /*           [3] is the reason for the game end */
3820                 /*           [4] is a PGN end game-token, preceded by " " */
3821                 /* For start-of-game: */
3822                 /*           [3] begins with "Creating" or "Continuing" */
3823                 /*           [4] is " *" or empty (don't care). */
3824                 int gamenum = atoi(star_match[0]);
3825                 char *whitename, *blackname, *why, *endtoken;
3826                 ChessMove endtype = EndOfFile;
3827
3828                 if (tkind == 0) {
3829                   whitename = star_match[1];
3830                   blackname = star_match[2];
3831                   why = star_match[3];
3832                   endtoken = star_match[4];
3833                 } else {
3834                   whitename = star_match[1];
3835                   blackname = star_match[3];
3836                   why = star_match[5];
3837                   endtoken = star_match[6];
3838                 }
3839
3840                 /* Game start messages */
3841                 if (strncmp(why, "Creating ", 9) == 0 ||
3842                     strncmp(why, "Continuing ", 11) == 0) {
3843                     gs_gamenum = gamenum;
3844                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3845                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3846 #if ZIPPY
3847                     if (appData.zippyPlay) {
3848                         ZippyGameStart(whitename, blackname);
3849                     }
3850 #endif /*ZIPPY*/
3851                     partnerBoardValid = FALSE; // [HGM] bughouse
3852                     continue;
3853                 }
3854
3855                 /* Game end messages */
3856                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3857                     ics_gamenum != gamenum) {
3858                     continue;
3859                 }
3860                 while (endtoken[0] == ' ') endtoken++;
3861                 switch (endtoken[0]) {
3862                   case '*':
3863                   default:
3864                     endtype = GameUnfinished;
3865                     break;
3866                   case '0':
3867                     endtype = BlackWins;
3868                     break;
3869                   case '1':
3870                     if (endtoken[1] == '/')
3871                       endtype = GameIsDrawn;
3872                     else
3873                       endtype = WhiteWins;
3874                     break;
3875                 }
3876                 GameEnds(endtype, why, GE_ICS);
3877 #if ZIPPY
3878                 if (appData.zippyPlay && first.initDone) {
3879                     ZippyGameEnd(endtype, why);
3880                     if (first.pr == NULL) {
3881                       /* Start the next process early so that we'll
3882                          be ready for the next challenge */
3883                       StartChessProgram(&first);
3884                     }
3885                     /* Send "new" early, in case this command takes
3886                        a long time to finish, so that we'll be ready
3887                        for the next challenge. */
3888                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3889                     Reset(TRUE, TRUE);
3890                 }
3891 #endif /*ZIPPY*/
3892                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3893                 continue;
3894             }
3895
3896             if (looking_at(buf, &i, "Removing game * from observation") ||
3897                 looking_at(buf, &i, "no longer observing game *") ||
3898                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3899                 if (gameMode == IcsObserving &&
3900                     atoi(star_match[0]) == ics_gamenum)
3901                   {
3902                       /* icsEngineAnalyze */
3903                       if (appData.icsEngineAnalyze) {
3904                             ExitAnalyzeMode();
3905                             ModeHighlight();
3906                       }
3907                       StopClocks();
3908                       gameMode = IcsIdle;
3909                       ics_gamenum = -1;
3910                       ics_user_moved = FALSE;
3911                   }
3912                 continue;
3913             }
3914
3915             if (looking_at(buf, &i, "no longer examining game *")) {
3916                 if (gameMode == IcsExamining &&
3917                     atoi(star_match[0]) == ics_gamenum)
3918                   {
3919                       gameMode = IcsIdle;
3920                       ics_gamenum = -1;
3921                       ics_user_moved = FALSE;
3922                   }
3923                 continue;
3924             }
3925
3926             /* Advance leftover_start past any newlines we find,
3927                so only partial lines can get reparsed */
3928             if (looking_at(buf, &i, "\n")) {
3929                 prevColor = curColor;
3930                 if (curColor != ColorNormal) {
3931                     if (oldi > next_out) {
3932                         SendToPlayer(&buf[next_out], oldi - next_out);
3933                         next_out = oldi;
3934                     }
3935                     Colorize(ColorNormal, FALSE);
3936                     curColor = ColorNormal;
3937                 }
3938                 if (started == STARTED_BOARD) {
3939                     started = STARTED_NONE;
3940                     parse[parse_pos] = NULLCHAR;
3941                     ParseBoard12(parse);
3942                     ics_user_moved = 0;
3943
3944                     /* Send premove here */
3945                     if (appData.premove) {
3946                       char str[MSG_SIZ];
3947                       if (currentMove == 0 &&
3948                           gameMode == IcsPlayingWhite &&
3949                           appData.premoveWhite) {
3950                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3951                         if (appData.debugMode)
3952                           fprintf(debugFP, "Sending premove:\n");
3953                         SendToICS(str);
3954                       } else if (currentMove == 1 &&
3955                                  gameMode == IcsPlayingBlack &&
3956                                  appData.premoveBlack) {
3957                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3958                         if (appData.debugMode)
3959                           fprintf(debugFP, "Sending premove:\n");
3960                         SendToICS(str);
3961                       } else if (gotPremove) {
3962                         gotPremove = 0;
3963                         ClearPremoveHighlights();
3964                         if (appData.debugMode)
3965                           fprintf(debugFP, "Sending premove:\n");
3966                           UserMoveEvent(premoveFromX, premoveFromY,
3967                                         premoveToX, premoveToY,
3968                                         premovePromoChar);
3969                       }
3970                     }
3971
3972                     /* Usually suppress following prompt */
3973                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3974                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3975                         if (looking_at(buf, &i, "*% ")) {
3976                             savingComment = FALSE;
3977                             suppressKibitz = 0;
3978                         }
3979                     }
3980                     next_out = i;
3981                 } else if (started == STARTED_HOLDINGS) {
3982                     int gamenum;
3983                     char new_piece[MSG_SIZ];
3984                     started = STARTED_NONE;
3985                     parse[parse_pos] = NULLCHAR;
3986                     if (appData.debugMode)
3987                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3988                                                         parse, currentMove);
3989                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3990                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3991                         if (gameInfo.variant == VariantNormal) {
3992                           /* [HGM] We seem to switch variant during a game!
3993                            * Presumably no holdings were displayed, so we have
3994                            * to move the position two files to the right to
3995                            * create room for them!
3996                            */
3997                           VariantClass newVariant;
3998                           switch(gameInfo.boardWidth) { // base guess on board width
3999                                 case 9:  newVariant = VariantShogi; break;
4000                                 case 10: newVariant = VariantGreat; break;
4001                                 default: newVariant = VariantCrazyhouse; break;
4002                           }
4003                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4004                           /* Get a move list just to see the header, which
4005                              will tell us whether this is really bug or zh */
4006                           if (ics_getting_history == H_FALSE) {
4007                             ics_getting_history = H_REQUESTED;
4008                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4009                             SendToICS(str);
4010                           }
4011                         }
4012                         new_piece[0] = NULLCHAR;
4013                         sscanf(parse, "game %d white [%s black [%s <- %s",
4014                                &gamenum, white_holding, black_holding,
4015                                new_piece);
4016                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4017                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4018                         /* [HGM] copy holdings to board holdings area */
4019                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4020                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4021                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4022 #if ZIPPY
4023                         if (appData.zippyPlay && first.initDone) {
4024                             ZippyHoldings(white_holding, black_holding,
4025                                           new_piece);
4026                         }
4027 #endif /*ZIPPY*/
4028                         if (tinyLayout || smallLayout) {
4029                             char wh[16], bh[16];
4030                             PackHolding(wh, white_holding);
4031                             PackHolding(bh, black_holding);
4032                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4033                                     gameInfo.white, gameInfo.black);
4034                         } else {
4035                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4036                                     gameInfo.white, white_holding,
4037                                     gameInfo.black, black_holding);
4038                         }
4039                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4040                         DrawPosition(FALSE, boards[currentMove]);
4041                         DisplayTitle(str);
4042                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4043                         sscanf(parse, "game %d white [%s black [%s <- %s",
4044                                &gamenum, white_holding, black_holding,
4045                                new_piece);
4046                         white_holding[strlen(white_holding)-1] = NULLCHAR;
4047                         black_holding[strlen(black_holding)-1] = NULLCHAR;
4048                         /* [HGM] copy holdings to partner-board holdings area */
4049                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
4050                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
4051                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4052                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
4053                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4054                       }
4055                     }
4056                     /* Suppress following prompt */
4057                     if (looking_at(buf, &i, "*% ")) {
4058                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4059                         savingComment = FALSE;
4060                         suppressKibitz = 0;
4061                     }
4062                     next_out = i;
4063                 }
4064                 continue;
4065             }
4066
4067             i++;                /* skip unparsed character and loop back */
4068         }
4069
4070         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4071 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4072 //          SendToPlayer(&buf[next_out], i - next_out);
4073             started != STARTED_HOLDINGS && leftover_start > next_out) {
4074             SendToPlayer(&buf[next_out], leftover_start - next_out);
4075             next_out = i;
4076         }
4077
4078         leftover_len = buf_len - leftover_start;
4079         /* if buffer ends with something we couldn't parse,
4080            reparse it after appending the next read */
4081
4082     } else if (count == 0) {
4083         RemoveInputSource(isr);
4084         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4085     } else {
4086         DisplayFatalError(_("Error reading from ICS"), error, 1);
4087     }
4088 }
4089
4090
4091 /* Board style 12 looks like this:
4092
4093    <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
4094
4095  * The "<12> " is stripped before it gets to this routine.  The two
4096  * trailing 0's (flip state and clock ticking) are later addition, and
4097  * some chess servers may not have them, or may have only the first.
4098  * Additional trailing fields may be added in the future.
4099  */
4100
4101 #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"
4102
4103 #define RELATION_OBSERVING_PLAYED    0
4104 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
4105 #define RELATION_PLAYING_MYMOVE      1
4106 #define RELATION_PLAYING_NOTMYMOVE  -1
4107 #define RELATION_EXAMINING           2
4108 #define RELATION_ISOLATED_BOARD     -3
4109 #define RELATION_STARTING_POSITION  -4   /* FICS only */
4110
4111 void
4112 ParseBoard12(string)
4113      char *string;
4114 {
4115     GameMode newGameMode;
4116     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4117     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4118     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4119     char to_play, board_chars[200];
4120     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4121     char black[32], white[32];
4122     Board board;
4123     int prevMove = currentMove;
4124     int ticking = 2;
4125     ChessMove moveType;
4126     int fromX, fromY, toX, toY;
4127     char promoChar;
4128     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4129     char *bookHit = NULL; // [HGM] book
4130     Boolean weird = FALSE, reqFlag = FALSE;
4131
4132     fromX = fromY = toX = toY = -1;
4133
4134     newGame = FALSE;
4135
4136     if (appData.debugMode)
4137       fprintf(debugFP, _("Parsing board: %s\n"), string);
4138
4139     move_str[0] = NULLCHAR;
4140     elapsed_time[0] = NULLCHAR;
4141     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4142         int  i = 0, j;
4143         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4144             if(string[i] == ' ') { ranks++; files = 0; }
4145             else files++;
4146             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4147             i++;
4148         }
4149         for(j = 0; j <i; j++) board_chars[j] = string[j];
4150         board_chars[i] = '\0';
4151         string += i + 1;
4152     }
4153     n = sscanf(string, PATTERN, &to_play, &double_push,
4154                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4155                &gamenum, white, black, &relation, &basetime, &increment,
4156                &white_stren, &black_stren, &white_time, &black_time,
4157                &moveNum, str, elapsed_time, move_str, &ics_flip,
4158                &ticking);
4159
4160     if (n < 21) {
4161         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4162         DisplayError(str, 0);
4163         return;
4164     }
4165
4166     /* Convert the move number to internal form */
4167     moveNum = (moveNum - 1) * 2;
4168     if (to_play == 'B') moveNum++;
4169     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4170       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4171                         0, 1);
4172       return;
4173     }
4174
4175     switch (relation) {
4176       case RELATION_OBSERVING_PLAYED:
4177       case RELATION_OBSERVING_STATIC:
4178         if (gamenum == -1) {
4179             /* Old ICC buglet */
4180             relation = RELATION_OBSERVING_STATIC;
4181         }
4182         newGameMode = IcsObserving;
4183         break;
4184       case RELATION_PLAYING_MYMOVE:
4185       case RELATION_PLAYING_NOTMYMOVE:
4186         newGameMode =
4187           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4188             IcsPlayingWhite : IcsPlayingBlack;
4189         break;
4190       case RELATION_EXAMINING:
4191         newGameMode = IcsExamining;
4192         break;
4193       case RELATION_ISOLATED_BOARD:
4194       default:
4195         /* Just display this board.  If user was doing something else,
4196            we will forget about it until the next board comes. */
4197         newGameMode = IcsIdle;
4198         break;
4199       case RELATION_STARTING_POSITION:
4200         newGameMode = gameMode;
4201         break;
4202     }
4203
4204     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4205          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4206       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4207       char *toSqr;
4208       for (k = 0; k < ranks; k++) {
4209         for (j = 0; j < files; j++)
4210           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4211         if(gameInfo.holdingsWidth > 1) {
4212              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4213              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4214         }
4215       }
4216       CopyBoard(partnerBoard, board);
4217       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4218         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4219         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4220       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4221       if(toSqr = strchr(str, '-')) {
4222         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4223         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4224       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4225       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4226       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4227       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4228       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4229       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4230                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4231       DisplayMessage(partnerStatus, "");
4232         partnerBoardValid = TRUE;
4233       return;
4234     }
4235
4236     /* Modify behavior for initial board display on move listing
4237        of wild games.
4238        */
4239     switch (ics_getting_history) {
4240       case H_FALSE:
4241       case H_REQUESTED:
4242         break;
4243       case H_GOT_REQ_HEADER:
4244       case H_GOT_UNREQ_HEADER:
4245         /* This is the initial position of the current game */
4246         gamenum = ics_gamenum;
4247         moveNum = 0;            /* old ICS bug workaround */
4248         if (to_play == 'B') {
4249           startedFromSetupPosition = TRUE;
4250           blackPlaysFirst = TRUE;
4251           moveNum = 1;
4252           if (forwardMostMove == 0) forwardMostMove = 1;
4253           if (backwardMostMove == 0) backwardMostMove = 1;
4254           if (currentMove == 0) currentMove = 1;
4255         }
4256         newGameMode = gameMode;
4257         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4258         break;
4259       case H_GOT_UNWANTED_HEADER:
4260         /* This is an initial board that we don't want */
4261         return;
4262       case H_GETTING_MOVES:
4263         /* Should not happen */
4264         DisplayError(_("Error gathering move list: extra board"), 0);
4265         ics_getting_history = H_FALSE;
4266         return;
4267     }
4268
4269    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4270                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4271      /* [HGM] We seem to have switched variant unexpectedly
4272       * Try to guess new variant from board size
4273       */
4274           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4275           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4276           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4277           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4278           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4279           if(!weird) newVariant = VariantNormal;
4280           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4281           /* Get a move list just to see the header, which
4282              will tell us whether this is really bug or zh */
4283           if (ics_getting_history == H_FALSE) {
4284             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4285             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4286             SendToICS(str);
4287           }
4288     }
4289
4290     /* Take action if this is the first board of a new game, or of a
4291        different game than is currently being displayed.  */
4292     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4293         relation == RELATION_ISOLATED_BOARD) {
4294
4295         /* Forget the old game and get the history (if any) of the new one */
4296         if (gameMode != BeginningOfGame) {
4297           Reset(TRUE, TRUE);
4298         }
4299         newGame = TRUE;
4300         if (appData.autoRaiseBoard) BoardToTop();
4301         prevMove = -3;
4302         if (gamenum == -1) {
4303             newGameMode = IcsIdle;
4304         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4305                    appData.getMoveList && !reqFlag) {
4306             /* Need to get game history */
4307             ics_getting_history = H_REQUESTED;
4308             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4309             SendToICS(str);
4310         }
4311
4312         /* Initially flip the board to have black on the bottom if playing
4313            black or if the ICS flip flag is set, but let the user change
4314            it with the Flip View button. */
4315         flipView = appData.autoFlipView ?
4316           (newGameMode == IcsPlayingBlack) || ics_flip :
4317           appData.flipView;
4318
4319         /* Done with values from previous mode; copy in new ones */
4320         gameMode = newGameMode;
4321         ModeHighlight();
4322         ics_gamenum = gamenum;
4323         if (gamenum == gs_gamenum) {
4324             int klen = strlen(gs_kind);
4325             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4326             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4327             gameInfo.event = StrSave(str);
4328         } else {
4329             gameInfo.event = StrSave("ICS game");
4330         }
4331         gameInfo.site = StrSave(appData.icsHost);
4332         gameInfo.date = PGNDate();
4333         gameInfo.round = StrSave("-");
4334         gameInfo.white = StrSave(white);
4335         gameInfo.black = StrSave(black);
4336         timeControl = basetime * 60 * 1000;
4337         timeControl_2 = 0;
4338         timeIncrement = increment * 1000;
4339         movesPerSession = 0;
4340         gameInfo.timeControl = TimeControlTagValue();
4341         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4342   if (appData.debugMode) {
4343     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4344     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4345     setbuf(debugFP, NULL);
4346   }
4347
4348         gameInfo.outOfBook = NULL;
4349
4350         /* Do we have the ratings? */
4351         if (strcmp(player1Name, white) == 0 &&
4352             strcmp(player2Name, black) == 0) {
4353             if (appData.debugMode)
4354               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4355                       player1Rating, player2Rating);
4356             gameInfo.whiteRating = player1Rating;
4357             gameInfo.blackRating = player2Rating;
4358         } else if (strcmp(player2Name, white) == 0 &&
4359                    strcmp(player1Name, black) == 0) {
4360             if (appData.debugMode)
4361               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4362                       player2Rating, player1Rating);
4363             gameInfo.whiteRating = player2Rating;
4364             gameInfo.blackRating = player1Rating;
4365         }
4366         player1Name[0] = player2Name[0] = NULLCHAR;
4367
4368         /* Silence shouts if requested */
4369         if (appData.quietPlay &&
4370             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4371             SendToICS(ics_prefix);
4372             SendToICS("set shout 0\n");
4373         }
4374     }
4375
4376     /* Deal with midgame name changes */
4377     if (!newGame) {
4378         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4379             if (gameInfo.white) free(gameInfo.white);
4380             gameInfo.white = StrSave(white);
4381         }
4382         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4383             if (gameInfo.black) free(gameInfo.black);
4384             gameInfo.black = StrSave(black);
4385         }
4386     }
4387
4388     /* Throw away game result if anything actually changes in examine mode */
4389     if (gameMode == IcsExamining && !newGame) {
4390         gameInfo.result = GameUnfinished;
4391         if (gameInfo.resultDetails != NULL) {
4392             free(gameInfo.resultDetails);
4393             gameInfo.resultDetails = NULL;
4394         }
4395     }
4396
4397     /* In pausing && IcsExamining mode, we ignore boards coming
4398        in if they are in a different variation than we are. */
4399     if (pauseExamInvalid) return;
4400     if (pausing && gameMode == IcsExamining) {
4401         if (moveNum <= pauseExamForwardMostMove) {
4402             pauseExamInvalid = TRUE;
4403             forwardMostMove = pauseExamForwardMostMove;
4404             return;
4405         }
4406     }
4407
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4410   }
4411     /* Parse the board */
4412     for (k = 0; k < ranks; k++) {
4413       for (j = 0; j < files; j++)
4414         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4415       if(gameInfo.holdingsWidth > 1) {
4416            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4417            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4418       }
4419     }
4420     CopyBoard(boards[moveNum], board);
4421     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4422     if (moveNum == 0) {
4423         startedFromSetupPosition =
4424           !CompareBoards(board, initialPosition);
4425         if(startedFromSetupPosition)
4426             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4427     }
4428
4429     /* [HGM] Set castling rights. Take the outermost Rooks,
4430        to make it also work for FRC opening positions. Note that board12
4431        is really defective for later FRC positions, as it has no way to
4432        indicate which Rook can castle if they are on the same side of King.
4433        For the initial position we grant rights to the outermost Rooks,
4434        and remember thos rights, and we then copy them on positions
4435        later in an FRC game. This means WB might not recognize castlings with
4436        Rooks that have moved back to their original position as illegal,
4437        but in ICS mode that is not its job anyway.
4438     */
4439     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4440     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4441
4442         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4443             if(board[0][i] == WhiteRook) j = i;
4444         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4445         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4446             if(board[0][i] == WhiteRook) j = i;
4447         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4448         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4449             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4450         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4451         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4452             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4453         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4454
4455         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4456         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4457             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4458         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4459             if(board[BOARD_HEIGHT-1][k] == bKing)
4460                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4461         if(gameInfo.variant == VariantTwoKings) {
4462             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4463             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4464             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4465         }
4466     } else { int r;
4467         r = boards[moveNum][CASTLING][0] = initialRights[0];
4468         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4469         r = boards[moveNum][CASTLING][1] = initialRights[1];
4470         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4471         r = boards[moveNum][CASTLING][3] = initialRights[3];
4472         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4473         r = boards[moveNum][CASTLING][4] = initialRights[4];
4474         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4475         /* wildcastle kludge: always assume King has rights */
4476         r = boards[moveNum][CASTLING][2] = initialRights[2];
4477         r = boards[moveNum][CASTLING][5] = initialRights[5];
4478     }
4479     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4480     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4481
4482
4483     if (ics_getting_history == H_GOT_REQ_HEADER ||
4484         ics_getting_history == H_GOT_UNREQ_HEADER) {
4485         /* This was an initial position from a move list, not
4486            the current position */
4487         return;
4488     }
4489
4490     /* Update currentMove and known move number limits */
4491     newMove = newGame || moveNum > forwardMostMove;
4492
4493     if (newGame) {
4494         forwardMostMove = backwardMostMove = currentMove = moveNum;
4495         if (gameMode == IcsExamining && moveNum == 0) {
4496           /* Workaround for ICS limitation: we are not told the wild
4497              type when starting to examine a game.  But if we ask for
4498              the move list, the move list header will tell us */
4499             ics_getting_history = H_REQUESTED;
4500             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4501             SendToICS(str);
4502         }
4503     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4504                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4505 #if ZIPPY
4506         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4507         /* [HGM] applied this also to an engine that is silently watching        */
4508         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4509             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4510             gameInfo.variant == currentlyInitializedVariant) {
4511           takeback = forwardMostMove - moveNum;
4512           for (i = 0; i < takeback; i++) {
4513             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4514             SendToProgram("undo\n", &first);
4515           }
4516         }
4517 #endif
4518
4519         forwardMostMove = moveNum;
4520         if (!pausing || currentMove > forwardMostMove)
4521           currentMove = forwardMostMove;
4522     } else {
4523         /* New part of history that is not contiguous with old part */
4524         if (pausing && gameMode == IcsExamining) {
4525             pauseExamInvalid = TRUE;
4526             forwardMostMove = pauseExamForwardMostMove;
4527             return;
4528         }
4529         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4530 #if ZIPPY
4531             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4532                 // [HGM] when we will receive the move list we now request, it will be
4533                 // fed to the engine from the first move on. So if the engine is not
4534                 // in the initial position now, bring it there.
4535                 InitChessProgram(&first, 0);
4536             }
4537 #endif
4538             ics_getting_history = H_REQUESTED;
4539             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4540             SendToICS(str);
4541         }
4542         forwardMostMove = backwardMostMove = currentMove = moveNum;
4543     }
4544
4545     /* Update the clocks */
4546     if (strchr(elapsed_time, '.')) {
4547       /* Time is in ms */
4548       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4549       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4550     } else {
4551       /* Time is in seconds */
4552       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4553       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4554     }
4555
4556
4557 #if ZIPPY
4558     if (appData.zippyPlay && newGame &&
4559         gameMode != IcsObserving && gameMode != IcsIdle &&
4560         gameMode != IcsExamining)
4561       ZippyFirstBoard(moveNum, basetime, increment);
4562 #endif
4563
4564     /* Put the move on the move list, first converting
4565        to canonical algebraic form. */
4566     if (moveNum > 0) {
4567   if (appData.debugMode) {
4568     if (appData.debugMode) { int f = forwardMostMove;
4569         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4570                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4571                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4572     }
4573     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4574     fprintf(debugFP, "moveNum = %d\n", moveNum);
4575     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4576     setbuf(debugFP, NULL);
4577   }
4578         if (moveNum <= backwardMostMove) {
4579             /* We don't know what the board looked like before
4580                this move.  Punt. */
4581           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4582             strcat(parseList[moveNum - 1], " ");
4583             strcat(parseList[moveNum - 1], elapsed_time);
4584             moveList[moveNum - 1][0] = NULLCHAR;
4585         } else if (strcmp(move_str, "none") == 0) {
4586             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4587             /* Again, we don't know what the board looked like;
4588                this is really the start of the game. */
4589             parseList[moveNum - 1][0] = NULLCHAR;
4590             moveList[moveNum - 1][0] = NULLCHAR;
4591             backwardMostMove = moveNum;
4592             startedFromSetupPosition = TRUE;
4593             fromX = fromY = toX = toY = -1;
4594         } else {
4595           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4596           //                 So we parse the long-algebraic move string in stead of the SAN move
4597           int valid; char buf[MSG_SIZ], *prom;
4598
4599           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4600                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4601           // str looks something like "Q/a1-a2"; kill the slash
4602           if(str[1] == '/')
4603             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4604           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4605           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4606                 strcat(buf, prom); // long move lacks promo specification!
4607           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4608                 if(appData.debugMode)
4609                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4610                 safeStrCpy(move_str, buf, MSG_SIZ);
4611           }
4612           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4613                                 &fromX, &fromY, &toX, &toY, &promoChar)
4614                || ParseOneMove(buf, moveNum - 1, &moveType,
4615                                 &fromX, &fromY, &toX, &toY, &promoChar);
4616           // end of long SAN patch
4617           if (valid) {
4618             (void) CoordsToAlgebraic(boards[moveNum - 1],
4619                                      PosFlags(moveNum - 1),
4620                                      fromY, fromX, toY, toX, promoChar,
4621                                      parseList[moveNum-1]);
4622             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4623               case MT_NONE:
4624               case MT_STALEMATE:
4625               default:
4626                 break;
4627               case MT_CHECK:
4628                 if(gameInfo.variant != VariantShogi)
4629                     strcat(parseList[moveNum - 1], "+");
4630                 break;
4631               case MT_CHECKMATE:
4632               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4633                 strcat(parseList[moveNum - 1], "#");
4634                 break;
4635             }
4636             strcat(parseList[moveNum - 1], " ");
4637             strcat(parseList[moveNum - 1], elapsed_time);
4638             /* currentMoveString is set as a side-effect of ParseOneMove */
4639             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4640             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4641             strcat(moveList[moveNum - 1], "\n");
4642
4643             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4644                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4645               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4646                 ChessSquare old, new = boards[moveNum][k][j];
4647                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4648                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4649                   if(old == new) continue;
4650                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4651                   else if(new == WhiteWazir || new == BlackWazir) {
4652                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4653                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4654                       else boards[moveNum][k][j] = old; // preserve type of Gold
4655                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4656                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4657               }
4658           } else {
4659             /* Move from ICS was illegal!?  Punt. */
4660             if (appData.debugMode) {
4661               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4662               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4663             }
4664             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4665             strcat(parseList[moveNum - 1], " ");
4666             strcat(parseList[moveNum - 1], elapsed_time);
4667             moveList[moveNum - 1][0] = NULLCHAR;
4668             fromX = fromY = toX = toY = -1;
4669           }
4670         }
4671   if (appData.debugMode) {
4672     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4673     setbuf(debugFP, NULL);
4674   }
4675
4676 #if ZIPPY
4677         /* Send move to chess program (BEFORE animating it). */
4678         if (appData.zippyPlay && !newGame && newMove &&
4679            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4680
4681             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4682                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4683                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4684                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4685                             move_str);
4686                     DisplayError(str, 0);
4687                 } else {
4688                     if (first.sendTime) {
4689                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4690                     }
4691                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4692                     if (firstMove && !bookHit) {
4693                         firstMove = FALSE;
4694                         if (first.useColors) {
4695                           SendToProgram(gameMode == IcsPlayingWhite ?
4696                                         "white\ngo\n" :
4697                                         "black\ngo\n", &first);
4698                         } else {
4699                           SendToProgram("go\n", &first);
4700                         }
4701                         first.maybeThinking = TRUE;
4702                     }
4703                 }
4704             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4705               if (moveList[moveNum - 1][0] == NULLCHAR) {
4706                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4707                 DisplayError(str, 0);
4708               } else {
4709                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4710                 SendMoveToProgram(moveNum - 1, &first);
4711               }
4712             }
4713         }
4714 #endif
4715     }
4716
4717     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4718         /* If move comes from a remote source, animate it.  If it
4719            isn't remote, it will have already been animated. */
4720         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4721             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4722         }
4723         if (!pausing && appData.highlightLastMove) {
4724             SetHighlights(fromX, fromY, toX, toY);
4725         }
4726     }
4727
4728     /* Start the clocks */
4729     whiteFlag = blackFlag = FALSE;
4730     appData.clockMode = !(basetime == 0 && increment == 0);
4731     if (ticking == 0) {
4732       ics_clock_paused = TRUE;
4733       StopClocks();
4734     } else if (ticking == 1) {
4735       ics_clock_paused = FALSE;
4736     }
4737     if (gameMode == IcsIdle ||
4738         relation == RELATION_OBSERVING_STATIC ||
4739         relation == RELATION_EXAMINING ||
4740         ics_clock_paused)
4741       DisplayBothClocks();
4742     else
4743       StartClocks();
4744
4745     /* Display opponents and material strengths */
4746     if (gameInfo.variant != VariantBughouse &&
4747         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4748         if (tinyLayout || smallLayout) {
4749             if(gameInfo.variant == VariantNormal)
4750               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4751                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4752                     basetime, increment);
4753             else
4754               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4755                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4756                     basetime, increment, (int) gameInfo.variant);
4757         } else {
4758             if(gameInfo.variant == VariantNormal)
4759               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4760                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4761                     basetime, increment);
4762             else
4763               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4764                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4765                     basetime, increment, VariantName(gameInfo.variant));
4766         }
4767         DisplayTitle(str);
4768   if (appData.debugMode) {
4769     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4770   }
4771     }
4772
4773
4774     /* Display the board */
4775     if (!pausing && !appData.noGUI) {
4776
4777       if (appData.premove)
4778           if (!gotPremove ||
4779              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4780              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4781               ClearPremoveHighlights();
4782
4783       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4784         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4785       DrawPosition(j, boards[currentMove]);
4786
4787       DisplayMove(moveNum - 1);
4788       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4789             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4790               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4791         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4792       }
4793     }
4794
4795     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4796 #if ZIPPY
4797     if(bookHit) { // [HGM] book: simulate book reply
4798         static char bookMove[MSG_SIZ]; // a bit generous?
4799
4800         programStats.nodes = programStats.depth = programStats.time =
4801         programStats.score = programStats.got_only_move = 0;
4802         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4803
4804         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4805         strcat(bookMove, bookHit);
4806         HandleMachineMove(bookMove, &first);
4807     }
4808 #endif
4809 }
4810
4811 void
4812 GetMoveListEvent()
4813 {
4814     char buf[MSG_SIZ];
4815     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4816         ics_getting_history = H_REQUESTED;
4817         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4818         SendToICS(buf);
4819     }
4820 }
4821
4822 void
4823 AnalysisPeriodicEvent(force)
4824      int force;
4825 {
4826     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4827          && !force) || !appData.periodicUpdates)
4828       return;
4829
4830     /* Send . command to Crafty to collect stats */
4831     SendToProgram(".\n", &first);
4832
4833     /* Don't send another until we get a response (this makes
4834        us stop sending to old Crafty's which don't understand
4835        the "." command (sending illegal cmds resets node count & time,
4836        which looks bad)) */
4837     programStats.ok_to_send = 0;
4838 }
4839
4840 void ics_update_width(new_width)
4841         int new_width;
4842 {
4843         ics_printf("set width %d\n", new_width);
4844 }
4845
4846 void
4847 SendMoveToProgram(moveNum, cps)
4848      int moveNum;
4849      ChessProgramState *cps;
4850 {
4851     char buf[MSG_SIZ];
4852
4853     if (cps->useUsermove) {
4854       SendToProgram("usermove ", cps);
4855     }
4856     if (cps->useSAN) {
4857       char *space;
4858       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4859         int len = space - parseList[moveNum];
4860         memcpy(buf, parseList[moveNum], len);
4861         buf[len++] = '\n';
4862         buf[len] = NULLCHAR;
4863       } else {
4864         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4865       }
4866       SendToProgram(buf, cps);
4867     } else {
4868       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4869         AlphaRank(moveList[moveNum], 4);
4870         SendToProgram(moveList[moveNum], cps);
4871         AlphaRank(moveList[moveNum], 4); // and back
4872       } else
4873       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4874        * the engine. It would be nice to have a better way to identify castle
4875        * moves here. */
4876       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4877                                                                          && cps->useOOCastle) {
4878         int fromX = moveList[moveNum][0] - AAA;
4879         int fromY = moveList[moveNum][1] - ONE;
4880         int toX = moveList[moveNum][2] - AAA;
4881         int toY = moveList[moveNum][3] - ONE;
4882         if((boards[moveNum][fromY][fromX] == WhiteKing
4883             && boards[moveNum][toY][toX] == WhiteRook)
4884            || (boards[moveNum][fromY][fromX] == BlackKing
4885                && boards[moveNum][toY][toX] == BlackRook)) {
4886           if(toX > fromX) SendToProgram("O-O\n", cps);
4887           else SendToProgram("O-O-O\n", cps);
4888         }
4889         else SendToProgram(moveList[moveNum], cps);
4890       }
4891       else SendToProgram(moveList[moveNum], cps);
4892       /* End of additions by Tord */
4893     }
4894
4895     /* [HGM] setting up the opening has brought engine in force mode! */
4896     /*       Send 'go' if we are in a mode where machine should play. */
4897     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4898         (gameMode == TwoMachinesPlay   ||
4899 #if ZIPPY
4900          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4901 #endif
4902          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4903         SendToProgram("go\n", cps);
4904   if (appData.debugMode) {
4905     fprintf(debugFP, "(extra)\n");
4906   }
4907     }
4908     setboardSpoiledMachineBlack = 0;
4909 }
4910
4911 void
4912 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4913      ChessMove moveType;
4914      int fromX, fromY, toX, toY;
4915      char promoChar;
4916 {
4917     char user_move[MSG_SIZ];
4918
4919     switch (moveType) {
4920       default:
4921         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4922                 (int)moveType, fromX, fromY, toX, toY);
4923         DisplayError(user_move + strlen("say "), 0);
4924         break;
4925       case WhiteKingSideCastle:
4926       case BlackKingSideCastle:
4927       case WhiteQueenSideCastleWild:
4928       case BlackQueenSideCastleWild:
4929       /* PUSH Fabien */
4930       case WhiteHSideCastleFR:
4931       case BlackHSideCastleFR:
4932       /* POP Fabien */
4933         snprintf(user_move, MSG_SIZ, "o-o\n");
4934         break;
4935       case WhiteQueenSideCastle:
4936       case BlackQueenSideCastle:
4937       case WhiteKingSideCastleWild:
4938       case BlackKingSideCastleWild:
4939       /* PUSH Fabien */
4940       case WhiteASideCastleFR:
4941       case BlackASideCastleFR:
4942       /* POP Fabien */
4943         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4944         break;
4945       case WhiteNonPromotion:
4946       case BlackNonPromotion:
4947         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4948         break;
4949       case WhitePromotion:
4950       case BlackPromotion:
4951         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4952           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4953                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4954                 PieceToChar(WhiteFerz));
4955         else if(gameInfo.variant == VariantGreat)
4956           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4957                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4958                 PieceToChar(WhiteMan));
4959         else
4960           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4961                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4962                 promoChar);
4963         break;
4964       case WhiteDrop:
4965       case BlackDrop:
4966       drop:
4967         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4968                  ToUpper(PieceToChar((ChessSquare) fromX)),
4969                  AAA + toX, ONE + toY);
4970         break;
4971       case IllegalMove:  /* could be a variant we don't quite understand */
4972         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4973       case NormalMove:
4974       case WhiteCapturesEnPassant:
4975       case BlackCapturesEnPassant:
4976         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4977                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4978         break;
4979     }
4980     SendToICS(user_move);
4981     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4982         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4983 }
4984
4985 void
4986 UploadGameEvent()
4987 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4988     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4989     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4990     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4991         DisplayError("You cannot do this while you are playing or observing", 0);
4992         return;
4993     }
4994     if(gameMode != IcsExamining) { // is this ever not the case?
4995         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4996
4997         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4998           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4999         } else { // on FICS we must first go to general examine mode
5000           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5001         }
5002         if(gameInfo.variant != VariantNormal) {
5003             // try figure out wild number, as xboard names are not always valid on ICS
5004             for(i=1; i<=36; i++) {
5005               snprintf(buf, MSG_SIZ, "wild/%d", i);
5006                 if(StringToVariant(buf) == gameInfo.variant) break;
5007             }
5008             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5009             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5010             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5011         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5012         SendToICS(ics_prefix);
5013         SendToICS(buf);
5014         if(startedFromSetupPosition || backwardMostMove != 0) {
5015           fen = PositionToFEN(backwardMostMove, NULL);
5016           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5017             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5018             SendToICS(buf);
5019           } else { // FICS: everything has to set by separate bsetup commands
5020             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5021             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5022             SendToICS(buf);
5023             if(!WhiteOnMove(backwardMostMove)) {
5024                 SendToICS("bsetup tomove black\n");
5025             }
5026             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5027             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5028             SendToICS(buf);
5029             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5030             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5031             SendToICS(buf);
5032             i = boards[backwardMostMove][EP_STATUS];
5033             if(i >= 0) { // set e.p.
5034               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5035                 SendToICS(buf);
5036             }
5037             bsetup++;
5038           }
5039         }
5040       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5041             SendToICS("bsetup done\n"); // switch to normal examining.
5042     }
5043     for(i = backwardMostMove; i<last; i++) {
5044         char buf[20];
5045         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5046         SendToICS(buf);
5047     }
5048     SendToICS(ics_prefix);
5049     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5050 }
5051
5052 void
5053 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5054      int rf, ff, rt, ft;
5055      char promoChar;
5056      char move[7];
5057 {
5058     if (rf == DROP_RANK) {
5059       sprintf(move, "%c@%c%c\n",
5060                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5061     } else {
5062         if (promoChar == 'x' || promoChar == NULLCHAR) {
5063           sprintf(move, "%c%c%c%c\n",
5064                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5065         } else {
5066             sprintf(move, "%c%c%c%c%c\n",
5067                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5068         }
5069     }
5070 }
5071
5072 void
5073 ProcessICSInitScript(f)
5074      FILE *f;
5075 {
5076     char buf[MSG_SIZ];
5077
5078     while (fgets(buf, MSG_SIZ, f)) {
5079         SendToICSDelayed(buf,(long)appData.msLoginDelay);
5080     }
5081
5082     fclose(f);
5083 }
5084
5085
5086 static int lastX, lastY, selectFlag, dragging;
5087
5088 void
5089 Sweep(int step)
5090 {
5091     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5092     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5093     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5094     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5095     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5096     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5097     do {
5098         promoSweep -= step;
5099         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5100         else if((int)promoSweep == -1) promoSweep = WhiteKing;
5101         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5102         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5103         if(!step) step = 1;
5104     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5105             appData.testLegality && (promoSweep == king ||
5106             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5107     ChangeDragPiece(promoSweep);
5108 }
5109
5110 int PromoScroll(int x, int y)
5111 {
5112   int step = 0;
5113
5114   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5115   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5116   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5117   if(!step) return FALSE;
5118   lastX = x; lastY = y;
5119   if((promoSweep < BlackPawn) == flipView) step = -step;
5120   if(step > 0) selectFlag = 1;
5121   if(!selectFlag) Sweep(step);
5122   return FALSE;
5123 }
5124
5125 void
5126 NextPiece(int step)
5127 {
5128     ChessSquare piece = boards[currentMove][toY][toX];
5129     do {
5130         pieceSweep -= step;
5131         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5132         if((int)pieceSweep == -1) pieceSweep = BlackKing;
5133         if(!step) step = -1;
5134     } while(PieceToChar(pieceSweep) == '.');
5135     boards[currentMove][toY][toX] = pieceSweep;
5136     DrawPosition(FALSE, boards[currentMove]);
5137     boards[currentMove][toY][toX] = piece;
5138 }
5139 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5140 void
5141 AlphaRank(char *move, int n)
5142 {
5143 //    char *p = move, c; int x, y;
5144
5145     if (appData.debugMode) {
5146         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5147     }
5148
5149     if(move[1]=='*' &&
5150        move[2]>='0' && move[2]<='9' &&
5151        move[3]>='a' && move[3]<='x'    ) {
5152         move[1] = '@';
5153         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5154         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5155     } else
5156     if(move[0]>='0' && move[0]<='9' &&
5157        move[1]>='a' && move[1]<='x' &&
5158        move[2]>='0' && move[2]<='9' &&
5159        move[3]>='a' && move[3]<='x'    ) {
5160         /* input move, Shogi -> normal */
5161         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
5162         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5163         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
5164         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5165     } else
5166     if(move[1]=='@' &&
5167        move[3]>='0' && move[3]<='9' &&
5168        move[2]>='a' && move[2]<='x'    ) {
5169         move[1] = '*';
5170         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5171         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5172     } else
5173     if(
5174        move[0]>='a' && move[0]<='x' &&
5175        move[3]>='0' && move[3]<='9' &&
5176        move[2]>='a' && move[2]<='x'    ) {
5177          /* output move, normal -> Shogi */
5178         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5179         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5180         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5181         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5182         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5183     }
5184     if (appData.debugMode) {
5185         fprintf(debugFP, "   out = '%s'\n", move);
5186     }
5187 }
5188
5189 char yy_textstr[8000];
5190
5191 /* Parser for moves from gnuchess, ICS, or user typein box */
5192 Boolean
5193 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5194      char *move;
5195      int moveNum;
5196      ChessMove *moveType;
5197      int *fromX, *fromY, *toX, *toY;
5198      char *promoChar;
5199 {
5200     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5201
5202     switch (*moveType) {
5203       case WhitePromotion:
5204       case BlackPromotion:
5205       case WhiteNonPromotion:
5206       case BlackNonPromotion:
5207       case NormalMove:
5208       case WhiteCapturesEnPassant:
5209       case BlackCapturesEnPassant:
5210       case WhiteKingSideCastle:
5211       case WhiteQueenSideCastle:
5212       case BlackKingSideCastle:
5213       case BlackQueenSideCastle:
5214       case WhiteKingSideCastleWild:
5215       case WhiteQueenSideCastleWild:
5216       case BlackKingSideCastleWild:
5217       case BlackQueenSideCastleWild:
5218       /* Code added by Tord: */
5219       case WhiteHSideCastleFR:
5220       case WhiteASideCastleFR:
5221       case BlackHSideCastleFR:
5222       case BlackASideCastleFR:
5223       /* End of code added by Tord */
5224       case IllegalMove:         /* bug or odd chess variant */
5225         *fromX = currentMoveString[0] - AAA;
5226         *fromY = currentMoveString[1] - ONE;
5227         *toX = currentMoveString[2] - AAA;
5228         *toY = currentMoveString[3] - ONE;
5229         *promoChar = currentMoveString[4];
5230         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5231             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5232     if (appData.debugMode) {
5233         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5234     }
5235             *fromX = *fromY = *toX = *toY = 0;
5236             return FALSE;
5237         }
5238         if (appData.testLegality) {
5239           return (*moveType != IllegalMove);
5240         } else {
5241           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5242                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5243         }
5244
5245       case WhiteDrop:
5246       case BlackDrop:
5247         *fromX = *moveType == WhiteDrop ?
5248           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5249           (int) CharToPiece(ToLower(currentMoveString[0]));
5250         *fromY = DROP_RANK;
5251         *toX = currentMoveString[2] - AAA;
5252         *toY = currentMoveString[3] - ONE;
5253         *promoChar = NULLCHAR;
5254         return TRUE;
5255
5256       case AmbiguousMove:
5257       case ImpossibleMove:
5258       case EndOfFile:
5259       case ElapsedTime:
5260       case Comment:
5261       case PGNTag:
5262       case NAG:
5263       case WhiteWins:
5264       case BlackWins:
5265       case GameIsDrawn:
5266       default:
5267     if (appData.debugMode) {
5268         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5269     }
5270         /* bug? */
5271         *fromX = *fromY = *toX = *toY = 0;
5272         *promoChar = NULLCHAR;
5273         return FALSE;
5274     }
5275 }
5276
5277 Boolean pushed = FALSE;
5278
5279 void
5280 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5281 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5282   int fromX, fromY, toX, toY; char promoChar;
5283   ChessMove moveType;
5284   Boolean valid;
5285   int nr = 0;
5286
5287   if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5288     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5289     pushed = TRUE;
5290   }
5291   endPV = forwardMostMove;
5292   do {
5293     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5294     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5295     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5296 if(appData.debugMode){
5297 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);
5298 }
5299     if(!valid && nr == 0 &&
5300        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5301         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5302         // Hande case where played move is different from leading PV move
5303         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5304         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5305         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5306         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5307           endPV += 2; // if position different, keep this
5308           moveList[endPV-1][0] = fromX + AAA;
5309           moveList[endPV-1][1] = fromY + ONE;
5310           moveList[endPV-1][2] = toX + AAA;
5311           moveList[endPV-1][3] = toY + ONE;
5312           parseList[endPV-1][0] = NULLCHAR;
5313           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5314         }
5315       }
5316     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5317     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5318     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5319     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5320         valid++; // allow comments in PV
5321         continue;
5322     }
5323     nr++;
5324     if(endPV+1 > framePtr) break; // no space, truncate
5325     if(!valid) break;
5326     endPV++;
5327     CopyBoard(boards[endPV], boards[endPV-1]);
5328     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5329     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5330     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5331     CoordsToAlgebraic(boards[endPV - 1],
5332                              PosFlags(endPV - 1),
5333                              fromY, fromX, toY, toX, promoChar,
5334                              parseList[endPV - 1]);
5335   } while(valid);
5336   currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5337   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5338   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5339                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5340   DrawPosition(TRUE, boards[currentMove]);
5341 }
5342
5343 int
5344 MultiPV(ChessProgramState *cps)
5345 {       // check if engine supports MultiPV, and if so, return the number of the option that sets it
5346         int i;
5347         for(i=0; i<cps->nrOptions; i++)
5348             if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5349                 return i;
5350         return -1;
5351 }
5352
5353 Boolean
5354 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5355 {
5356         int startPV, multi, lineStart, origIndex = index;
5357         char *p, buf2[MSG_SIZ];
5358
5359         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5360         lastX = x; lastY = y;
5361         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5362         lineStart = startPV = index;
5363         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5364         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5365         index = startPV;
5366         do{ while(buf[index] && buf[index] != '\n') index++;
5367         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5368         buf[index] = 0;
5369         if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5370                 int n = first.option[multi].value;
5371                 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5372                 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5373                 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5374                 first.option[multi].value = n;
5375                 *start = *end = 0;
5376                 return FALSE;
5377         }
5378         ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5379         *start = startPV; *end = index-1;
5380         return TRUE;
5381 }
5382
5383 Boolean
5384 LoadPV(int x, int y)
5385 { // called on right mouse click to load PV
5386   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5387   lastX = x; lastY = y;
5388   ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5389   return TRUE;
5390 }
5391
5392 void
5393 UnLoadPV()
5394 {
5395   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5396   if(endPV < 0) return;
5397   endPV = -1;
5398   if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5399         Boolean saveAnimate = appData.animate;
5400         if(pushed) {
5401             if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5402                 if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
5403             } else storedGames--; // abandon shelved tail of original game
5404         }
5405         pushed = FALSE;
5406         forwardMostMove = currentMove;
5407         currentMove = oldFMM;
5408         appData.animate = FALSE;
5409         ToNrEvent(forwardMostMove);
5410         appData.animate = saveAnimate;
5411   }
5412   currentMove = forwardMostMove;
5413   if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5414   ClearPremoveHighlights();
5415   DrawPosition(TRUE, boards[currentMove]);
5416 }
5417
5418 void
5419 MovePV(int x, int y, int h)
5420 { // step through PV based on mouse coordinates (called on mouse move)
5421   int margin = h>>3, step = 0;
5422
5423   // we must somehow check if right button is still down (might be released off board!)
5424   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5425   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5426   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5427   if(!step) return;
5428   lastX = x; lastY = y;
5429
5430   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5431   if(endPV < 0) return;
5432   if(y < margin) step = 1; else
5433   if(y > h - margin) step = -1;
5434   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5435   currentMove += step;
5436   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5437   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5438                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5439   DrawPosition(FALSE, boards[currentMove]);
5440 }
5441
5442
5443 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5444 // All positions will have equal probability, but the current method will not provide a unique
5445 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5446 #define DARK 1
5447 #define LITE 2
5448 #define ANY 3
5449
5450 int squaresLeft[4];
5451 int piecesLeft[(int)BlackPawn];
5452 int seed, nrOfShuffles;
5453
5454 void GetPositionNumber()
5455 {       // sets global variable seed
5456         int i;
5457
5458         seed = appData.defaultFrcPosition;
5459         if(seed < 0) { // randomize based on time for negative FRC position numbers
5460                 for(i=0; i<50; i++) seed += random();
5461                 seed = random() ^ random() >> 8 ^ random() << 8;
5462                 if(seed<0) seed = -seed;
5463         }
5464 }
5465
5466 int put(Board board, int pieceType, int rank, int n, int shade)
5467 // put the piece on the (n-1)-th empty squares of the given shade
5468 {
5469         int i;
5470
5471         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5472                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5473                         board[rank][i] = (ChessSquare) pieceType;
5474                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5475                         squaresLeft[ANY]--;
5476                         piecesLeft[pieceType]--;
5477                         return i;
5478                 }
5479         }
5480         return -1;
5481 }
5482
5483
5484 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5485 // calculate where the next piece goes, (any empty square), and put it there
5486 {
5487         int i;
5488
5489         i = seed % squaresLeft[shade];
5490         nrOfShuffles *= squaresLeft[shade];
5491         seed /= squaresLeft[shade];
5492         put(board, pieceType, rank, i, shade);
5493 }
5494
5495 void AddTwoPieces(Board board, int pieceType, int rank)
5496 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5497 {
5498         int i, n=squaresLeft[ANY], j=n-1, k;
5499
5500         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5501         i = seed % k;  // pick one
5502         nrOfShuffles *= k;
5503         seed /= k;
5504         while(i >= j) i -= j--;
5505         j = n - 1 - j; i += j;
5506         put(board, pieceType, rank, j, ANY);
5507         put(board, pieceType, rank, i, ANY);
5508 }
5509
5510 void SetUpShuffle(Board board, int number)
5511 {
5512         int i, p, first=1;
5513
5514         GetPositionNumber(); nrOfShuffles = 1;
5515
5516         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5517         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5518         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5519
5520         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5521
5522         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5523             p = (int) board[0][i];
5524             if(p < (int) BlackPawn) piecesLeft[p] ++;
5525             board[0][i] = EmptySquare;
5526         }
5527
5528         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5529             // shuffles restricted to allow normal castling put KRR first
5530             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5531                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5532             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5533                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5534             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5535                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5536             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5537                 put(board, WhiteRook, 0, 0, ANY);
5538             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5539         }
5540
5541         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5542             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5543             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5544                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5545                 while(piecesLeft[p] >= 2) {
5546                     AddOnePiece(board, p, 0, LITE);
5547                     AddOnePiece(board, p, 0, DARK);
5548                 }
5549                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5550             }
5551
5552         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5553             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5554             // but we leave King and Rooks for last, to possibly obey FRC restriction
5555             if(p == (int)WhiteRook) continue;
5556             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5557             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5558         }
5559
5560         // now everything is placed, except perhaps King (Unicorn) and Rooks
5561
5562         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5563             // Last King gets castling rights
5564             while(piecesLeft[(int)WhiteUnicorn]) {
5565                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5566                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5567             }
5568
5569             while(piecesLeft[(int)WhiteKing]) {
5570                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5571                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5572             }
5573
5574
5575         } else {
5576             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5577             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5578         }
5579
5580         // Only Rooks can be left; simply place them all
5581         while(piecesLeft[(int)WhiteRook]) {
5582                 i = put(board, WhiteRook, 0, 0, ANY);
5583                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5584                         if(first) {
5585                                 first=0;
5586                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5587                         }
5588                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5589                 }
5590         }
5591         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5592             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5593         }
5594
5595         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5596 }
5597
5598 int SetCharTable( char *table, const char * map )
5599 /* [HGM] moved here from winboard.c because of its general usefulness */
5600 /*       Basically a safe strcpy that uses the last character as King */
5601 {
5602     int result = FALSE; int NrPieces;
5603
5604     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5605                     && NrPieces >= 12 && !(NrPieces&1)) {
5606         int i; /* [HGM] Accept even length from 12 to 34 */
5607
5608         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5609         for( i=0; i<NrPieces/2-1; i++ ) {
5610             table[i] = map[i];
5611             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5612         }
5613         table[(int) WhiteKing]  = map[NrPieces/2-1];
5614         table[(int) BlackKing]  = map[NrPieces-1];
5615
5616         result = TRUE;
5617     }
5618
5619     return result;
5620 }
5621
5622 void Prelude(Board board)
5623 {       // [HGM] superchess: random selection of exo-pieces
5624         int i, j, k; ChessSquare p;
5625         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5626
5627         GetPositionNumber(); // use FRC position number
5628
5629         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5630             SetCharTable(pieceToChar, appData.pieceToCharTable);
5631             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5632                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5633         }
5634
5635         j = seed%4;                 seed /= 4;
5636         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5637         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5638         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5639         j = seed%3 + (seed%3 >= j); seed /= 3;
5640         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5641         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5642         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5643         j = seed%3;                 seed /= 3;
5644         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5645         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5646         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5647         j = seed%2 + (seed%2 >= j); seed /= 2;
5648         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5649         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5650         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5651         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5652         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5653         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5654         put(board, exoPieces[0],    0, 0, ANY);
5655         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5656 }
5657
5658 void
5659 InitPosition(redraw)
5660      int redraw;
5661 {
5662     ChessSquare (* pieces)[BOARD_FILES];
5663     int i, j, pawnRow, overrule,
5664     oldx = gameInfo.boardWidth,
5665     oldy = gameInfo.boardHeight,
5666     oldh = gameInfo.holdingsWidth;
5667     static int oldv;
5668
5669     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5670
5671     /* [AS] Initialize pv info list [HGM] and game status */
5672     {
5673         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5674             pvInfoList[i].depth = 0;
5675             boards[i][EP_STATUS] = EP_NONE;
5676             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5677         }
5678
5679         initialRulePlies = 0; /* 50-move counter start */
5680
5681         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5682         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5683     }
5684
5685
5686     /* [HGM] logic here is completely changed. In stead of full positions */
5687     /* the initialized data only consist of the two backranks. The switch */
5688     /* selects which one we will use, which is than copied to the Board   */
5689     /* initialPosition, which for the rest is initialized by Pawns and    */
5690     /* empty squares. This initial position is then copied to boards[0],  */
5691     /* possibly after shuffling, so that it remains available.            */
5692
5693     gameInfo.holdingsWidth = 0; /* default board sizes */
5694     gameInfo.boardWidth    = 8;
5695     gameInfo.boardHeight   = 8;
5696     gameInfo.holdingsSize  = 0;
5697     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5698     for(i=0; i<BOARD_FILES-2; i++)
5699       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5700     initialPosition[EP_STATUS] = EP_NONE;
5701     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5702     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5703          SetCharTable(pieceNickName, appData.pieceNickNames);
5704     else SetCharTable(pieceNickName, "............");
5705     pieces = FIDEArray;
5706
5707     switch (gameInfo.variant) {
5708     case VariantFischeRandom:
5709       shuffleOpenings = TRUE;
5710     default:
5711       break;
5712     case VariantShatranj:
5713       pieces = ShatranjArray;
5714       nrCastlingRights = 0;
5715       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5716       break;
5717     case VariantMakruk:
5718       pieces = makrukArray;
5719       nrCastlingRights = 0;
5720       startedFromSetupPosition = TRUE;
5721       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5722       break;
5723     case VariantTwoKings:
5724       pieces = twoKingsArray;
5725       break;
5726     case VariantCapaRandom:
5727       shuffleOpenings = TRUE;
5728     case VariantCapablanca:
5729       pieces = CapablancaArray;
5730       gameInfo.boardWidth = 10;
5731       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5732       break;
5733     case VariantGothic:
5734       pieces = GothicArray;
5735       gameInfo.boardWidth = 10;
5736       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5737       break;
5738     case VariantSChess:
5739       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5740       gameInfo.holdingsSize = 7;
5741       break;
5742     case VariantJanus:
5743       pieces = JanusArray;
5744       gameInfo.boardWidth = 10;
5745       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5746       nrCastlingRights = 6;
5747         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5748         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5749         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5750         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5751         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5752         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5753       break;
5754     case VariantFalcon:
5755       pieces = FalconArray;
5756       gameInfo.boardWidth = 10;
5757       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5758       break;
5759     case VariantXiangqi:
5760       pieces = XiangqiArray;
5761       gameInfo.boardWidth  = 9;
5762       gameInfo.boardHeight = 10;
5763       nrCastlingRights = 0;
5764       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5765       break;
5766     case VariantShogi:
5767       pieces = ShogiArray;
5768       gameInfo.boardWidth  = 9;
5769       gameInfo.boardHeight = 9;
5770       gameInfo.holdingsSize = 7;
5771       nrCastlingRights = 0;
5772       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5773       break;
5774     case VariantCourier:
5775       pieces = CourierArray;
5776       gameInfo.boardWidth  = 12;
5777       nrCastlingRights = 0;
5778       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5779       break;
5780     case VariantKnightmate:
5781       pieces = KnightmateArray;
5782       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5783       break;
5784     case VariantSpartan:
5785       pieces = SpartanArray;
5786       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5787       break;
5788     case VariantFairy:
5789       pieces = fairyArray;
5790       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5791       break;
5792     case VariantGreat:
5793       pieces = GreatArray;
5794       gameInfo.boardWidth = 10;
5795       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5796       gameInfo.holdingsSize = 8;
5797       break;
5798     case VariantSuper:
5799       pieces = FIDEArray;
5800       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5801       gameInfo.holdingsSize = 8;
5802       startedFromSetupPosition = TRUE;
5803       break;
5804     case VariantCrazyhouse:
5805     case VariantBughouse:
5806       pieces = FIDEArray;
5807       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5808       gameInfo.holdingsSize = 5;
5809       break;
5810     case VariantWildCastle:
5811       pieces = FIDEArray;
5812       /* !!?shuffle with kings guaranteed to be on d or e file */
5813       shuffleOpenings = 1;
5814       break;
5815     case VariantNoCastle:
5816       pieces = FIDEArray;
5817       nrCastlingRights = 0;
5818       /* !!?unconstrained back-rank shuffle */
5819       shuffleOpenings = 1;
5820       break;
5821     }
5822
5823     overrule = 0;
5824     if(appData.NrFiles >= 0) {
5825         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5826         gameInfo.boardWidth = appData.NrFiles;
5827     }
5828     if(appData.NrRanks >= 0) {
5829         gameInfo.boardHeight = appData.NrRanks;
5830     }
5831     if(appData.holdingsSize >= 0) {
5832         i = appData.holdingsSize;
5833         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5834         gameInfo.holdingsSize = i;
5835     }
5836     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5837     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5838         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5839
5840     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5841     if(pawnRow < 1) pawnRow = 1;
5842     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5843
5844     /* User pieceToChar list overrules defaults */
5845     if(appData.pieceToCharTable != NULL)
5846         SetCharTable(pieceToChar, appData.pieceToCharTable);
5847
5848     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5849
5850         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5851             s = (ChessSquare) 0; /* account holding counts in guard band */
5852         for( i=0; i<BOARD_HEIGHT; i++ )
5853             initialPosition[i][j] = s;
5854
5855         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5856         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5857         initialPosition[pawnRow][j] = WhitePawn;
5858         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5859         if(gameInfo.variant == VariantXiangqi) {
5860             if(j&1) {
5861                 initialPosition[pawnRow][j] =
5862                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5863                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5864                    initialPosition[2][j] = WhiteCannon;
5865                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5866                 }
5867             }
5868         }
5869         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5870     }
5871     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5872
5873             j=BOARD_LEFT+1;
5874             initialPosition[1][j] = WhiteBishop;
5875             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5876             j=BOARD_RGHT-2;
5877             initialPosition[1][j] = WhiteRook;
5878             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5879     }
5880
5881     if( nrCastlingRights == -1) {
5882         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5883         /*       This sets default castling rights from none to normal corners   */
5884         /* Variants with other castling rights must set them themselves above    */
5885         nrCastlingRights = 6;
5886
5887         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5888         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5889         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5890         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5891         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5892         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5893      }
5894
5895      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5896      if(gameInfo.variant == VariantGreat) { // promotion commoners
5897         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5898         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5899         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5900         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5901      }
5902      if( gameInfo.variant == VariantSChess ) {
5903       initialPosition[1][0] = BlackMarshall;
5904       initialPosition[2][0] = BlackAngel;
5905       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5906       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5907       initialPosition[1][1] = initialPosition[2][1] = 
5908       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5909      }
5910   if (appData.debugMode) {
5911     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5912   }
5913     if(shuffleOpenings) {
5914         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5915         startedFromSetupPosition = TRUE;
5916     }
5917     if(startedFromPositionFile) {
5918       /* [HGM] loadPos: use PositionFile for every new game */
5919       CopyBoard(initialPosition, filePosition);
5920       for(i=0; i<nrCastlingRights; i++)
5921           initialRights[i] = filePosition[CASTLING][i];
5922       startedFromSetupPosition = TRUE;
5923     }
5924
5925     CopyBoard(boards[0], initialPosition);
5926
5927     if(oldx != gameInfo.boardWidth ||
5928        oldy != gameInfo.boardHeight ||
5929        oldv != gameInfo.variant ||
5930        oldh != gameInfo.holdingsWidth
5931                                          )
5932             InitDrawingSizes(-2 ,0);
5933
5934     oldv = gameInfo.variant;
5935     if (redraw)
5936       DrawPosition(TRUE, boards[currentMove]);
5937 }
5938
5939 void
5940 SendBoard(cps, moveNum)
5941      ChessProgramState *cps;
5942      int moveNum;
5943 {
5944     char message[MSG_SIZ];
5945
5946     if (cps->useSetboard) {
5947       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5948       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5949       SendToProgram(message, cps);
5950       free(fen);
5951
5952     } else {
5953       ChessSquare *bp;
5954       int i, j;
5955       /* Kludge to set black to move, avoiding the troublesome and now
5956        * deprecated "black" command.
5957        */
5958       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5959         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5960
5961       SendToProgram("edit\n", cps);
5962       SendToProgram("#\n", cps);
5963       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5964         bp = &boards[moveNum][i][BOARD_LEFT];
5965         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5966           if ((int) *bp < (int) BlackPawn) {
5967             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5968                     AAA + j, ONE + i);
5969             if(message[0] == '+' || message[0] == '~') {
5970               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5971                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5972                         AAA + j, ONE + i);
5973             }
5974             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5975                 message[1] = BOARD_RGHT   - 1 - j + '1';
5976                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5977             }
5978             SendToProgram(message, cps);
5979           }
5980         }
5981       }
5982
5983       SendToProgram("c\n", cps);
5984       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5985         bp = &boards[moveNum][i][BOARD_LEFT];
5986         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5987           if (((int) *bp != (int) EmptySquare)
5988               && ((int) *bp >= (int) BlackPawn)) {
5989             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5990                     AAA + j, ONE + i);
5991             if(message[0] == '+' || message[0] == '~') {
5992               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5993                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5994                         AAA + j, ONE + i);
5995             }
5996             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5997                 message[1] = BOARD_RGHT   - 1 - j + '1';
5998                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5999             }
6000             SendToProgram(message, cps);
6001           }
6002         }
6003       }
6004
6005       SendToProgram(".\n", cps);
6006     }
6007     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6008 }
6009
6010 ChessSquare
6011 DefaultPromoChoice(int white)
6012 {
6013     ChessSquare result;
6014     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6015         result = WhiteFerz; // no choice
6016     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6017         result= WhiteKing; // in Suicide Q is the last thing we want
6018     else if(gameInfo.variant == VariantSpartan)
6019         result = white ? WhiteQueen : WhiteAngel;
6020     else result = WhiteQueen;
6021     if(!white) result = WHITE_TO_BLACK result;
6022     return result;
6023 }
6024
6025 static int autoQueen; // [HGM] oneclick
6026
6027 int
6028 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
6029 {
6030     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6031     /* [HGM] add Shogi promotions */
6032     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6033     ChessSquare piece;
6034     ChessMove moveType;
6035     Boolean premove;
6036
6037     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6038     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
6039
6040     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6041       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6042         return FALSE;
6043
6044     piece = boards[currentMove][fromY][fromX];
6045     if(gameInfo.variant == VariantShogi) {
6046         promotionZoneSize = BOARD_HEIGHT/3;
6047         highestPromotingPiece = (int)WhiteFerz;
6048     } else if(gameInfo.variant == VariantMakruk) {
6049         promotionZoneSize = 3;
6050     }
6051
6052     // Treat Lance as Pawn when it is not representing Amazon
6053     if(gameInfo.variant != VariantSuper) {
6054         if(piece == WhiteLance) piece = WhitePawn; else
6055         if(piece == BlackLance) piece = BlackPawn;
6056     }
6057
6058     // next weed out all moves that do not touch the promotion zone at all
6059     if((int)piece >= BlackPawn) {
6060         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6061              return FALSE;
6062         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6063     } else {
6064         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
6065            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6066     }
6067
6068     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6069
6070     // weed out mandatory Shogi promotions
6071     if(gameInfo.variant == VariantShogi) {
6072         if(piece >= BlackPawn) {
6073             if(toY == 0 && piece == BlackPawn ||
6074                toY == 0 && piece == BlackQueen ||
6075                toY <= 1 && piece == BlackKnight) {
6076                 *promoChoice = '+';
6077                 return FALSE;
6078             }
6079         } else {
6080             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6081                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6082                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6083                 *promoChoice = '+';
6084                 return FALSE;
6085             }
6086         }
6087     }
6088
6089     // weed out obviously illegal Pawn moves
6090     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
6091         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6092         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6093         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6094         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6095         // note we are not allowed to test for valid (non-)capture, due to premove
6096     }
6097
6098     // we either have a choice what to promote to, or (in Shogi) whether to promote
6099     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6100         *promoChoice = PieceToChar(BlackFerz);  // no choice
6101         return FALSE;
6102     }
6103     // no sense asking what we must promote to if it is going to explode...
6104     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6105         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6106         return FALSE;
6107     }
6108     // give caller the default choice even if we will not make it
6109     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6110     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6111     if(appData.sweepSelect && gameInfo.variant != VariantGreat
6112                            && gameInfo.variant != VariantShogi
6113                            && gameInfo.variant != VariantSuper) return FALSE;
6114     if(autoQueen) return FALSE; // predetermined
6115
6116     // suppress promotion popup on illegal moves that are not premoves
6117     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6118               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
6119     if(appData.testLegality && !premove) {
6120         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6121                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6122         if(moveType != WhitePromotion && moveType  != BlackPromotion)
6123             return FALSE;
6124     }
6125
6126     return TRUE;
6127 }
6128
6129 int
6130 InPalace(row, column)
6131      int row, column;
6132 {   /* [HGM] for Xiangqi */
6133     if( (row < 3 || row > BOARD_HEIGHT-4) &&
6134          column < (BOARD_WIDTH + 4)/2 &&
6135          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6136     return FALSE;
6137 }
6138
6139 int
6140 PieceForSquare (x, y)
6141      int x;
6142      int y;
6143 {
6144   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6145      return -1;
6146   else
6147      return boards[currentMove][y][x];
6148 }
6149
6150 int
6151 OKToStartUserMove(x, y)
6152      int x, y;
6153 {
6154     ChessSquare from_piece;
6155     int white_piece;
6156
6157     if (matchMode) return FALSE;
6158     if (gameMode == EditPosition) return TRUE;
6159
6160     if (x >= 0 && y >= 0)
6161       from_piece = boards[currentMove][y][x];
6162     else
6163       from_piece = EmptySquare;
6164
6165     if (from_piece == EmptySquare) return FALSE;
6166
6167     white_piece = (int)from_piece >= (int)WhitePawn &&
6168       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6169
6170     switch (gameMode) {
6171       case PlayFromGameFile:
6172       case AnalyzeFile:
6173       case TwoMachinesPlay:
6174       case EndOfGame:
6175         return FALSE;
6176
6177       case IcsObserving:
6178       case IcsIdle:
6179         return FALSE;
6180
6181       case MachinePlaysWhite:
6182       case IcsPlayingBlack:
6183         if (appData.zippyPlay) return FALSE;
6184         if (white_piece) {
6185             DisplayMoveError(_("You are playing Black"));
6186             return FALSE;
6187         }
6188         break;
6189
6190       case MachinePlaysBlack:
6191       case IcsPlayingWhite:
6192         if (appData.zippyPlay) return FALSE;
6193         if (!white_piece) {
6194             DisplayMoveError(_("You are playing White"));
6195             return FALSE;
6196         }
6197         break;
6198
6199       case EditGame:
6200         if (!white_piece && WhiteOnMove(currentMove)) {
6201             DisplayMoveError(_("It is White's turn"));
6202             return FALSE;
6203         }
6204         if (white_piece && !WhiteOnMove(currentMove)) {
6205             DisplayMoveError(_("It is Black's turn"));
6206             return FALSE;
6207         }
6208         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6209             /* Editing correspondence game history */
6210             /* Could disallow this or prompt for confirmation */
6211             cmailOldMove = -1;
6212         }
6213         break;
6214
6215       case BeginningOfGame:
6216         if (appData.icsActive) return FALSE;
6217         if (!appData.noChessProgram) {
6218             if (!white_piece) {
6219                 DisplayMoveError(_("You are playing White"));
6220                 return FALSE;
6221             }
6222         }
6223         break;
6224
6225       case Training:
6226         if (!white_piece && WhiteOnMove(currentMove)) {
6227             DisplayMoveError(_("It is White's turn"));
6228             return FALSE;
6229         }
6230         if (white_piece && !WhiteOnMove(currentMove)) {
6231             DisplayMoveError(_("It is Black's turn"));
6232             return FALSE;
6233         }
6234         break;
6235
6236       default:
6237       case IcsExamining:
6238         break;
6239     }
6240     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6241         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6242         && gameMode != AnalyzeFile && gameMode != Training) {
6243         DisplayMoveError(_("Displayed position is not current"));
6244         return FALSE;
6245     }
6246     return TRUE;
6247 }
6248
6249 Boolean
6250 OnlyMove(int *x, int *y, Boolean captures) {
6251     DisambiguateClosure cl;
6252     if (appData.zippyPlay) return FALSE;
6253     switch(gameMode) {
6254       case MachinePlaysBlack:
6255       case IcsPlayingWhite:
6256       case BeginningOfGame:
6257         if(!WhiteOnMove(currentMove)) return FALSE;
6258         break;
6259       case MachinePlaysWhite:
6260       case IcsPlayingBlack:
6261         if(WhiteOnMove(currentMove)) return FALSE;
6262         break;
6263       case EditGame:
6264         break;
6265       default:
6266         return FALSE;
6267     }
6268     cl.pieceIn = EmptySquare;
6269     cl.rfIn = *y;
6270     cl.ffIn = *x;
6271     cl.rtIn = -1;
6272     cl.ftIn = -1;
6273     cl.promoCharIn = NULLCHAR;
6274     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6275     if( cl.kind == NormalMove ||
6276         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6277         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6278         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6279       fromX = cl.ff;
6280       fromY = cl.rf;
6281       *x = cl.ft;
6282       *y = cl.rt;
6283       return TRUE;
6284     }
6285     if(cl.kind != ImpossibleMove) return FALSE;
6286     cl.pieceIn = EmptySquare;
6287     cl.rfIn = -1;
6288     cl.ffIn = -1;
6289     cl.rtIn = *y;
6290     cl.ftIn = *x;
6291     cl.promoCharIn = NULLCHAR;
6292     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6293     if( cl.kind == NormalMove ||
6294         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6295         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6296         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6297       fromX = cl.ff;
6298       fromY = cl.rf;
6299       *x = cl.ft;
6300       *y = cl.rt;
6301       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6302       return TRUE;
6303     }
6304     return FALSE;
6305 }
6306
6307 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6308 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6309 int lastLoadGameUseList = FALSE;
6310 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6311 ChessMove lastLoadGameStart = EndOfFile;
6312
6313 void
6314 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6315      int fromX, fromY, toX, toY;
6316      int promoChar;
6317 {
6318     ChessMove moveType;
6319     ChessSquare pdown, pup;
6320
6321     /* Check if the user is playing in turn.  This is complicated because we
6322        let the user "pick up" a piece before it is his turn.  So the piece he
6323        tried to pick up may have been captured by the time he puts it down!
6324        Therefore we use the color the user is supposed to be playing in this
6325        test, not the color of the piece that is currently on the starting
6326        square---except in EditGame mode, where the user is playing both
6327        sides; fortunately there the capture race can't happen.  (It can
6328        now happen in IcsExamining mode, but that's just too bad.  The user
6329        will get a somewhat confusing message in that case.)
6330        */
6331
6332     switch (gameMode) {
6333       case PlayFromGameFile:
6334       case AnalyzeFile:
6335       case TwoMachinesPlay:
6336       case EndOfGame:
6337       case IcsObserving:
6338       case IcsIdle:
6339         /* We switched into a game mode where moves are not accepted,
6340            perhaps while the mouse button was down. */
6341         return;
6342
6343       case MachinePlaysWhite:
6344         /* User is moving for Black */
6345         if (WhiteOnMove(currentMove)) {
6346             DisplayMoveError(_("It is White's turn"));
6347             return;
6348         }
6349         break;
6350
6351       case MachinePlaysBlack:
6352         /* User is moving for White */
6353         if (!WhiteOnMove(currentMove)) {
6354             DisplayMoveError(_("It is Black's turn"));
6355             return;
6356         }
6357         break;
6358
6359       case EditGame:
6360       case IcsExamining:
6361       case BeginningOfGame:
6362       case AnalyzeMode:
6363       case Training:
6364         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6365         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6366             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6367             /* User is moving for Black */
6368             if (WhiteOnMove(currentMove)) {
6369                 DisplayMoveError(_("It is White's turn"));
6370                 return;
6371             }
6372         } else {
6373             /* User is moving for White */
6374             if (!WhiteOnMove(currentMove)) {
6375                 DisplayMoveError(_("It is Black's turn"));
6376                 return;
6377             }
6378         }
6379         break;
6380
6381       case IcsPlayingBlack:
6382         /* User is moving for Black */
6383         if (WhiteOnMove(currentMove)) {
6384             if (!appData.premove) {
6385                 DisplayMoveError(_("It is White's turn"));
6386             } else if (toX >= 0 && toY >= 0) {
6387                 premoveToX = toX;
6388                 premoveToY = toY;
6389                 premoveFromX = fromX;
6390                 premoveFromY = fromY;
6391                 premovePromoChar = promoChar;
6392                 gotPremove = 1;
6393                 if (appData.debugMode)
6394                     fprintf(debugFP, "Got premove: fromX %d,"
6395                             "fromY %d, toX %d, toY %d\n",
6396                             fromX, fromY, toX, toY);
6397             }
6398             return;
6399         }
6400         break;
6401
6402       case IcsPlayingWhite:
6403         /* User is moving for White */
6404         if (!WhiteOnMove(currentMove)) {
6405             if (!appData.premove) {
6406                 DisplayMoveError(_("It is Black's turn"));
6407             } else if (toX >= 0 && toY >= 0) {
6408                 premoveToX = toX;
6409                 premoveToY = toY;
6410                 premoveFromX = fromX;
6411                 premoveFromY = fromY;
6412                 premovePromoChar = promoChar;
6413                 gotPremove = 1;
6414                 if (appData.debugMode)
6415                     fprintf(debugFP, "Got premove: fromX %d,"
6416                             "fromY %d, toX %d, toY %d\n",
6417                             fromX, fromY, toX, toY);
6418             }
6419             return;
6420         }
6421         break;
6422
6423       default:
6424         break;
6425
6426       case EditPosition:
6427         /* EditPosition, empty square, or different color piece;
6428            click-click move is possible */
6429         if (toX == -2 || toY == -2) {
6430             boards[0][fromY][fromX] = EmptySquare;
6431             DrawPosition(FALSE, boards[currentMove]);
6432             return;
6433         } else if (toX >= 0 && toY >= 0) {
6434             boards[0][toY][toX] = boards[0][fromY][fromX];
6435             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6436                 if(boards[0][fromY][0] != EmptySquare) {
6437                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6438                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6439                 }
6440             } else
6441             if(fromX == BOARD_RGHT+1) {
6442                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6443                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6444                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6445                 }
6446             } else
6447             boards[0][fromY][fromX] = EmptySquare;
6448             DrawPosition(FALSE, boards[currentMove]);
6449             return;
6450         }
6451         return;
6452     }
6453
6454     if(toX < 0 || toY < 0) return;
6455     pdown = boards[currentMove][fromY][fromX];
6456     pup = boards[currentMove][toY][toX];
6457
6458     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6459     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6460          if( pup != EmptySquare ) return;
6461          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6462            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6463                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6464            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6465            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6466            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6467            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6468          fromY = DROP_RANK;
6469     }
6470
6471     /* [HGM] always test for legality, to get promotion info */
6472     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6473                                          fromY, fromX, toY, toX, promoChar);
6474     /* [HGM] but possibly ignore an IllegalMove result */
6475     if (appData.testLegality) {
6476         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6477             DisplayMoveError(_("Illegal move"));
6478             return;
6479         }
6480     }
6481
6482     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6483 }
6484
6485 /* Common tail of UserMoveEvent and DropMenuEvent */
6486 int
6487 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6488      ChessMove moveType;
6489      int fromX, fromY, toX, toY;
6490      /*char*/int promoChar;
6491 {
6492     char *bookHit = 0;
6493
6494     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6495         // [HGM] superchess: suppress promotions to non-available piece
6496         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6497         if(WhiteOnMove(currentMove)) {
6498             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6499         } else {
6500             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6501         }
6502     }
6503
6504     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6505        move type in caller when we know the move is a legal promotion */
6506     if(moveType == NormalMove && promoChar)
6507         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6508
6509     /* [HGM] <popupFix> The following if has been moved here from
6510        UserMoveEvent(). Because it seemed to belong here (why not allow
6511        piece drops in training games?), and because it can only be
6512        performed after it is known to what we promote. */
6513     if (gameMode == Training) {
6514       /* compare the move played on the board to the next move in the
6515        * game. If they match, display the move and the opponent's response.
6516        * If they don't match, display an error message.
6517        */
6518       int saveAnimate;
6519       Board testBoard;
6520       CopyBoard(testBoard, boards[currentMove]);
6521       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6522
6523       if (CompareBoards(testBoard, boards[currentMove+1])) {
6524         ForwardInner(currentMove+1);
6525
6526         /* Autoplay the opponent's response.
6527          * if appData.animate was TRUE when Training mode was entered,
6528          * the response will be animated.
6529          */
6530         saveAnimate = appData.animate;
6531         appData.animate = animateTraining;
6532         ForwardInner(currentMove+1);
6533         appData.animate = saveAnimate;
6534
6535         /* check for the end of the game */
6536         if (currentMove >= forwardMostMove) {
6537           gameMode = PlayFromGameFile;
6538           ModeHighlight();
6539           SetTrainingModeOff();
6540           DisplayInformation(_("End of game"));
6541         }
6542       } else {
6543         DisplayError(_("Incorrect move"), 0);
6544       }
6545       return 1;
6546     }
6547
6548   /* Ok, now we know that the move is good, so we can kill
6549      the previous line in Analysis Mode */
6550   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6551                                 && currentMove < forwardMostMove) {
6552     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6553     else forwardMostMove = currentMove;
6554   }
6555
6556   /* If we need the chess program but it's dead, restart it */
6557   ResurrectChessProgram();
6558
6559   /* A user move restarts a paused game*/
6560   if (pausing)
6561     PauseEvent();
6562
6563   thinkOutput[0] = NULLCHAR;
6564
6565   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6566
6567   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6568     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6569     return 1;
6570   }
6571
6572   if (gameMode == BeginningOfGame) {
6573     if (appData.noChessProgram) {
6574       gameMode = EditGame;
6575       SetGameInfo();
6576     } else {
6577       char buf[MSG_SIZ];
6578       gameMode = MachinePlaysBlack;
6579       StartClocks();
6580       SetGameInfo();
6581       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6582       DisplayTitle(buf);
6583       if (first.sendName) {
6584         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6585         SendToProgram(buf, &first);
6586       }
6587       StartClocks();
6588     }
6589     ModeHighlight();
6590   }
6591
6592   /* Relay move to ICS or chess engine */
6593   if (appData.icsActive) {
6594     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6595         gameMode == IcsExamining) {
6596       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6597         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6598         SendToICS("draw ");
6599         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6600       }
6601       // also send plain move, in case ICS does not understand atomic claims
6602       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6603       ics_user_moved = 1;
6604     }
6605   } else {
6606     if (first.sendTime && (gameMode == BeginningOfGame ||
6607                            gameMode == MachinePlaysWhite ||
6608                            gameMode == MachinePlaysBlack)) {
6609       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6610     }
6611     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6612          // [HGM] book: if program might be playing, let it use book
6613         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6614         first.maybeThinking = TRUE;
6615     } else SendMoveToProgram(forwardMostMove-1, &first);
6616     if (currentMove == cmailOldMove + 1) {
6617       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6618     }
6619   }
6620
6621   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6622
6623   switch (gameMode) {
6624   case EditGame:
6625     if(appData.testLegality)
6626     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6627     case MT_NONE:
6628     case MT_CHECK:
6629       break;
6630     case MT_CHECKMATE:
6631     case MT_STAINMATE:
6632       if (WhiteOnMove(currentMove)) {
6633         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6634       } else {
6635         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6636       }
6637       break;
6638     case MT_STALEMATE:
6639       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6640       break;
6641     }
6642     break;
6643
6644   case MachinePlaysBlack:
6645   case MachinePlaysWhite:
6646     /* disable certain menu options while machine is thinking */
6647     SetMachineThinkingEnables();
6648     break;
6649
6650   default:
6651     break;
6652   }
6653
6654   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6655   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6656
6657   if(bookHit) { // [HGM] book: simulate book reply
6658         static char bookMove[MSG_SIZ]; // a bit generous?
6659
6660         programStats.nodes = programStats.depth = programStats.time =
6661         programStats.score = programStats.got_only_move = 0;
6662         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6663
6664         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6665         strcat(bookMove, bookHit);
6666         HandleMachineMove(bookMove, &first);
6667   }
6668   return 1;
6669 }
6670
6671 void
6672 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6673      Board board;
6674      int flags;
6675      ChessMove kind;
6676      int rf, ff, rt, ft;
6677      VOIDSTAR closure;
6678 {
6679     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6680     Markers *m = (Markers *) closure;
6681     if(rf == fromY && ff == fromX)
6682         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6683                          || kind == WhiteCapturesEnPassant
6684                          || kind == BlackCapturesEnPassant);
6685     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6686 }
6687
6688 void
6689 MarkTargetSquares(int clear)
6690 {
6691   int x, y;
6692   if(!appData.markers || !appData.highlightDragging ||
6693      !appData.testLegality || gameMode == EditPosition) return;
6694   if(clear) {
6695     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6696   } else {
6697     int capt = 0;
6698     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6699     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6700       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6701       if(capt)
6702       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6703     }
6704   }
6705   DrawPosition(TRUE, NULL);
6706 }
6707
6708 int
6709 Explode(Board board, int fromX, int fromY, int toX, int toY)
6710 {
6711     if(gameInfo.variant == VariantAtomic &&
6712        (board[toY][toX] != EmptySquare ||                     // capture?
6713         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6714                          board[fromY][fromX] == BlackPawn   )
6715       )) {
6716         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6717         return TRUE;
6718     }
6719     return FALSE;
6720 }
6721
6722 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6723
6724 int CanPromote(ChessSquare piece, int y)
6725 {
6726         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6727         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6728         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6729            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6730            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6731                                                   gameInfo.variant == VariantMakruk) return FALSE;
6732         return (piece == BlackPawn && y == 1 ||
6733                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6734                 piece == BlackLance && y == 1 ||
6735                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6736 }
6737
6738 void LeftClick(ClickType clickType, int xPix, int yPix)
6739 {
6740     int x, y;
6741     Boolean saveAnimate;
6742     static int second = 0, promotionChoice = 0, clearFlag = 0;
6743     char promoChoice = NULLCHAR;
6744     ChessSquare piece;
6745
6746     if(appData.seekGraph && appData.icsActive && loggedOn &&
6747         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6748         SeekGraphClick(clickType, xPix, yPix, 0);
6749         return;
6750     }
6751
6752     if (clickType == Press) ErrorPopDown();
6753     MarkTargetSquares(1);
6754
6755     x = EventToSquare(xPix, BOARD_WIDTH);
6756     y = EventToSquare(yPix, BOARD_HEIGHT);
6757     if (!flipView && y >= 0) {
6758         y = BOARD_HEIGHT - 1 - y;
6759     }
6760     if (flipView && x >= 0) {
6761         x = BOARD_WIDTH - 1 - x;
6762     }
6763
6764     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6765         defaultPromoChoice = promoSweep;
6766         promoSweep = EmptySquare;   // terminate sweep
6767         promoDefaultAltered = TRUE;
6768         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6769     }
6770
6771     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6772         if(clickType == Release) return; // ignore upclick of click-click destination
6773         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6774         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6775         if(gameInfo.holdingsWidth &&
6776                 (WhiteOnMove(currentMove)
6777                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6778                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6779             // click in right holdings, for determining promotion piece
6780             ChessSquare p = boards[currentMove][y][x];
6781             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6782             if(p != EmptySquare) {
6783                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6784                 fromX = fromY = -1;
6785                 return;
6786             }
6787         }
6788         DrawPosition(FALSE, boards[currentMove]);
6789         return;
6790     }
6791
6792     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6793     if(clickType == Press
6794             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6795               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6796               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6797         return;
6798
6799     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6800         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6801
6802     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6803         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6804                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6805         defaultPromoChoice = DefaultPromoChoice(side);
6806     }
6807
6808     autoQueen = appData.alwaysPromoteToQueen;
6809
6810     if (fromX == -1) {
6811       int originalY = y;
6812       gatingPiece = EmptySquare;
6813       if (clickType != Press) {
6814         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6815             DragPieceEnd(xPix, yPix); dragging = 0;
6816             DrawPosition(FALSE, NULL);
6817         }
6818         return;
6819       }
6820       fromX = x; fromY = y;
6821       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6822          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6823          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6824             /* First square */
6825             if (OKToStartUserMove(fromX, fromY)) {
6826                 second = 0;
6827                 MarkTargetSquares(0);
6828                 DragPieceBegin(xPix, yPix); dragging = 1;
6829                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6830                     promoSweep = defaultPromoChoice;
6831                     selectFlag = 0; lastX = xPix; lastY = yPix;
6832                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6833                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6834                 }
6835                 if (appData.highlightDragging) {
6836                     SetHighlights(fromX, fromY, -1, -1);
6837                 }
6838             } else fromX = fromY = -1;
6839             return;
6840         }
6841     }
6842
6843     /* fromX != -1 */
6844     if (clickType == Press && gameMode != EditPosition) {
6845         ChessSquare fromP;
6846         ChessSquare toP;
6847         int frc;
6848
6849         // ignore off-board to clicks
6850         if(y < 0 || x < 0) return;
6851
6852         /* Check if clicking again on the same color piece */
6853         fromP = boards[currentMove][fromY][fromX];
6854         toP = boards[currentMove][y][x];
6855         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6856         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6857              WhitePawn <= toP && toP <= WhiteKing &&
6858              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6859              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6860             (BlackPawn <= fromP && fromP <= BlackKing &&
6861              BlackPawn <= toP && toP <= BlackKing &&
6862              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6863              !(fromP == BlackKing && toP == BlackRook && frc))) {
6864             /* Clicked again on same color piece -- changed his mind */
6865             second = (x == fromX && y == fromY);
6866             promoDefaultAltered = FALSE;
6867            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6868             if (appData.highlightDragging) {
6869                 SetHighlights(x, y, -1, -1);
6870             } else {
6871                 ClearHighlights();
6872             }
6873             if (OKToStartUserMove(x, y)) {
6874                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6875                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6876                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6877                  gatingPiece = boards[currentMove][fromY][fromX];
6878                 else gatingPiece = EmptySquare;
6879                 fromX = x;
6880                 fromY = y; dragging = 1;
6881                 MarkTargetSquares(0);
6882                 DragPieceBegin(xPix, yPix);
6883                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6884                     promoSweep = defaultPromoChoice;
6885                     selectFlag = 0; lastX = xPix; lastY = yPix;
6886                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6887                 }
6888             }
6889            }
6890            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6891            second = FALSE; 
6892         }
6893         // ignore clicks on holdings
6894         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6895     }
6896
6897     if (clickType == Release && x == fromX && y == fromY) {
6898         DragPieceEnd(xPix, yPix); dragging = 0;
6899         if(clearFlag) {
6900             // a deferred attempt to click-click move an empty square on top of a piece
6901             boards[currentMove][y][x] = EmptySquare;
6902             ClearHighlights();
6903             DrawPosition(FALSE, boards[currentMove]);
6904             fromX = fromY = -1; clearFlag = 0;
6905             return;
6906         }
6907         if (appData.animateDragging) {
6908             /* Undo animation damage if any */
6909             DrawPosition(FALSE, NULL);
6910         }
6911         if (second) {
6912             /* Second up/down in same square; just abort move */
6913             second = 0;
6914             fromX = fromY = -1;
6915             gatingPiece = EmptySquare;
6916             ClearHighlights();
6917             gotPremove = 0;
6918             ClearPremoveHighlights();
6919         } else {
6920             /* First upclick in same square; start click-click mode */
6921             SetHighlights(x, y, -1, -1);
6922         }
6923         return;
6924     }
6925
6926     clearFlag = 0;
6927
6928     /* we now have a different from- and (possibly off-board) to-square */
6929     /* Completed move */
6930     toX = x;
6931     toY = y;
6932     saveAnimate = appData.animate;
6933     if (clickType == Press) {
6934         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6935             // must be Edit Position mode with empty-square selected
6936             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6937             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6938             return;
6939         }
6940         /* Finish clickclick move */
6941         if (appData.animate || appData.highlightLastMove) {
6942             SetHighlights(fromX, fromY, toX, toY);
6943         } else {
6944             ClearHighlights();
6945         }
6946     } else {
6947         /* Finish drag move */
6948         if (appData.highlightLastMove) {
6949             SetHighlights(fromX, fromY, toX, toY);
6950         } else {
6951             ClearHighlights();
6952         }
6953         DragPieceEnd(xPix, yPix); dragging = 0;
6954         /* Don't animate move and drag both */
6955         appData.animate = FALSE;
6956     }
6957
6958     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6959     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6960         ChessSquare piece = boards[currentMove][fromY][fromX];
6961         if(gameMode == EditPosition && piece != EmptySquare &&
6962            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6963             int n;
6964
6965             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6966                 n = PieceToNumber(piece - (int)BlackPawn);
6967                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6968                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6969                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6970             } else
6971             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6972                 n = PieceToNumber(piece);
6973                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6974                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6975                 boards[currentMove][n][BOARD_WIDTH-2]++;
6976             }
6977             boards[currentMove][fromY][fromX] = EmptySquare;
6978         }
6979         ClearHighlights();
6980         fromX = fromY = -1;
6981         DrawPosition(TRUE, boards[currentMove]);
6982         return;
6983     }
6984
6985     // off-board moves should not be highlighted
6986     if(x < 0 || y < 0) ClearHighlights();
6987
6988     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6989
6990     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6991         SetHighlights(fromX, fromY, toX, toY);
6992         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6993             // [HGM] super: promotion to captured piece selected from holdings
6994             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6995             promotionChoice = TRUE;
6996             // kludge follows to temporarily execute move on display, without promoting yet
6997             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6998             boards[currentMove][toY][toX] = p;
6999             DrawPosition(FALSE, boards[currentMove]);
7000             boards[currentMove][fromY][fromX] = p; // take back, but display stays
7001             boards[currentMove][toY][toX] = q;
7002             DisplayMessage("Click in holdings to choose piece", "");
7003             return;
7004         }
7005         PromotionPopUp();
7006     } else {
7007         int oldMove = currentMove;
7008         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7009         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7010         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7011         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7012            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7013             DrawPosition(TRUE, boards[currentMove]);
7014         fromX = fromY = -1;
7015     }
7016     appData.animate = saveAnimate;
7017     if (appData.animate || appData.animateDragging) {
7018         /* Undo animation damage if needed */
7019         DrawPosition(FALSE, NULL);
7020     }
7021 }
7022
7023 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7024 {   // front-end-free part taken out of PieceMenuPopup
7025     int whichMenu; int xSqr, ySqr;
7026
7027     if(seekGraphUp) { // [HGM] seekgraph
7028         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7029         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7030         return -2;
7031     }
7032
7033     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7034          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7035         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7036         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7037         if(action == Press)   {
7038             originalFlip = flipView;
7039             flipView = !flipView; // temporarily flip board to see game from partners perspective
7040             DrawPosition(TRUE, partnerBoard);
7041             DisplayMessage(partnerStatus, "");
7042             partnerUp = TRUE;
7043         } else if(action == Release) {
7044             flipView = originalFlip;
7045             DrawPosition(TRUE, boards[currentMove]);
7046             partnerUp = FALSE;
7047         }
7048         return -2;
7049     }
7050
7051     xSqr = EventToSquare(x, BOARD_WIDTH);
7052     ySqr = EventToSquare(y, BOARD_HEIGHT);
7053     if (action == Release) {
7054         if(pieceSweep != EmptySquare) {
7055             EditPositionMenuEvent(pieceSweep, toX, toY);
7056             pieceSweep = EmptySquare;
7057         } else UnLoadPV(); // [HGM] pv
7058     }
7059     if (action != Press) return -2; // return code to be ignored
7060     switch (gameMode) {
7061       case IcsExamining:
7062         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
7063       case EditPosition:
7064         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
7065         if (xSqr < 0 || ySqr < 0) return -1;
7066         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7067         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
7068         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7069         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7070         NextPiece(0);
7071         return -2;\r
7072       case IcsObserving:
7073         if(!appData.icsEngineAnalyze) return -1;
7074       case IcsPlayingWhite:
7075       case IcsPlayingBlack:
7076         if(!appData.zippyPlay) goto noZip;
7077       case AnalyzeMode:
7078       case AnalyzeFile:
7079       case MachinePlaysWhite:
7080       case MachinePlaysBlack:
7081       case TwoMachinesPlay: // [HGM] pv: use for showing PV
7082         if (!appData.dropMenu) {
7083           LoadPV(x, y);
7084           return 2; // flag front-end to grab mouse events
7085         }
7086         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7087            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7088       case EditGame:
7089       noZip:
7090         if (xSqr < 0 || ySqr < 0) return -1;
7091         if (!appData.dropMenu || appData.testLegality &&
7092             gameInfo.variant != VariantBughouse &&
7093             gameInfo.variant != VariantCrazyhouse) return -1;
7094         whichMenu = 1; // drop menu
7095         break;
7096       default:
7097         return -1;
7098     }
7099
7100     if (((*fromX = xSqr) < 0) ||
7101         ((*fromY = ySqr) < 0)) {
7102         *fromX = *fromY = -1;
7103         return -1;
7104     }
7105     if (flipView)
7106       *fromX = BOARD_WIDTH - 1 - *fromX;
7107     else
7108       *fromY = BOARD_HEIGHT - 1 - *fromY;
7109
7110     return whichMenu;
7111 }
7112
7113 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7114 {
7115 //    char * hint = lastHint;
7116     FrontEndProgramStats stats;
7117
7118     stats.which = cps == &first ? 0 : 1;
7119     stats.depth = cpstats->depth;
7120     stats.nodes = cpstats->nodes;
7121     stats.score = cpstats->score;
7122     stats.time = cpstats->time;
7123     stats.pv = cpstats->movelist;
7124     stats.hint = lastHint;
7125     stats.an_move_index = 0;
7126     stats.an_move_count = 0;
7127
7128     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7129         stats.hint = cpstats->move_name;
7130         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7131         stats.an_move_count = cpstats->nr_moves;
7132     }
7133
7134     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
7135
7136     SetProgramStats( &stats );
7137 }
7138
7139 void
7140 ClearEngineOutputPane(int which)
7141 {
7142     static FrontEndProgramStats dummyStats;
7143     dummyStats.which = which;
7144     dummyStats.pv = "#";
7145     SetProgramStats( &dummyStats );
7146 }
7147
7148 #define MAXPLAYERS 500
7149
7150 char *
7151 TourneyStandings(int display)
7152 {
7153     int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7154     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7155     char result, *p, *names[MAXPLAYERS];
7156
7157     names[0] = p = strdup(appData.participants);
7158     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7159
7160     for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7161
7162     while(result = appData.results[nr]) {
7163         color = Pairing(nr, nPlayers, &w, &b, &dummy);
7164         if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7165         wScore = bScore = 0;
7166         switch(result) {
7167           case '+': wScore = 2; break;
7168           case '-': bScore = 2; break;
7169           case '=': wScore = bScore = 1; break;
7170           case ' ':
7171           case '*': return strdup("busy"); // tourney not finished
7172         }
7173         score[w] += wScore;
7174         score[b] += bScore;
7175         games[w]++;
7176         games[b]++;
7177         nr++;
7178     }
7179     if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
7180     if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7181     for(w=0; w<nPlayers; w++) {
7182         bScore = -1;
7183         for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7184         ranking[w] = b; points[w] = bScore; score[b] = -2;
7185     }
7186     p = malloc(nPlayers*34+1);
7187     for(w=0; w<nPlayers && w<display; w++)
7188         sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7189     free(names[0]);
7190     return p;
7191 }
7192
7193 void
7194 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7195 {       // count all piece types
7196         int p, f, r;
7197         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7198         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7199         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7200                 p = board[r][f];
7201                 pCnt[p]++;
7202                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7203                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7204                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7205                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7206                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
7207                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7208         }
7209 }
7210
7211 int
7212 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7213 {
7214         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7215         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7216
7217         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7218         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7219         if(myPawns == 2 && nMine == 3) // KPP
7220             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7221         if(myPawns == 1 && nMine == 2) // KP
7222             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
7223         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7224             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7225         if(myPawns) return FALSE;
7226         if(pCnt[WhiteRook+side])
7227             return pCnt[BlackRook-side] ||
7228                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7229                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7230                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7231         if(pCnt[WhiteCannon+side]) {
7232             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7233             return majorDefense || pCnt[BlackAlfil-side] >= 2;
7234         }
7235         if(pCnt[WhiteKnight+side])
7236             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7237         return FALSE;
7238 }
7239
7240 int
7241 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7242 {
7243         VariantClass v = gameInfo.variant;
7244
7245         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7246         if(v == VariantShatranj) return TRUE; // always winnable through baring
7247         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7248         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7249
7250         if(v == VariantXiangqi) {
7251                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7252
7253                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7254                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7255                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7256                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7257                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7258                 if(stale) // we have at least one last-rank P plus perhaps C
7259                     return majors // KPKX
7260                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7261                 else // KCA*E*
7262                     return pCnt[WhiteFerz+side] // KCAK
7263                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7264                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7265                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7266
7267         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7268                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7269
7270                 if(nMine == 1) return FALSE; // bare King
7271                 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
7272                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7273                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7274                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7275                 if(pCnt[WhiteKnight+side])
7276                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7277                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7278                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7279                 if(nBishops)
7280                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7281                 if(pCnt[WhiteAlfil+side])
7282                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7283                 if(pCnt[WhiteWazir+side])
7284                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7285         }
7286
7287         return TRUE;
7288 }
7289
7290 int
7291 Adjudicate(ChessProgramState *cps)
7292 {       // [HGM] some adjudications useful with buggy engines
7293         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7294         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7295         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7296         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7297         int k, count = 0; static int bare = 1;
7298         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7299         Boolean canAdjudicate = !appData.icsActive;
7300
7301         // most tests only when we understand the game, i.e. legality-checking on
7302             if( appData.testLegality )
7303             {   /* [HGM] Some more adjudications for obstinate engines */
7304                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7305                 static int moveCount = 6;
7306                 ChessMove result;
7307                 char *reason = NULL;
7308
7309                 /* Count what is on board. */
7310                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7311
7312                 /* Some material-based adjudications that have to be made before stalemate test */
7313                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7314                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7315                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7316                      if(canAdjudicate && appData.checkMates) {
7317                          if(engineOpponent)
7318                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7319                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7320                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7321                          return 1;
7322                      }
7323                 }
7324
7325                 /* Bare King in Shatranj (loses) or Losers (wins) */
7326                 if( nrW == 1 || nrB == 1) {
7327                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7328                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7329                      if(canAdjudicate && appData.checkMates) {
7330                          if(engineOpponent)
7331                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7332                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7333                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7334                          return 1;
7335                      }
7336                   } else
7337                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7338                   {    /* bare King */
7339                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7340                         if(canAdjudicate && appData.checkMates) {
7341                             /* but only adjudicate if adjudication enabled */
7342                             if(engineOpponent)
7343                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7344                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7345                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7346                             return 1;
7347                         }
7348                   }
7349                 } else bare = 1;
7350
7351
7352             // don't wait for engine to announce game end if we can judge ourselves
7353             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7354               case MT_CHECK:
7355                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7356                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7357                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7358                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7359                             checkCnt++;
7360                         if(checkCnt >= 2) {
7361                             reason = "Xboard adjudication: 3rd check";
7362                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7363                             break;
7364                         }
7365                     }
7366                 }
7367               case MT_NONE:
7368               default:
7369                 break;
7370               case MT_STALEMATE:
7371               case MT_STAINMATE:
7372                 reason = "Xboard adjudication: Stalemate";
7373                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7374                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7375                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7376                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7377                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7378                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7379                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7380                                                                         EP_CHECKMATE : EP_WINS);
7381                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7382                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7383                 }
7384                 break;
7385               case MT_CHECKMATE:
7386                 reason = "Xboard adjudication: Checkmate";
7387                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7388                 break;
7389             }
7390
7391                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7392                     case EP_STALEMATE:
7393                         result = GameIsDrawn; break;
7394                     case EP_CHECKMATE:
7395                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7396                     case EP_WINS:
7397                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7398                     default:
7399                         result = EndOfFile;
7400                 }
7401                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7402                     if(engineOpponent)
7403                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7404                     GameEnds( result, reason, GE_XBOARD );
7405                     return 1;
7406                 }
7407
7408                 /* Next absolutely insufficient mating material. */
7409                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7410                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7411                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7412
7413                      /* always flag draws, for judging claims */
7414                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7415
7416                      if(canAdjudicate && appData.materialDraws) {
7417                          /* but only adjudicate them if adjudication enabled */
7418                          if(engineOpponent) {
7419                            SendToProgram("force\n", engineOpponent); // suppress reply
7420                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7421                          }
7422                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7423                          return 1;
7424                      }
7425                 }
7426
7427                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7428                 if(gameInfo.variant == VariantXiangqi ?
7429                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7430                  : nrW + nrB == 4 &&
7431                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7432                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7433                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7434                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7435                    ) ) {
7436                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7437                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7438                           if(engineOpponent) {
7439                             SendToProgram("force\n", engineOpponent); // suppress reply
7440                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7441                           }
7442                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7443                           return 1;
7444                      }
7445                 } else moveCount = 6;
7446             }
7447         if (appData.debugMode) { int i;
7448             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7449                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7450                     appData.drawRepeats);
7451             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7452               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7453
7454         }
7455
7456         // Repetition draws and 50-move rule can be applied independently of legality testing
7457
7458                 /* Check for rep-draws */
7459                 count = 0;
7460                 for(k = forwardMostMove-2;
7461                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7462                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7463                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7464                     k-=2)
7465                 {   int rights=0;
7466                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7467                         /* compare castling rights */
7468                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7469                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7470                                 rights++; /* King lost rights, while rook still had them */
7471                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7472                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7473                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7474                                    rights++; /* but at least one rook lost them */
7475                         }
7476                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7477                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7478                                 rights++;
7479                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7480                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7481                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7482                                    rights++;
7483                         }
7484                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7485                             && appData.drawRepeats > 1) {
7486                              /* adjudicate after user-specified nr of repeats */
7487                              int result = GameIsDrawn;
7488                              char *details = "XBoard adjudication: repetition draw";
7489                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7490                                 // [HGM] xiangqi: check for forbidden perpetuals
7491                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7492                                 for(m=forwardMostMove; m>k; m-=2) {
7493                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7494                                         ourPerpetual = 0; // the current mover did not always check
7495                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7496                                         hisPerpetual = 0; // the opponent did not always check
7497                                 }
7498                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7499                                                                         ourPerpetual, hisPerpetual);
7500                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7501                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7502                                     details = "Xboard adjudication: perpetual checking";
7503                                 } else
7504                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7505                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7506                                 } else
7507                                 // Now check for perpetual chases
7508                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7509                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7510                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7511                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7512                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7513                                         details = "Xboard adjudication: perpetual chasing";
7514                                     } else
7515                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7516                                         break; // Abort repetition-checking loop.
7517                                 }
7518                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7519                              }
7520                              if(engineOpponent) {
7521                                SendToProgram("force\n", engineOpponent); // suppress reply
7522                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7523                              }
7524                              GameEnds( result, details, GE_XBOARD );
7525                              return 1;
7526                         }
7527                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7528                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7529                     }
7530                 }
7531
7532                 /* Now we test for 50-move draws. Determine ply count */
7533                 count = forwardMostMove;
7534                 /* look for last irreversble move */
7535                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7536                     count--;
7537                 /* if we hit starting position, add initial plies */
7538                 if( count == backwardMostMove )
7539                     count -= initialRulePlies;
7540                 count = forwardMostMove - count;
7541                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7542                         // adjust reversible move counter for checks in Xiangqi
7543                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7544                         if(i < backwardMostMove) i = backwardMostMove;
7545                         while(i <= forwardMostMove) {
7546                                 lastCheck = inCheck; // check evasion does not count
7547                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7548                                 if(inCheck || lastCheck) count--; // check does not count
7549                                 i++;
7550                         }
7551                 }
7552                 if( count >= 100)
7553                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7554                          /* this is used to judge if draw claims are legal */
7555                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7556                          if(engineOpponent) {
7557                            SendToProgram("force\n", engineOpponent); // suppress reply
7558                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7559                          }
7560                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7561                          return 1;
7562                 }
7563
7564                 /* if draw offer is pending, treat it as a draw claim
7565                  * when draw condition present, to allow engines a way to
7566                  * claim draws before making their move to avoid a race
7567                  * condition occurring after their move
7568                  */
7569                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7570                          char *p = NULL;
7571                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7572                              p = "Draw claim: 50-move rule";
7573                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7574                              p = "Draw claim: 3-fold repetition";
7575                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7576                              p = "Draw claim: insufficient mating material";
7577                          if( p != NULL && canAdjudicate) {
7578                              if(engineOpponent) {
7579                                SendToProgram("force\n", engineOpponent); // suppress reply
7580                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7581                              }
7582                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7583                              return 1;
7584                          }
7585                 }
7586
7587                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7588                     if(engineOpponent) {
7589                       SendToProgram("force\n", engineOpponent); // suppress reply
7590                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7591                     }
7592                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7593                     return 1;
7594                 }
7595         return 0;
7596 }
7597
7598 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7599 {   // [HGM] book: this routine intercepts moves to simulate book replies
7600     char *bookHit = NULL;
7601
7602     //first determine if the incoming move brings opponent into his book
7603     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7604         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7605     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7606     if(bookHit != NULL && !cps->bookSuspend) {
7607         // make sure opponent is not going to reply after receiving move to book position
7608         SendToProgram("force\n", cps);
7609         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7610     }
7611     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7612     // now arrange restart after book miss
7613     if(bookHit) {
7614         // after a book hit we never send 'go', and the code after the call to this routine
7615         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7616         char buf[MSG_SIZ], *move = bookHit;
7617         if(cps->useSAN) {
7618             int fromX, fromY, toX, toY;
7619             char promoChar;
7620             ChessMove moveType;
7621             move = buf + 30;
7622             if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7623                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7624                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7625                                     PosFlags(forwardMostMove),
7626                                     fromY, fromX, toY, toX, promoChar, move);
7627             } else {
7628                 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7629                 bookHit = NULL;
7630             }
7631         }
7632         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7633         SendToProgram(buf, cps);
7634         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7635     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7636         SendToProgram("go\n", cps);
7637         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7638     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7639         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7640             SendToProgram("go\n", cps);
7641         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7642     }
7643     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7644 }
7645
7646 char *savedMessage;
7647 ChessProgramState *savedState;
7648 void DeferredBookMove(void)
7649 {
7650         if(savedState->lastPing != savedState->lastPong)
7651                     ScheduleDelayedEvent(DeferredBookMove, 10);
7652         else
7653         HandleMachineMove(savedMessage, savedState);
7654 }
7655
7656 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7657
7658 void
7659 HandleMachineMove(message, cps)
7660      char *message;
7661      ChessProgramState *cps;
7662 {
7663     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7664     char realname[MSG_SIZ];
7665     int fromX, fromY, toX, toY;
7666     ChessMove moveType;
7667     char promoChar;
7668     char *p;
7669     int machineWhite;
7670     char *bookHit;
7671
7672     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7673         // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7674         if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7675             DisplayError(_("Invalid pairing from pairing engine"), 0);
7676             return;
7677         }
7678         pairingReceived = 1;
7679         NextMatchGame();
7680         return; // Skim the pairing messages here.
7681     }
7682
7683     cps->userError = 0;
7684
7685 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7686     /*
7687      * Kludge to ignore BEL characters
7688      */
7689     while (*message == '\007') message++;
7690
7691     /*
7692      * [HGM] engine debug message: ignore lines starting with '#' character
7693      */
7694     if(cps->debug && *message == '#') return;
7695
7696     /*
7697      * Look for book output
7698      */
7699     if (cps == &first && bookRequested) {
7700         if (message[0] == '\t' || message[0] == ' ') {
7701             /* Part of the book output is here; append it */
7702             strcat(bookOutput, message);
7703             strcat(bookOutput, "  \n");
7704             return;
7705         } else if (bookOutput[0] != NULLCHAR) {
7706             /* All of book output has arrived; display it */
7707             char *p = bookOutput;
7708             while (*p != NULLCHAR) {
7709                 if (*p == '\t') *p = ' ';
7710                 p++;
7711             }
7712             DisplayInformation(bookOutput);
7713             bookRequested = FALSE;
7714             /* Fall through to parse the current output */
7715         }
7716     }
7717
7718     /*
7719      * Look for machine move.
7720      */
7721     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7722         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7723     {
7724         /* This method is only useful on engines that support ping */
7725         if (cps->lastPing != cps->lastPong) {
7726           if (gameMode == BeginningOfGame) {
7727             /* Extra move from before last new; ignore */
7728             if (appData.debugMode) {
7729                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7730             }
7731           } else {
7732             if (appData.debugMode) {
7733                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7734                         cps->which, gameMode);
7735             }
7736
7737             SendToProgram("undo\n", cps);
7738           }
7739           return;
7740         }
7741
7742         switch (gameMode) {
7743           case BeginningOfGame:
7744             /* Extra move from before last reset; ignore */
7745             if (appData.debugMode) {
7746                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7747             }
7748             return;
7749
7750           case EndOfGame:
7751           case IcsIdle:
7752           default:
7753             /* Extra move after we tried to stop.  The mode test is
7754                not a reliable way of detecting this problem, but it's
7755                the best we can do on engines that don't support ping.
7756             */
7757             if (appData.debugMode) {
7758                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7759                         cps->which, gameMode);
7760             }
7761             SendToProgram("undo\n", cps);
7762             return;
7763
7764           case MachinePlaysWhite:
7765           case IcsPlayingWhite:
7766             machineWhite = TRUE;
7767             break;
7768
7769           case MachinePlaysBlack:
7770           case IcsPlayingBlack:
7771             machineWhite = FALSE;
7772             break;
7773
7774           case TwoMachinesPlay:
7775             machineWhite = (cps->twoMachinesColor[0] == 'w');
7776             break;
7777         }
7778         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7779             if (appData.debugMode) {
7780                 fprintf(debugFP,
7781                         "Ignoring move out of turn by %s, gameMode %d"
7782                         ", forwardMost %d\n",
7783                         cps->which, gameMode, forwardMostMove);
7784             }
7785             return;
7786         }
7787
7788     if (appData.debugMode) { int f = forwardMostMove;
7789         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7790                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7791                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7792     }
7793         if(cps->alphaRank) AlphaRank(machineMove, 4);
7794         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7795                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7796             /* Machine move could not be parsed; ignore it. */
7797           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7798                     machineMove, _(cps->which));
7799             DisplayError(buf1, 0);
7800             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7801                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7802             if (gameMode == TwoMachinesPlay) {
7803               GameEnds(machineWhite ? BlackWins : WhiteWins,
7804                        buf1, GE_XBOARD);
7805             }
7806             return;
7807         }
7808
7809         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7810         /* So we have to redo legality test with true e.p. status here,  */
7811         /* to make sure an illegal e.p. capture does not slip through,   */
7812         /* to cause a forfeit on a justified illegal-move complaint      */
7813         /* of the opponent.                                              */
7814         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7815            ChessMove moveType;
7816            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7817                              fromY, fromX, toY, toX, promoChar);
7818             if (appData.debugMode) {
7819                 int i;
7820                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7821                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7822                 fprintf(debugFP, "castling rights\n");
7823             }
7824             if(moveType == IllegalMove) {
7825               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7826                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7827                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7828                            buf1, GE_XBOARD);
7829                 return;
7830            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7831            /* [HGM] Kludge to handle engines that send FRC-style castling
7832               when they shouldn't (like TSCP-Gothic) */
7833            switch(moveType) {
7834              case WhiteASideCastleFR:
7835              case BlackASideCastleFR:
7836                toX+=2;
7837                currentMoveString[2]++;
7838                break;
7839              case WhiteHSideCastleFR:
7840              case BlackHSideCastleFR:
7841                toX--;
7842                currentMoveString[2]--;
7843                break;
7844              default: ; // nothing to do, but suppresses warning of pedantic compilers
7845            }
7846         }
7847         hintRequested = FALSE;
7848         lastHint[0] = NULLCHAR;
7849         bookRequested = FALSE;
7850         /* Program may be pondering now */
7851         cps->maybeThinking = TRUE;
7852         if (cps->sendTime == 2) cps->sendTime = 1;
7853         if (cps->offeredDraw) cps->offeredDraw--;
7854
7855         /* [AS] Save move info*/
7856         pvInfoList[ forwardMostMove ].score = programStats.score;
7857         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7858         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7859
7860         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7861
7862         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7863         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7864             int count = 0;
7865
7866             while( count < adjudicateLossPlies ) {
7867                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7868
7869                 if( count & 1 ) {
7870                     score = -score; /* Flip score for winning side */
7871                 }
7872
7873                 if( score > adjudicateLossThreshold ) {
7874                     break;
7875                 }
7876
7877                 count++;
7878             }
7879
7880             if( count >= adjudicateLossPlies ) {
7881                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7882
7883                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7884                     "Xboard adjudication",
7885                     GE_XBOARD );
7886
7887                 return;
7888             }
7889         }
7890
7891         if(Adjudicate(cps)) {
7892             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7893             return; // [HGM] adjudicate: for all automatic game ends
7894         }
7895
7896 #if ZIPPY
7897         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7898             first.initDone) {
7899           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7900                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7901                 SendToICS("draw ");
7902                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7903           }
7904           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7905           ics_user_moved = 1;
7906           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7907                 char buf[3*MSG_SIZ];
7908
7909                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7910                         programStats.score / 100.,
7911                         programStats.depth,
7912                         programStats.time / 100.,
7913                         (unsigned int)programStats.nodes,
7914                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7915                         programStats.movelist);
7916                 SendToICS(buf);
7917 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7918           }
7919         }
7920 #endif
7921
7922         /* [AS] Clear stats for next move */
7923         ClearProgramStats();
7924         thinkOutput[0] = NULLCHAR;
7925         hiddenThinkOutputState = 0;
7926
7927         bookHit = NULL;
7928         if (gameMode == TwoMachinesPlay) {
7929             /* [HGM] relaying draw offers moved to after reception of move */
7930             /* and interpreting offer as claim if it brings draw condition */
7931             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7932                 SendToProgram("draw\n", cps->other);
7933             }
7934             if (cps->other->sendTime) {
7935                 SendTimeRemaining(cps->other,
7936                                   cps->other->twoMachinesColor[0] == 'w');
7937             }
7938             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7939             if (firstMove && !bookHit) {
7940                 firstMove = FALSE;
7941                 if (cps->other->useColors) {
7942                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7943                 }
7944                 SendToProgram("go\n", cps->other);
7945             }
7946             cps->other->maybeThinking = TRUE;
7947         }
7948
7949         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7950
7951         if (!pausing && appData.ringBellAfterMoves) {
7952             RingBell();
7953         }
7954
7955         /*
7956          * Reenable menu items that were disabled while
7957          * machine was thinking
7958          */
7959         if (gameMode != TwoMachinesPlay)
7960             SetUserThinkingEnables();
7961
7962         // [HGM] book: after book hit opponent has received move and is now in force mode
7963         // force the book reply into it, and then fake that it outputted this move by jumping
7964         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7965         if(bookHit) {
7966                 static char bookMove[MSG_SIZ]; // a bit generous?
7967
7968                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7969                 strcat(bookMove, bookHit);
7970                 message = bookMove;
7971                 cps = cps->other;
7972                 programStats.nodes = programStats.depth = programStats.time =
7973                 programStats.score = programStats.got_only_move = 0;
7974                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7975
7976                 if(cps->lastPing != cps->lastPong) {
7977                     savedMessage = message; // args for deferred call
7978                     savedState = cps;
7979                     ScheduleDelayedEvent(DeferredBookMove, 10);
7980                     return;
7981                 }
7982                 goto FakeBookMove;
7983         }
7984
7985         return;
7986     }
7987
7988     /* Set special modes for chess engines.  Later something general
7989      *  could be added here; for now there is just one kludge feature,
7990      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7991      *  when "xboard" is given as an interactive command.
7992      */
7993     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7994         cps->useSigint = FALSE;
7995         cps->useSigterm = FALSE;
7996     }
7997     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7998       ParseFeatures(message+8, cps);
7999       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8000     }
8001
8002     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8003       int dummy, s=6; char buf[MSG_SIZ];
8004       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8005       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8006       ParseFEN(boards[0], &dummy, message+s);
8007       DrawPosition(TRUE, boards[0]);
8008       startedFromSetupPosition = TRUE;
8009       return;
8010     }
8011     /* [HGM] Allow engine to set up a position. Don't ask me why one would
8012      * want this, I was asked to put it in, and obliged.
8013      */
8014     if (!strncmp(message, "setboard ", 9)) {
8015         Board initial_position;
8016
8017         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8018
8019         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8020             DisplayError(_("Bad FEN received from engine"), 0);
8021             return ;
8022         } else {
8023            Reset(TRUE, FALSE);
8024            CopyBoard(boards[0], initial_position);
8025            initialRulePlies = FENrulePlies;
8026            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8027            else gameMode = MachinePlaysBlack;
8028            DrawPosition(FALSE, boards[currentMove]);
8029         }
8030         return;
8031     }
8032
8033     /*
8034      * Look for communication commands
8035      */
8036     if (!strncmp(message, "telluser ", 9)) {
8037         if(message[9] == '\\' && message[10] == '\\')
8038             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8039         DisplayNote(message + 9);
8040         return;
8041     }
8042     if (!strncmp(message, "tellusererror ", 14)) {
8043         cps->userError = 1;
8044         if(message[14] == '\\' && message[15] == '\\')
8045             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8046         DisplayError(message + 14, 0);
8047         return;
8048     }
8049     if (!strncmp(message, "tellopponent ", 13)) {
8050       if (appData.icsActive) {
8051         if (loggedOn) {
8052           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8053           SendToICS(buf1);
8054         }
8055       } else {
8056         DisplayNote(message + 13);
8057       }
8058       return;
8059     }
8060     if (!strncmp(message, "tellothers ", 11)) {
8061       if (appData.icsActive) {
8062         if (loggedOn) {
8063           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8064           SendToICS(buf1);
8065         }
8066       }
8067       return;
8068     }
8069     if (!strncmp(message, "tellall ", 8)) {
8070       if (appData.icsActive) {
8071         if (loggedOn) {
8072           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8073           SendToICS(buf1);
8074         }
8075       } else {
8076         DisplayNote(message + 8);
8077       }
8078       return;
8079     }
8080     if (strncmp(message, "warning", 7) == 0) {
8081         /* Undocumented feature, use tellusererror in new code */
8082         DisplayError(message, 0);
8083         return;
8084     }
8085     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8086         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8087         strcat(realname, " query");
8088         AskQuestion(realname, buf2, buf1, cps->pr);
8089         return;
8090     }
8091     /* Commands from the engine directly to ICS.  We don't allow these to be
8092      *  sent until we are logged on. Crafty kibitzes have been known to
8093      *  interfere with the login process.
8094      */
8095     if (loggedOn) {
8096         if (!strncmp(message, "tellics ", 8)) {
8097             SendToICS(message + 8);
8098             SendToICS("\n");
8099             return;
8100         }
8101         if (!strncmp(message, "tellicsnoalias ", 15)) {
8102             SendToICS(ics_prefix);
8103             SendToICS(message + 15);
8104             SendToICS("\n");
8105             return;
8106         }
8107         /* The following are for backward compatibility only */
8108         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8109             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8110             SendToICS(ics_prefix);
8111             SendToICS(message);
8112             SendToICS("\n");
8113             return;
8114         }
8115     }
8116     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8117         return;
8118     }
8119     /*
8120      * If the move is illegal, cancel it and redraw the board.
8121      * Also deal with other error cases.  Matching is rather loose
8122      * here to accommodate engines written before the spec.
8123      */
8124     if (strncmp(message + 1, "llegal move", 11) == 0 ||
8125         strncmp(message, "Error", 5) == 0) {
8126         if (StrStr(message, "name") ||
8127             StrStr(message, "rating") || StrStr(message, "?") ||
8128             StrStr(message, "result") || StrStr(message, "board") ||
8129             StrStr(message, "bk") || StrStr(message, "computer") ||
8130             StrStr(message, "variant") || StrStr(message, "hint") ||
8131             StrStr(message, "random") || StrStr(message, "depth") ||
8132             StrStr(message, "accepted")) {
8133             return;
8134         }
8135         if (StrStr(message, "protover")) {
8136           /* Program is responding to input, so it's apparently done
8137              initializing, and this error message indicates it is
8138              protocol version 1.  So we don't need to wait any longer
8139              for it to initialize and send feature commands. */
8140           FeatureDone(cps, 1);
8141           cps->protocolVersion = 1;
8142           return;
8143         }
8144         cps->maybeThinking = FALSE;
8145
8146         if (StrStr(message, "draw")) {
8147             /* Program doesn't have "draw" command */
8148             cps->sendDrawOffers = 0;
8149             return;
8150         }
8151         if (cps->sendTime != 1 &&
8152             (StrStr(message, "time") || StrStr(message, "otim"))) {
8153           /* Program apparently doesn't have "time" or "otim" command */
8154           cps->sendTime = 0;
8155           return;
8156         }
8157         if (StrStr(message, "analyze")) {
8158             cps->analysisSupport = FALSE;
8159             cps->analyzing = FALSE;
8160             Reset(FALSE, TRUE);
8161             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8162             DisplayError(buf2, 0);
8163             return;
8164         }
8165         if (StrStr(message, "(no matching move)st")) {
8166           /* Special kludge for GNU Chess 4 only */
8167           cps->stKludge = TRUE;
8168           SendTimeControl(cps, movesPerSession, timeControl,
8169                           timeIncrement, appData.searchDepth,
8170                           searchTime);
8171           return;
8172         }
8173         if (StrStr(message, "(no matching move)sd")) {
8174           /* Special kludge for GNU Chess 4 only */
8175           cps->sdKludge = TRUE;
8176           SendTimeControl(cps, movesPerSession, timeControl,
8177                           timeIncrement, appData.searchDepth,
8178                           searchTime);
8179           return;
8180         }
8181         if (!StrStr(message, "llegal")) {
8182             return;
8183         }
8184         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8185             gameMode == IcsIdle) return;
8186         if (forwardMostMove <= backwardMostMove) return;
8187         if (pausing) PauseEvent();
8188       if(appData.forceIllegal) {
8189             // [HGM] illegal: machine refused move; force position after move into it
8190           SendToProgram("force\n", cps);
8191           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8192                 // we have a real problem now, as SendBoard will use the a2a3 kludge
8193                 // when black is to move, while there might be nothing on a2 or black
8194                 // might already have the move. So send the board as if white has the move.
8195                 // But first we must change the stm of the engine, as it refused the last move
8196                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8197                 if(WhiteOnMove(forwardMostMove)) {
8198                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
8199                     SendBoard(cps, forwardMostMove); // kludgeless board
8200                 } else {
8201                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
8202                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8203                     SendBoard(cps, forwardMostMove+1); // kludgeless board
8204                 }
8205           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8206             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8207                  gameMode == TwoMachinesPlay)
8208               SendToProgram("go\n", cps);
8209             return;
8210       } else
8211         if (gameMode == PlayFromGameFile) {
8212             /* Stop reading this game file */
8213             gameMode = EditGame;
8214             ModeHighlight();
8215         }
8216         /* [HGM] illegal-move claim should forfeit game when Xboard */
8217         /* only passes fully legal moves                            */
8218         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8219             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8220                                 "False illegal-move claim", GE_XBOARD );
8221             return; // do not take back move we tested as valid
8222         }
8223         currentMove = forwardMostMove-1;
8224         DisplayMove(currentMove-1); /* before DisplayMoveError */
8225         SwitchClocks(forwardMostMove-1); // [HGM] race
8226         DisplayBothClocks();
8227         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8228                 parseList[currentMove], _(cps->which));
8229         DisplayMoveError(buf1);
8230         DrawPosition(FALSE, boards[currentMove]);
8231         return;
8232     }
8233     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8234         /* Program has a broken "time" command that
8235            outputs a string not ending in newline.
8236            Don't use it. */
8237         cps->sendTime = 0;
8238     }
8239
8240     /*
8241      * If chess program startup fails, exit with an error message.
8242      * Attempts to recover here are futile.
8243      */
8244     if ((StrStr(message, "unknown host") != NULL)
8245         || (StrStr(message, "No remote directory") != NULL)
8246         || (StrStr(message, "not found") != NULL)
8247         || (StrStr(message, "No such file") != NULL)
8248         || (StrStr(message, "can't alloc") != NULL)
8249         || (StrStr(message, "Permission denied") != NULL)) {
8250
8251         cps->maybeThinking = FALSE;
8252         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8253                 _(cps->which), cps->program, cps->host, message);
8254         RemoveInputSource(cps->isr);
8255         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8256             if(cps == &first) appData.noChessProgram = TRUE;
8257             DisplayError(buf1, 0);
8258         }
8259         return;
8260     }
8261
8262     /*
8263      * Look for hint output
8264      */
8265     if (sscanf(message, "Hint: %s", buf1) == 1) {
8266         if (cps == &first && hintRequested) {
8267             hintRequested = FALSE;
8268             if (ParseOneMove(buf1, forwardMostMove, &moveType,
8269                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
8270                 (void) CoordsToAlgebraic(boards[forwardMostMove],
8271                                     PosFlags(forwardMostMove),
8272                                     fromY, fromX, toY, toX, promoChar, buf1);
8273                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8274                 DisplayInformation(buf2);
8275             } else {
8276                 /* Hint move could not be parsed!? */
8277               snprintf(buf2, sizeof(buf2),
8278                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
8279                         buf1, _(cps->which));
8280                 DisplayError(buf2, 0);
8281             }
8282         } else {
8283           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8284         }
8285         return;
8286     }
8287
8288     /*
8289      * Ignore other messages if game is not in progress
8290      */
8291     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8292         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8293
8294     /*
8295      * look for win, lose, draw, or draw offer
8296      */
8297     if (strncmp(message, "1-0", 3) == 0) {
8298         char *p, *q, *r = "";
8299         p = strchr(message, '{');
8300         if (p) {
8301             q = strchr(p, '}');
8302             if (q) {
8303                 *q = NULLCHAR;
8304                 r = p + 1;
8305             }
8306         }
8307         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8308         return;
8309     } else if (strncmp(message, "0-1", 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         /* Kludge for Arasan 4.1 bug */
8320         if (strcmp(r, "Black resigns") == 0) {
8321             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8322             return;
8323         }
8324         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8325         return;
8326     } else if (strncmp(message, "1/2", 3) == 0) {
8327         char *p, *q, *r = "";
8328         p = strchr(message, '{');
8329         if (p) {
8330             q = strchr(p, '}');
8331             if (q) {
8332                 *q = NULLCHAR;
8333                 r = p + 1;
8334             }
8335         }
8336
8337         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8338         return;
8339
8340     } else if (strncmp(message, "White resign", 12) == 0) {
8341         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8342         return;
8343     } else if (strncmp(message, "Black resign", 12) == 0) {
8344         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8345         return;
8346     } else if (strncmp(message, "White matches", 13) == 0 ||
8347                strncmp(message, "Black matches", 13) == 0   ) {
8348         /* [HGM] ignore GNUShogi noises */
8349         return;
8350     } else if (strncmp(message, "White", 5) == 0 &&
8351                message[5] != '(' &&
8352                StrStr(message, "Black") == NULL) {
8353         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8354         return;
8355     } else if (strncmp(message, "Black", 5) == 0 &&
8356                message[5] != '(') {
8357         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8358         return;
8359     } else if (strcmp(message, "resign") == 0 ||
8360                strcmp(message, "computer resigns") == 0) {
8361         switch (gameMode) {
8362           case MachinePlaysBlack:
8363           case IcsPlayingBlack:
8364             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8365             break;
8366           case MachinePlaysWhite:
8367           case IcsPlayingWhite:
8368             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8369             break;
8370           case TwoMachinesPlay:
8371             if (cps->twoMachinesColor[0] == 'w')
8372               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8373             else
8374               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8375             break;
8376           default:
8377             /* can't happen */
8378             break;
8379         }
8380         return;
8381     } else if (strncmp(message, "opponent mates", 14) == 0) {
8382         switch (gameMode) {
8383           case MachinePlaysBlack:
8384           case IcsPlayingBlack:
8385             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8386             break;
8387           case MachinePlaysWhite:
8388           case IcsPlayingWhite:
8389             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8390             break;
8391           case TwoMachinesPlay:
8392             if (cps->twoMachinesColor[0] == 'w')
8393               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8394             else
8395               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8396             break;
8397           default:
8398             /* can't happen */
8399             break;
8400         }
8401         return;
8402     } else if (strncmp(message, "computer mates", 14) == 0) {
8403         switch (gameMode) {
8404           case MachinePlaysBlack:
8405           case IcsPlayingBlack:
8406             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8407             break;
8408           case MachinePlaysWhite:
8409           case IcsPlayingWhite:
8410             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8411             break;
8412           case TwoMachinesPlay:
8413             if (cps->twoMachinesColor[0] == 'w')
8414               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8415             else
8416               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8417             break;
8418           default:
8419             /* can't happen */
8420             break;
8421         }
8422         return;
8423     } else if (strncmp(message, "checkmate", 9) == 0) {
8424         if (WhiteOnMove(forwardMostMove)) {
8425             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8426         } else {
8427             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8428         }
8429         return;
8430     } else if (strstr(message, "Draw") != NULL ||
8431                strstr(message, "game is a draw") != NULL) {
8432         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8433         return;
8434     } else if (strstr(message, "offer") != NULL &&
8435                strstr(message, "draw") != NULL) {
8436 #if ZIPPY
8437         if (appData.zippyPlay && first.initDone) {
8438             /* Relay offer to ICS */
8439             SendToICS(ics_prefix);
8440             SendToICS("draw\n");
8441         }
8442 #endif
8443         cps->offeredDraw = 2; /* valid until this engine moves twice */
8444         if (gameMode == TwoMachinesPlay) {
8445             if (cps->other->offeredDraw) {
8446                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8447             /* [HGM] in two-machine mode we delay relaying draw offer      */
8448             /* until after we also have move, to see if it is really claim */
8449             }
8450         } else if (gameMode == MachinePlaysWhite ||
8451                    gameMode == MachinePlaysBlack) {
8452           if (userOfferedDraw) {
8453             DisplayInformation(_("Machine accepts your draw offer"));
8454             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8455           } else {
8456             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8457           }
8458         }
8459     }
8460
8461
8462     /*
8463      * Look for thinking output
8464      */
8465     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8466           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8467                                 ) {
8468         int plylev, mvleft, mvtot, curscore, time;
8469         char mvname[MOVE_LEN];
8470         u64 nodes; // [DM]
8471         char plyext;
8472         int ignore = FALSE;
8473         int prefixHint = FALSE;
8474         mvname[0] = NULLCHAR;
8475
8476         switch (gameMode) {
8477           case MachinePlaysBlack:
8478           case IcsPlayingBlack:
8479             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8480             break;
8481           case MachinePlaysWhite:
8482           case IcsPlayingWhite:
8483             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8484             break;
8485           case AnalyzeMode:
8486           case AnalyzeFile:
8487             break;
8488           case IcsObserving: /* [DM] icsEngineAnalyze */
8489             if (!appData.icsEngineAnalyze) ignore = TRUE;
8490             break;
8491           case TwoMachinesPlay:
8492             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8493                 ignore = TRUE;
8494             }
8495             break;
8496           default:
8497             ignore = TRUE;
8498             break;
8499         }
8500
8501         if (!ignore) {
8502             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8503             buf1[0] = NULLCHAR;
8504             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8505                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8506
8507                 if (plyext != ' ' && plyext != '\t') {
8508                     time *= 100;
8509                 }
8510
8511                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8512                 if( cps->scoreIsAbsolute &&
8513                     ( gameMode == MachinePlaysBlack ||
8514                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8515                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8516                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8517                      !WhiteOnMove(currentMove)
8518                     ) )
8519                 {
8520                     curscore = -curscore;
8521                 }
8522
8523
8524                 tempStats.depth = plylev;
8525                 tempStats.nodes = nodes;
8526                 tempStats.time = time;
8527                 tempStats.score = curscore;
8528                 tempStats.got_only_move = 0;
8529
8530                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8531                         int ticklen;
8532
8533                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8534                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8535                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8536                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8537                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8538                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8539                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8540                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8541                 }
8542
8543                 /* Buffer overflow protection */
8544                 if (buf1[0] != NULLCHAR) {
8545                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8546                         && appData.debugMode) {
8547                         fprintf(debugFP,
8548                                 "PV is too long; using the first %u bytes.\n",
8549                                 (unsigned) sizeof(tempStats.movelist) - 1);
8550                     }
8551
8552                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8553                 } else {
8554                     sprintf(tempStats.movelist, " no PV\n");
8555                 }
8556
8557                 if (tempStats.seen_stat) {
8558                     tempStats.ok_to_send = 1;
8559                 }
8560
8561                 if (strchr(tempStats.movelist, '(') != NULL) {
8562                     tempStats.line_is_book = 1;
8563                     tempStats.nr_moves = 0;
8564                     tempStats.moves_left = 0;
8565                 } else {
8566                     tempStats.line_is_book = 0;
8567                 }
8568
8569                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8570                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8571
8572                 SendProgramStatsToFrontend( cps, &tempStats );
8573
8574                 /*
8575                     [AS] Protect the thinkOutput buffer from overflow... this
8576                     is only useful if buf1 hasn't overflowed first!
8577                 */
8578                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8579                          plylev,
8580                          (gameMode == TwoMachinesPlay ?
8581                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8582                          ((double) curscore) / 100.0,
8583                          prefixHint ? lastHint : "",
8584                          prefixHint ? " " : "" );
8585
8586                 if( buf1[0] != NULLCHAR ) {
8587                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8588
8589                     if( strlen(buf1) > max_len ) {
8590                         if( appData.debugMode) {
8591                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8592                         }
8593                         buf1[max_len+1] = '\0';
8594                     }
8595
8596                     strcat( thinkOutput, buf1 );
8597                 }
8598
8599                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8600                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8601                     DisplayMove(currentMove - 1);
8602                 }
8603                 return;
8604
8605             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8606                 /* crafty (9.25+) says "(only move) <move>"
8607                  * if there is only 1 legal move
8608                  */
8609                 sscanf(p, "(only move) %s", buf1);
8610                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8611                 sprintf(programStats.movelist, "%s (only move)", buf1);
8612                 programStats.depth = 1;
8613                 programStats.nr_moves = 1;
8614                 programStats.moves_left = 1;
8615                 programStats.nodes = 1;
8616                 programStats.time = 1;
8617                 programStats.got_only_move = 1;
8618
8619                 /* Not really, but we also use this member to
8620                    mean "line isn't going to change" (Crafty
8621                    isn't searching, so stats won't change) */
8622                 programStats.line_is_book = 1;
8623
8624                 SendProgramStatsToFrontend( cps, &programStats );
8625
8626                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8627                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8628                     DisplayMove(currentMove - 1);
8629                 }
8630                 return;
8631             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8632                               &time, &nodes, &plylev, &mvleft,
8633                               &mvtot, mvname) >= 5) {
8634                 /* The stat01: line is from Crafty (9.29+) in response
8635                    to the "." command */
8636                 programStats.seen_stat = 1;
8637                 cps->maybeThinking = TRUE;
8638
8639                 if (programStats.got_only_move || !appData.periodicUpdates)
8640                   return;
8641
8642                 programStats.depth = plylev;
8643                 programStats.time = time;
8644                 programStats.nodes = nodes;
8645                 programStats.moves_left = mvleft;
8646                 programStats.nr_moves = mvtot;
8647                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8648                 programStats.ok_to_send = 1;
8649                 programStats.movelist[0] = '\0';
8650
8651                 SendProgramStatsToFrontend( cps, &programStats );
8652
8653                 return;
8654
8655             } else if (strncmp(message,"++",2) == 0) {
8656                 /* Crafty 9.29+ outputs this */
8657                 programStats.got_fail = 2;
8658                 return;
8659
8660             } else if (strncmp(message,"--",2) == 0) {
8661                 /* Crafty 9.29+ outputs this */
8662                 programStats.got_fail = 1;
8663                 return;
8664
8665             } else if (thinkOutput[0] != NULLCHAR &&
8666                        strncmp(message, "    ", 4) == 0) {
8667                 unsigned message_len;
8668
8669                 p = message;
8670                 while (*p && *p == ' ') p++;
8671
8672                 message_len = strlen( p );
8673
8674                 /* [AS] Avoid buffer overflow */
8675                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8676                     strcat(thinkOutput, " ");
8677                     strcat(thinkOutput, p);
8678                 }
8679
8680                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8681                     strcat(programStats.movelist, " ");
8682                     strcat(programStats.movelist, p);
8683                 }
8684
8685                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8686                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8687                     DisplayMove(currentMove - 1);
8688                 }
8689                 return;
8690             }
8691         }
8692         else {
8693             buf1[0] = NULLCHAR;
8694
8695             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8696                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8697             {
8698                 ChessProgramStats cpstats;
8699
8700                 if (plyext != ' ' && plyext != '\t') {
8701                     time *= 100;
8702                 }
8703
8704                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8705                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8706                     curscore = -curscore;
8707                 }
8708
8709                 cpstats.depth = plylev;
8710                 cpstats.nodes = nodes;
8711                 cpstats.time = time;
8712                 cpstats.score = curscore;
8713                 cpstats.got_only_move = 0;
8714                 cpstats.movelist[0] = '\0';
8715
8716                 if (buf1[0] != NULLCHAR) {
8717                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8718                 }
8719
8720                 cpstats.ok_to_send = 0;
8721                 cpstats.line_is_book = 0;
8722                 cpstats.nr_moves = 0;
8723                 cpstats.moves_left = 0;
8724
8725                 SendProgramStatsToFrontend( cps, &cpstats );
8726             }
8727         }
8728     }
8729 }
8730
8731
8732 /* Parse a game score from the character string "game", and
8733    record it as the history of the current game.  The game
8734    score is NOT assumed to start from the standard position.
8735    The display is not updated in any way.
8736    */
8737 void
8738 ParseGameHistory(game)
8739      char *game;
8740 {
8741     ChessMove moveType;
8742     int fromX, fromY, toX, toY, boardIndex;
8743     char promoChar;
8744     char *p, *q;
8745     char buf[MSG_SIZ];
8746
8747     if (appData.debugMode)
8748       fprintf(debugFP, "Parsing game history: %s\n", game);
8749
8750     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8751     gameInfo.site = StrSave(appData.icsHost);
8752     gameInfo.date = PGNDate();
8753     gameInfo.round = StrSave("-");
8754
8755     /* Parse out names of players */
8756     while (*game == ' ') game++;
8757     p = buf;
8758     while (*game != ' ') *p++ = *game++;
8759     *p = NULLCHAR;
8760     gameInfo.white = StrSave(buf);
8761     while (*game == ' ') game++;
8762     p = buf;
8763     while (*game != ' ' && *game != '\n') *p++ = *game++;
8764     *p = NULLCHAR;
8765     gameInfo.black = StrSave(buf);
8766
8767     /* Parse moves */
8768     boardIndex = blackPlaysFirst ? 1 : 0;
8769     yynewstr(game);
8770     for (;;) {
8771         yyboardindex = boardIndex;
8772         moveType = (ChessMove) Myylex();
8773         switch (moveType) {
8774           case IllegalMove:             /* maybe suicide chess, etc. */
8775   if (appData.debugMode) {
8776     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8777     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8778     setbuf(debugFP, NULL);
8779   }
8780           case WhitePromotion:
8781           case BlackPromotion:
8782           case WhiteNonPromotion:
8783           case BlackNonPromotion:
8784           case NormalMove:
8785           case WhiteCapturesEnPassant:
8786           case BlackCapturesEnPassant:
8787           case WhiteKingSideCastle:
8788           case WhiteQueenSideCastle:
8789           case BlackKingSideCastle:
8790           case BlackQueenSideCastle:
8791           case WhiteKingSideCastleWild:
8792           case WhiteQueenSideCastleWild:
8793           case BlackKingSideCastleWild:
8794           case BlackQueenSideCastleWild:
8795           /* PUSH Fabien */
8796           case WhiteHSideCastleFR:
8797           case WhiteASideCastleFR:
8798           case BlackHSideCastleFR:
8799           case BlackASideCastleFR:
8800           /* POP Fabien */
8801             fromX = currentMoveString[0] - AAA;
8802             fromY = currentMoveString[1] - ONE;
8803             toX = currentMoveString[2] - AAA;
8804             toY = currentMoveString[3] - ONE;
8805             promoChar = currentMoveString[4];
8806             break;
8807           case WhiteDrop:
8808           case BlackDrop:
8809             fromX = moveType == WhiteDrop ?
8810               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8811             (int) CharToPiece(ToLower(currentMoveString[0]));
8812             fromY = DROP_RANK;
8813             toX = currentMoveString[2] - AAA;
8814             toY = currentMoveString[3] - ONE;
8815             promoChar = NULLCHAR;
8816             break;
8817           case AmbiguousMove:
8818             /* bug? */
8819             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8820   if (appData.debugMode) {
8821     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8822     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8823     setbuf(debugFP, NULL);
8824   }
8825             DisplayError(buf, 0);
8826             return;
8827           case ImpossibleMove:
8828             /* bug? */
8829             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8830   if (appData.debugMode) {
8831     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8832     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8833     setbuf(debugFP, NULL);
8834   }
8835             DisplayError(buf, 0);
8836             return;
8837           case EndOfFile:
8838             if (boardIndex < backwardMostMove) {
8839                 /* Oops, gap.  How did that happen? */
8840                 DisplayError(_("Gap in move list"), 0);
8841                 return;
8842             }
8843             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8844             if (boardIndex > forwardMostMove) {
8845                 forwardMostMove = boardIndex;
8846             }
8847             return;
8848           case ElapsedTime:
8849             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8850                 strcat(parseList[boardIndex-1], " ");
8851                 strcat(parseList[boardIndex-1], yy_text);
8852             }
8853             continue;
8854           case Comment:
8855           case PGNTag:
8856           case NAG:
8857           default:
8858             /* ignore */
8859             continue;
8860           case WhiteWins:
8861           case BlackWins:
8862           case GameIsDrawn:
8863           case GameUnfinished:
8864             if (gameMode == IcsExamining) {
8865                 if (boardIndex < backwardMostMove) {
8866                     /* Oops, gap.  How did that happen? */
8867                     return;
8868                 }
8869                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8870                 return;
8871             }
8872             gameInfo.result = moveType;
8873             p = strchr(yy_text, '{');
8874             if (p == NULL) p = strchr(yy_text, '(');
8875             if (p == NULL) {
8876                 p = yy_text;
8877                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8878             } else {
8879                 q = strchr(p, *p == '{' ? '}' : ')');
8880                 if (q != NULL) *q = NULLCHAR;
8881                 p++;
8882             }
8883             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8884             gameInfo.resultDetails = StrSave(p);
8885             continue;
8886         }
8887         if (boardIndex >= forwardMostMove &&
8888             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8889             backwardMostMove = blackPlaysFirst ? 1 : 0;
8890             return;
8891         }
8892         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8893                                  fromY, fromX, toY, toX, promoChar,
8894                                  parseList[boardIndex]);
8895         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8896         /* currentMoveString is set as a side-effect of yylex */
8897         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8898         strcat(moveList[boardIndex], "\n");
8899         boardIndex++;
8900         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8901         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8902           case MT_NONE:
8903           case MT_STALEMATE:
8904           default:
8905             break;
8906           case MT_CHECK:
8907             if(gameInfo.variant != VariantShogi)
8908                 strcat(parseList[boardIndex - 1], "+");
8909             break;
8910           case MT_CHECKMATE:
8911           case MT_STAINMATE:
8912             strcat(parseList[boardIndex - 1], "#");
8913             break;
8914         }
8915     }
8916 }
8917
8918
8919 /* Apply a move to the given board  */
8920 void
8921 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8922      int fromX, fromY, toX, toY;
8923      int promoChar;
8924      Board board;
8925 {
8926   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8927   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8928
8929     /* [HGM] compute & store e.p. status and castling rights for new position */
8930     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8931
8932       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8933       oldEP = (signed char)board[EP_STATUS];
8934       board[EP_STATUS] = EP_NONE;
8935
8936       if( board[toY][toX] != EmptySquare )
8937            board[EP_STATUS] = EP_CAPTURE;
8938
8939   if (fromY == DROP_RANK) {
8940         /* must be first */
8941         piece = board[toY][toX] = (ChessSquare) fromX;
8942   } else {
8943       int i;
8944
8945       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8946            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8947                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8948       } else
8949       if( board[fromY][fromX] == WhitePawn ) {
8950            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8951                board[EP_STATUS] = EP_PAWN_MOVE;
8952            if( toY-fromY==2) {
8953                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8954                         gameInfo.variant != VariantBerolina || toX < fromX)
8955                       board[EP_STATUS] = toX | berolina;
8956                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8957                         gameInfo.variant != VariantBerolina || toX > fromX)
8958                       board[EP_STATUS] = toX;
8959            }
8960       } else
8961       if( board[fromY][fromX] == BlackPawn ) {
8962            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8963                board[EP_STATUS] = EP_PAWN_MOVE;
8964            if( toY-fromY== -2) {
8965                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8966                         gameInfo.variant != VariantBerolina || toX < fromX)
8967                       board[EP_STATUS] = toX | berolina;
8968                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8969                         gameInfo.variant != VariantBerolina || toX > fromX)
8970                       board[EP_STATUS] = toX;
8971            }
8972        }
8973
8974        for(i=0; i<nrCastlingRights; i++) {
8975            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8976               board[CASTLING][i] == toX   && castlingRank[i] == toY
8977              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8978        }
8979
8980      if (fromX == toX && fromY == toY) return;
8981
8982      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8983      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8984      if(gameInfo.variant == VariantKnightmate)
8985          king += (int) WhiteUnicorn - (int) WhiteKing;
8986
8987     /* Code added by Tord: */
8988     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8989     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8990         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8991       board[fromY][fromX] = EmptySquare;
8992       board[toY][toX] = EmptySquare;
8993       if((toX > fromX) != (piece == WhiteRook)) {
8994         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8995       } else {
8996         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8997       }
8998     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8999                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9000       board[fromY][fromX] = EmptySquare;
9001       board[toY][toX] = EmptySquare;
9002       if((toX > fromX) != (piece == BlackRook)) {
9003         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9004       } else {
9005         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9006       }
9007     /* End of code added by Tord */
9008
9009     } else if (board[fromY][fromX] == king
9010         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9011         && toY == fromY && toX > fromX+1) {
9012         board[fromY][fromX] = EmptySquare;
9013         board[toY][toX] = king;
9014         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9015         board[fromY][BOARD_RGHT-1] = EmptySquare;
9016     } else if (board[fromY][fromX] == king
9017         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9018                && toY == fromY && toX < fromX-1) {
9019         board[fromY][fromX] = EmptySquare;
9020         board[toY][toX] = king;
9021         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9022         board[fromY][BOARD_LEFT] = EmptySquare;
9023     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9024                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9025                && toY >= BOARD_HEIGHT-promoRank
9026                ) {
9027         /* white pawn promotion */
9028         board[toY][toX] = CharToPiece(ToUpper(promoChar));
9029         if (board[toY][toX] == EmptySquare) {
9030             board[toY][toX] = WhiteQueen;
9031         }
9032         if(gameInfo.variant==VariantBughouse ||
9033            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9034             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9035         board[fromY][fromX] = EmptySquare;
9036     } else if ((fromY == BOARD_HEIGHT-4)
9037                && (toX != fromX)
9038                && gameInfo.variant != VariantXiangqi
9039                && gameInfo.variant != VariantBerolina
9040                && (board[fromY][fromX] == WhitePawn)
9041                && (board[toY][toX] == EmptySquare)) {
9042         board[fromY][fromX] = EmptySquare;
9043         board[toY][toX] = WhitePawn;
9044         captured = board[toY - 1][toX];
9045         board[toY - 1][toX] = EmptySquare;
9046     } else if ((fromY == BOARD_HEIGHT-4)
9047                && (toX == fromX)
9048                && gameInfo.variant == VariantBerolina
9049                && (board[fromY][fromX] == WhitePawn)
9050                && (board[toY][toX] == EmptySquare)) {
9051         board[fromY][fromX] = EmptySquare;
9052         board[toY][toX] = WhitePawn;
9053         if(oldEP & EP_BEROLIN_A) {
9054                 captured = board[fromY][fromX-1];
9055                 board[fromY][fromX-1] = EmptySquare;
9056         }else{  captured = board[fromY][fromX+1];
9057                 board[fromY][fromX+1] = EmptySquare;
9058         }
9059     } else if (board[fromY][fromX] == king
9060         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9061                && toY == fromY && toX > fromX+1) {
9062         board[fromY][fromX] = EmptySquare;
9063         board[toY][toX] = king;
9064         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9065         board[fromY][BOARD_RGHT-1] = EmptySquare;
9066     } else if (board[fromY][fromX] == king
9067         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9068                && toY == fromY && toX < fromX-1) {
9069         board[fromY][fromX] = EmptySquare;
9070         board[toY][toX] = king;
9071         board[toY][toX+1] = board[fromY][BOARD_LEFT];
9072         board[fromY][BOARD_LEFT] = EmptySquare;
9073     } else if (fromY == 7 && fromX == 3
9074                && board[fromY][fromX] == BlackKing
9075                && toY == 7 && toX == 5) {
9076         board[fromY][fromX] = EmptySquare;
9077         board[toY][toX] = BlackKing;
9078         board[fromY][7] = EmptySquare;
9079         board[toY][4] = BlackRook;
9080     } else if (fromY == 7 && fromX == 3
9081                && board[fromY][fromX] == BlackKing
9082                && toY == 7 && toX == 1) {
9083         board[fromY][fromX] = EmptySquare;
9084         board[toY][toX] = BlackKing;
9085         board[fromY][0] = EmptySquare;
9086         board[toY][2] = BlackRook;
9087     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9088                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9089                && toY < promoRank
9090                ) {
9091         /* black pawn promotion */
9092         board[toY][toX] = CharToPiece(ToLower(promoChar));
9093         if (board[toY][toX] == EmptySquare) {
9094             board[toY][toX] = BlackQueen;
9095         }
9096         if(gameInfo.variant==VariantBughouse ||
9097            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9098             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9099         board[fromY][fromX] = EmptySquare;
9100     } else if ((fromY == 3)
9101                && (toX != fromX)
9102                && gameInfo.variant != VariantXiangqi
9103                && gameInfo.variant != VariantBerolina
9104                && (board[fromY][fromX] == BlackPawn)
9105                && (board[toY][toX] == EmptySquare)) {
9106         board[fromY][fromX] = EmptySquare;
9107         board[toY][toX] = BlackPawn;
9108         captured = board[toY + 1][toX];
9109         board[toY + 1][toX] = EmptySquare;
9110     } else if ((fromY == 3)
9111                && (toX == fromX)
9112                && gameInfo.variant == VariantBerolina
9113                && (board[fromY][fromX] == BlackPawn)
9114                && (board[toY][toX] == EmptySquare)) {
9115         board[fromY][fromX] = EmptySquare;
9116         board[toY][toX] = BlackPawn;
9117         if(oldEP & EP_BEROLIN_A) {
9118                 captured = board[fromY][fromX-1];
9119                 board[fromY][fromX-1] = EmptySquare;
9120         }else{  captured = board[fromY][fromX+1];
9121                 board[fromY][fromX+1] = EmptySquare;
9122         }
9123     } else {
9124         board[toY][toX] = board[fromY][fromX];
9125         board[fromY][fromX] = EmptySquare;
9126     }
9127   }
9128
9129     if (gameInfo.holdingsWidth != 0) {
9130
9131       /* !!A lot more code needs to be written to support holdings  */
9132       /* [HGM] OK, so I have written it. Holdings are stored in the */
9133       /* penultimate board files, so they are automaticlly stored   */
9134       /* in the game history.                                       */
9135       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9136                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9137         /* Delete from holdings, by decreasing count */
9138         /* and erasing image if necessary            */
9139         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9140         if(p < (int) BlackPawn) { /* white drop */
9141              p -= (int)WhitePawn;
9142                  p = PieceToNumber((ChessSquare)p);
9143              if(p >= gameInfo.holdingsSize) p = 0;
9144              if(--board[p][BOARD_WIDTH-2] <= 0)
9145                   board[p][BOARD_WIDTH-1] = EmptySquare;
9146              if((int)board[p][BOARD_WIDTH-2] < 0)
9147                         board[p][BOARD_WIDTH-2] = 0;
9148         } else {                  /* black drop */
9149              p -= (int)BlackPawn;
9150                  p = PieceToNumber((ChessSquare)p);
9151              if(p >= gameInfo.holdingsSize) p = 0;
9152              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9153                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9154              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9155                         board[BOARD_HEIGHT-1-p][1] = 0;
9156         }
9157       }
9158       if (captured != EmptySquare && gameInfo.holdingsSize > 0
9159           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
9160         /* [HGM] holdings: Add to holdings, if holdings exist */
9161         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9162                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9163                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9164         }
9165         p = (int) captured;
9166         if (p >= (int) BlackPawn) {
9167           p -= (int)BlackPawn;
9168           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9169                   /* in Shogi restore piece to its original  first */
9170                   captured = (ChessSquare) (DEMOTED captured);
9171                   p = DEMOTED p;
9172           }
9173           p = PieceToNumber((ChessSquare)p);
9174           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9175           board[p][BOARD_WIDTH-2]++;
9176           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9177         } else {
9178           p -= (int)WhitePawn;
9179           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9180                   captured = (ChessSquare) (DEMOTED captured);
9181                   p = DEMOTED p;
9182           }
9183           p = PieceToNumber((ChessSquare)p);
9184           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9185           board[BOARD_HEIGHT-1-p][1]++;
9186           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9187         }
9188       }
9189     } else if (gameInfo.variant == VariantAtomic) {
9190       if (captured != EmptySquare) {
9191         int y, x;
9192         for (y = toY-1; y <= toY+1; y++) {
9193           for (x = toX-1; x <= toX+1; x++) {
9194             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9195                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9196               board[y][x] = EmptySquare;
9197             }
9198           }
9199         }
9200         board[toY][toX] = EmptySquare;
9201       }
9202     }
9203     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9204         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9205     } else
9206     if(promoChar == '+') {
9207         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9208         board[toY][toX] = (ChessSquare) (PROMOTED piece);
9209     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9210         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9211     }
9212     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
9213                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
9214         // [HGM] superchess: take promotion piece out of holdings
9215         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9216         if((int)piece < (int)BlackPawn) { // determine stm from piece color
9217             if(!--board[k][BOARD_WIDTH-2])
9218                 board[k][BOARD_WIDTH-1] = EmptySquare;
9219         } else {
9220             if(!--board[BOARD_HEIGHT-1-k][1])
9221                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9222         }
9223     }
9224
9225 }
9226
9227 /* Updates forwardMostMove */
9228 void
9229 MakeMove(fromX, fromY, toX, toY, promoChar)
9230      int fromX, fromY, toX, toY;
9231      int promoChar;
9232 {
9233 //    forwardMostMove++; // [HGM] bare: moved downstream
9234
9235     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9236         int timeLeft; static int lastLoadFlag=0; int king, piece;
9237         piece = boards[forwardMostMove][fromY][fromX];
9238         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9239         if(gameInfo.variant == VariantKnightmate)
9240             king += (int) WhiteUnicorn - (int) WhiteKing;
9241         if(forwardMostMove == 0) {
9242             if(blackPlaysFirst)
9243                 fprintf(serverMoves, "%s;", second.tidy);
9244             fprintf(serverMoves, "%s;", first.tidy);
9245             if(!blackPlaysFirst)
9246                 fprintf(serverMoves, "%s;", second.tidy);
9247         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9248         lastLoadFlag = loadFlag;
9249         // print base move
9250         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9251         // print castling suffix
9252         if( toY == fromY && piece == king ) {
9253             if(toX-fromX > 1)
9254                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9255             if(fromX-toX >1)
9256                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9257         }
9258         // e.p. suffix
9259         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9260              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
9261              boards[forwardMostMove][toY][toX] == EmptySquare
9262              && fromX != toX && fromY != toY)
9263                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9264         // promotion suffix
9265         if(promoChar != NULLCHAR)
9266                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9267         if(!loadFlag) {
9268             fprintf(serverMoves, "/%d/%d",
9269                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9270             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9271             else                      timeLeft = blackTimeRemaining/1000;
9272             fprintf(serverMoves, "/%d", timeLeft);
9273         }
9274         fflush(serverMoves);
9275     }
9276
9277     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9278       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9279                         0, 1);
9280       return;
9281     }
9282     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9283     if (commentList[forwardMostMove+1] != NULL) {
9284         free(commentList[forwardMostMove+1]);
9285         commentList[forwardMostMove+1] = NULL;
9286     }
9287     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9288     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9289     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9290     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9291     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9292     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9293     gameInfo.result = GameUnfinished;
9294     if (gameInfo.resultDetails != NULL) {
9295         free(gameInfo.resultDetails);
9296         gameInfo.resultDetails = NULL;
9297     }
9298     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9299                               moveList[forwardMostMove - 1]);
9300     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9301                              PosFlags(forwardMostMove - 1),
9302                              fromY, fromX, toY, toX, promoChar,
9303                              parseList[forwardMostMove - 1]);
9304     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9305       case MT_NONE:
9306       case MT_STALEMATE:
9307       default:
9308         break;
9309       case MT_CHECK:
9310         if(gameInfo.variant != VariantShogi)
9311             strcat(parseList[forwardMostMove - 1], "+");
9312         break;
9313       case MT_CHECKMATE:
9314       case MT_STAINMATE:
9315         strcat(parseList[forwardMostMove - 1], "#");
9316         break;
9317     }
9318     if (appData.debugMode) {
9319         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9320     }
9321
9322 }
9323
9324 /* Updates currentMove if not pausing */
9325 void
9326 ShowMove(fromX, fromY, toX, toY)
9327 {
9328     int instant = (gameMode == PlayFromGameFile) ?
9329         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9330     if(appData.noGUI) return;
9331     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9332         if (!instant) {
9333             if (forwardMostMove == currentMove + 1) {
9334                 AnimateMove(boards[forwardMostMove - 1],
9335                             fromX, fromY, toX, toY);
9336             }
9337             if (appData.highlightLastMove) {
9338                 SetHighlights(fromX, fromY, toX, toY);
9339             }
9340         }
9341         currentMove = forwardMostMove;
9342     }
9343
9344     if (instant) return;
9345
9346     DisplayMove(currentMove - 1);
9347     DrawPosition(FALSE, boards[currentMove]);
9348     DisplayBothClocks();
9349     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9350     DisplayBook(currentMove);
9351 }
9352
9353 void SendEgtPath(ChessProgramState *cps)
9354 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9355         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9356
9357         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9358
9359         while(*p) {
9360             char c, *q = name+1, *r, *s;
9361
9362             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9363             while(*p && *p != ',') *q++ = *p++;
9364             *q++ = ':'; *q = 0;
9365             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9366                 strcmp(name, ",nalimov:") == 0 ) {
9367                 // take nalimov path from the menu-changeable option first, if it is defined
9368               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9369                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9370             } else
9371             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9372                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9373                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9374                 s = r = StrStr(s, ":") + 1; // beginning of path info
9375                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9376                 c = *r; *r = 0;             // temporarily null-terminate path info
9377                     *--q = 0;               // strip of trailig ':' from name
9378                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9379                 *r = c;
9380                 SendToProgram(buf,cps);     // send egtbpath command for this format
9381             }
9382             if(*p == ',') p++; // read away comma to position for next format name
9383         }
9384 }
9385
9386 void
9387 InitChessProgram(cps, setup)
9388      ChessProgramState *cps;
9389      int setup; /* [HGM] needed to setup FRC opening position */
9390 {
9391     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9392     if (appData.noChessProgram) return;
9393     hintRequested = FALSE;
9394     bookRequested = FALSE;
9395
9396     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9397     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9398     if(cps->memSize) { /* [HGM] memory */
9399       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9400         SendToProgram(buf, cps);
9401     }
9402     SendEgtPath(cps); /* [HGM] EGT */
9403     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9404       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9405         SendToProgram(buf, cps);
9406     }
9407
9408     SendToProgram(cps->initString, cps);
9409     if (gameInfo.variant != VariantNormal &&
9410         gameInfo.variant != VariantLoadable
9411         /* [HGM] also send variant if board size non-standard */
9412         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9413                                             ) {
9414       char *v = VariantName(gameInfo.variant);
9415       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9416         /* [HGM] in protocol 1 we have to assume all variants valid */
9417         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9418         DisplayFatalError(buf, 0, 1);
9419         return;
9420       }
9421
9422       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9423       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9424       if( gameInfo.variant == VariantXiangqi )
9425            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9426       if( gameInfo.variant == VariantShogi )
9427            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9428       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9429            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9430       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9431           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9432            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9433       if( gameInfo.variant == VariantCourier )
9434            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9435       if( gameInfo.variant == VariantSuper )
9436            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9437       if( gameInfo.variant == VariantGreat )
9438            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9439       if( gameInfo.variant == VariantSChess )
9440            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9441
9442       if(overruled) {
9443         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9444                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9445            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9446            if(StrStr(cps->variants, b) == NULL) {
9447                // specific sized variant not known, check if general sizing allowed
9448                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9449                    if(StrStr(cps->variants, "boardsize") == NULL) {
9450                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9451                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9452                        DisplayFatalError(buf, 0, 1);
9453                        return;
9454                    }
9455                    /* [HGM] here we really should compare with the maximum supported board size */
9456                }
9457            }
9458       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9459       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9460       SendToProgram(buf, cps);
9461     }
9462     currentlyInitializedVariant = gameInfo.variant;
9463
9464     /* [HGM] send opening position in FRC to first engine */
9465     if(setup) {
9466           SendToProgram("force\n", cps);
9467           SendBoard(cps, 0);
9468           /* engine is now in force mode! Set flag to wake it up after first move. */
9469           setboardSpoiledMachineBlack = 1;
9470     }
9471
9472     if (cps->sendICS) {
9473       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9474       SendToProgram(buf, cps);
9475     }
9476     cps->maybeThinking = FALSE;
9477     cps->offeredDraw = 0;
9478     if (!appData.icsActive) {
9479         SendTimeControl(cps, movesPerSession, timeControl,
9480                         timeIncrement, appData.searchDepth,
9481                         searchTime);
9482     }
9483     if (appData.showThinking
9484         // [HGM] thinking: four options require thinking output to be sent
9485         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9486                                 ) {
9487         SendToProgram("post\n", cps);
9488     }
9489     SendToProgram("hard\n", cps);
9490     if (!appData.ponderNextMove) {
9491         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9492            it without being sure what state we are in first.  "hard"
9493            is not a toggle, so that one is OK.
9494          */
9495         SendToProgram("easy\n", cps);
9496     }
9497     if (cps->usePing) {
9498       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9499       SendToProgram(buf, cps);
9500     }
9501     cps->initDone = TRUE;
9502     ClearEngineOutputPane(cps == &second);
9503 }
9504
9505
9506 void
9507 StartChessProgram(cps)
9508      ChessProgramState *cps;
9509 {
9510     char buf[MSG_SIZ];
9511     int err;
9512
9513     if (appData.noChessProgram) return;
9514     cps->initDone = FALSE;
9515
9516     if (strcmp(cps->host, "localhost") == 0) {
9517         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9518     } else if (*appData.remoteShell == NULLCHAR) {
9519         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9520     } else {
9521         if (*appData.remoteUser == NULLCHAR) {
9522           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9523                     cps->program);
9524         } else {
9525           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9526                     cps->host, appData.remoteUser, cps->program);
9527         }
9528         err = StartChildProcess(buf, "", &cps->pr);
9529     }
9530
9531     if (err != 0) {
9532       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9533         DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9534         if(cps != &first) return;
9535         appData.noChessProgram = TRUE;
9536         ThawUI();
9537         SetNCPMode();
9538 //      DisplayFatalError(buf, err, 1);
9539 //      cps->pr = NoProc;
9540 //      cps->isr = NULL;
9541         return;
9542     }
9543
9544     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9545     if (cps->protocolVersion > 1) {
9546       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9547       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9548       cps->comboCnt = 0;  //                and values of combo boxes
9549       SendToProgram(buf, cps);
9550     } else {
9551       SendToProgram("xboard\n", cps);
9552     }
9553 }
9554
9555 void
9556 TwoMachinesEventIfReady P((void))
9557 {
9558   static int curMess = 0;
9559   if (first.lastPing != first.lastPong) {
9560     if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9561     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9562     return;
9563   }
9564   if (second.lastPing != second.lastPong) {
9565     if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9566     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9567     return;
9568   }
9569   DisplayMessage("", ""); curMess = 0;
9570   ThawUI();
9571   TwoMachinesEvent();
9572 }
9573
9574 char *
9575 MakeName(char *template)
9576 {
9577     time_t clock;
9578     struct tm *tm;
9579     static char buf[MSG_SIZ];
9580     char *p = buf;
9581     int i;
9582
9583     clock = time((time_t *)NULL);
9584     tm = localtime(&clock);
9585
9586     while(*p++ = *template++) if(p[-1] == '%') {
9587         switch(*template++) {
9588           case 0:   *p = 0; return buf;
9589           case 'Y': i = tm->tm_year+1900; break;
9590           case 'y': i = tm->tm_year-100; break;
9591           case 'M': i = tm->tm_mon+1; break;
9592           case 'd': i = tm->tm_mday; break;
9593           case 'h': i = tm->tm_hour; break;
9594           case 'm': i = tm->tm_min; break;
9595           case 's': i = tm->tm_sec; break;
9596           default:  i = 0;
9597         }
9598         snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9599     }
9600     return buf;
9601 }
9602
9603 int
9604 CountPlayers(char *p)
9605 {
9606     int n = 0;
9607     while(p = strchr(p, '\n')) p++, n++; // count participants
9608     return n;
9609 }
9610
9611 FILE *
9612 WriteTourneyFile(char *results)
9613 {   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9614     FILE *f = fopen(appData.tourneyFile, "w");
9615     if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9616         // create a file with tournament description
9617         fprintf(f, "-participants {%s}\n", appData.participants);
9618         fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9619         fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9620         fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9621         fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9622         fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9623         fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9624         fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9625         fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9626         fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9627         fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9628         fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9629         if(searchTime > 0)
9630                 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9631         else {
9632                 fprintf(f, "-mps %d\n", appData.movesPerSession);
9633                 fprintf(f, "-tc %s\n", appData.timeControl);
9634                 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9635         }
9636         fprintf(f, "-results \"%s\"\n", results);
9637     }
9638     return f;
9639 }
9640
9641 int
9642 CreateTourney(char *name)
9643 {
9644         FILE *f;
9645         if(name[0] == NULLCHAR) {
9646             if(appData.participants[0])
9647                 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9648             return 0;
9649         }
9650         f = fopen(name, "r");
9651         if(f) { // file exists
9652             ASSIGN(appData.tourneyFile, name);
9653             ParseArgsFromFile(f); // parse it
9654         } else {
9655             if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9656             if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9657                 DisplayError(_("Not enough participants"), 0);
9658                 return 0;
9659             }
9660             ASSIGN(appData.tourneyFile, name);
9661             if((f = WriteTourneyFile("")) == NULL) return 0;
9662         }
9663         fclose(f);
9664         appData.noChessProgram = FALSE;
9665         appData.clockMode = TRUE;
9666         SetGNUMode();
9667         return 1;
9668 }
9669
9670 #define MAXENGINES 1000
9671 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9672
9673 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9674 {
9675     char buf[MSG_SIZ], *p, *q;
9676     int i=1;
9677     while(*names) {
9678         p = names; q = buf;
9679         while(*p && *p != '\n') *q++ = *p++;
9680         *q = 0;
9681         if(engineList[i]) free(engineList[i]);
9682         engineList[i] = strdup(buf);
9683         if(*p == '\n') p++;
9684         TidyProgramName(engineList[i], "localhost", buf);
9685         if(engineMnemonic[i]) free(engineMnemonic[i]);
9686         if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9687             strcat(buf, " (");
9688             sscanf(q + 8, "%s", buf + strlen(buf));
9689             strcat(buf, ")");
9690         }
9691         engineMnemonic[i] = strdup(buf);
9692         names = p; i++;
9693       if(i > MAXENGINES - 2) break;
9694     }
9695     engineList[i] = NULL;
9696 }
9697
9698 // following implemented as macro to avoid type limitations
9699 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9700
9701 void SwapEngines(int n)
9702 {   // swap settings for first engine and other engine (so far only some selected options)
9703     int h;
9704     char *p;
9705     if(n == 0) return;
9706     SWAP(directory, p)
9707     SWAP(chessProgram, p)
9708     SWAP(isUCI, h)
9709     SWAP(hasOwnBookUCI, h)
9710     SWAP(protocolVersion, h)
9711     SWAP(reuse, h)
9712     SWAP(scoreIsAbsolute, h)
9713     SWAP(timeOdds, h)
9714     SWAP(logo, p)
9715     SWAP(pgnName, p)
9716 }
9717
9718 void
9719 SetPlayer(int player)
9720 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
9721     int i;
9722     char buf[MSG_SIZ], *engineName, *p = appData.participants;
9723     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9724     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9725     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9726     if(mnemonic[i]) {
9727         snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9728         ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9729         ParseArgsFromString(buf);
9730     }
9731     free(engineName);
9732 }
9733
9734 int
9735 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9736 {   // determine players from game number
9737     int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9738
9739     if(appData.tourneyType == 0) {
9740         roundsPerCycle = (nPlayers - 1) | 1;
9741         pairingsPerRound = nPlayers / 2;
9742     } else if(appData.tourneyType > 0) {
9743         roundsPerCycle = nPlayers - appData.tourneyType;
9744         pairingsPerRound = appData.tourneyType;
9745     }
9746     gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9747     gamesPerCycle = gamesPerRound * roundsPerCycle;
9748     appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9749     curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9750     curRound = nr / gamesPerRound; nr %= gamesPerRound;
9751     curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9752     matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9753     roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9754
9755     if(appData.cycleSync) *syncInterval = gamesPerCycle;
9756     if(appData.roundSync) *syncInterval = gamesPerRound;
9757
9758     if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9759
9760     if(appData.tourneyType == 0) {
9761         if(curPairing == (nPlayers-1)/2 ) {
9762             *whitePlayer = curRound;
9763             *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9764         } else {
9765             *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9766             if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9767             *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9768             if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9769         }
9770     } else if(appData.tourneyType > 0) {
9771         *whitePlayer = curPairing;
9772         *blackPlayer = curRound + appData.tourneyType;
9773     }
9774
9775     // take care of white/black alternation per round. 
9776     // For cycles and games this is already taken care of by default, derived from matchGame!
9777     return curRound & 1;
9778 }
9779
9780 int
9781 NextTourneyGame(int nr, int *swapColors)
9782 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9783     char *p, *q;
9784     int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9785     FILE *tf;
9786     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9787     tf = fopen(appData.tourneyFile, "r");
9788     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9789     ParseArgsFromFile(tf); fclose(tf);
9790     InitTimeControls(); // TC might be altered from tourney file
9791
9792     nPlayers = CountPlayers(appData.participants); // count participants
9793     if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9794     *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9795
9796     if(syncInterval) {
9797         p = q = appData.results;
9798         while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9799         if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9800             DisplayMessage(_("Waiting for other game(s)"),"");
9801             waitingForGame = TRUE;
9802             ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9803             return 0;
9804         }
9805         waitingForGame = FALSE;
9806     }
9807
9808     if(appData.tourneyType < 0) {
9809         if(nr>=0 && !pairingReceived) {
9810             char buf[1<<16];
9811             if(pairing.pr == NoProc) {
9812                 if(!appData.pairingEngine[0]) {
9813                     DisplayFatalError(_("No pairing engine specified"), 0, 1);
9814                     return 0;
9815                 }
9816                 StartChessProgram(&pairing); // starts the pairing engine
9817             }
9818             snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9819             SendToProgram(buf, &pairing);
9820             snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9821             SendToProgram(buf, &pairing);
9822             return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9823         }
9824         pairingReceived = 0;                              // ... so we continue here 
9825         *swapColors = 0;
9826         appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9827         whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9828         matchGame = 1; roundNr = nr / syncInterval + 1;
9829     }
9830
9831     if(first.pr != NoProc) return 1; // engines already loaded
9832
9833     // redefine engines, engine dir, etc.
9834     NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9835     SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9836     SwapEngines(1);
9837     SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9838     SwapEngines(1);         // and make that valid for second engine by swapping
9839     InitEngine(&first, 0);  // initialize ChessProgramStates based on new settings.
9840     InitEngine(&second, 1);
9841     CommonEngineInit();     // after this TwoMachinesEvent will create correct engine processes
9842     UpdateLogos(FALSE);     // leave display to ModeHiglight()
9843     return 1;
9844 }
9845
9846 void
9847 NextMatchGame()
9848 {   // performs game initialization that does not invoke engines, and then tries to start the game
9849     int firstWhite, swapColors = 0;
9850     if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9851     firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9852     firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9853     first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
9854     second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9855     appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9856     Reset(FALSE, first.pr != NoProc);
9857     appData.noChessProgram = FALSE;
9858     if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9859     TwoMachinesEvent();
9860 }
9861
9862 void UserAdjudicationEvent( int result )
9863 {
9864     ChessMove gameResult = GameIsDrawn;
9865
9866     if( result > 0 ) {
9867         gameResult = WhiteWins;
9868     }
9869     else if( result < 0 ) {
9870         gameResult = BlackWins;
9871     }
9872
9873     if( gameMode == TwoMachinesPlay ) {
9874         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9875     }
9876 }
9877
9878
9879 // [HGM] save: calculate checksum of game to make games easily identifiable
9880 int StringCheckSum(char *s)
9881 {
9882         int i = 0;
9883         if(s==NULL) return 0;
9884         while(*s) i = i*259 + *s++;
9885         return i;
9886 }
9887
9888 int GameCheckSum()
9889 {
9890         int i, sum=0;
9891         for(i=backwardMostMove; i<forwardMostMove; i++) {
9892                 sum += pvInfoList[i].depth;
9893                 sum += StringCheckSum(parseList[i]);
9894                 sum += StringCheckSum(commentList[i]);
9895                 sum *= 261;
9896         }
9897         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9898         return sum + StringCheckSum(commentList[i]);
9899 } // end of save patch
9900
9901 void
9902 GameEnds(result, resultDetails, whosays)
9903      ChessMove result;
9904      char *resultDetails;
9905      int whosays;
9906 {
9907     GameMode nextGameMode;
9908     int isIcsGame;
9909     char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9910
9911     if(endingGame) return; /* [HGM] crash: forbid recursion */
9912     endingGame = 1;
9913     if(twoBoards) { // [HGM] dual: switch back to one board
9914         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9915         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9916     }
9917     if (appData.debugMode) {
9918       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9919               result, resultDetails ? resultDetails : "(null)", whosays);
9920     }
9921
9922     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9923
9924     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9925         /* If we are playing on ICS, the server decides when the
9926            game is over, but the engine can offer to draw, claim
9927            a draw, or resign.
9928          */
9929 #if ZIPPY
9930         if (appData.zippyPlay && first.initDone) {
9931             if (result == GameIsDrawn) {
9932                 /* In case draw still needs to be claimed */
9933                 SendToICS(ics_prefix);
9934                 SendToICS("draw\n");
9935             } else if (StrCaseStr(resultDetails, "resign")) {
9936                 SendToICS(ics_prefix);
9937                 SendToICS("resign\n");
9938             }
9939         }
9940 #endif
9941         endingGame = 0; /* [HGM] crash */
9942         return;
9943     }
9944
9945     /* If we're loading the game from a file, stop */
9946     if (whosays == GE_FILE) {
9947       (void) StopLoadGameTimer();
9948       gameFileFP = NULL;
9949     }
9950
9951     /* Cancel draw offers */
9952     first.offeredDraw = second.offeredDraw = 0;
9953
9954     /* If this is an ICS game, only ICS can really say it's done;
9955        if not, anyone can. */
9956     isIcsGame = (gameMode == IcsPlayingWhite ||
9957                  gameMode == IcsPlayingBlack ||
9958                  gameMode == IcsObserving    ||
9959                  gameMode == IcsExamining);
9960
9961     if (!isIcsGame || whosays == GE_ICS) {
9962         /* OK -- not an ICS game, or ICS said it was done */
9963         StopClocks();
9964         if (!isIcsGame && !appData.noChessProgram)
9965           SetUserThinkingEnables();
9966
9967         /* [HGM] if a machine claims the game end we verify this claim */
9968         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9969             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9970                 char claimer;
9971                 ChessMove trueResult = (ChessMove) -1;
9972
9973                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9974                                             first.twoMachinesColor[0] :
9975                                             second.twoMachinesColor[0] ;
9976
9977                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9978                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9979                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9980                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9981                 } else
9982                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9983                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9984                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9985                 } else
9986                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9987                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9988                 }
9989
9990                 // now verify win claims, but not in drop games, as we don't understand those yet
9991                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9992                                                  || gameInfo.variant == VariantGreat) &&
9993                     (result == WhiteWins && claimer == 'w' ||
9994                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9995                       if (appData.debugMode) {
9996                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9997                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9998                       }
9999                       if(result != trueResult) {
10000                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10001                               result = claimer == 'w' ? BlackWins : WhiteWins;
10002                               resultDetails = buf;
10003                       }
10004                 } else
10005                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10006                     && (forwardMostMove <= backwardMostMove ||
10007                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10008                         (claimer=='b')==(forwardMostMove&1))
10009                                                                                   ) {
10010                       /* [HGM] verify: draws that were not flagged are false claims */
10011                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10012                       result = claimer == 'w' ? BlackWins : WhiteWins;
10013                       resultDetails = buf;
10014                 }
10015                 /* (Claiming a loss is accepted no questions asked!) */
10016             }
10017             /* [HGM] bare: don't allow bare King to win */
10018             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
10019                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10020                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10021                && result != GameIsDrawn)
10022             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10023                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10024                         int p = (signed char)boards[forwardMostMove][i][j] - color;
10025                         if(p >= 0 && p <= (int)WhiteKing) k++;
10026                 }
10027                 if (appData.debugMode) {
10028                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10029                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10030                 }
10031                 if(k <= 1) {
10032                         result = GameIsDrawn;
10033                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10034                         resultDetails = buf;
10035                 }
10036             }
10037         }
10038
10039
10040         if(serverMoves != NULL && !loadFlag) { char c = '=';
10041             if(result==WhiteWins) c = '+';
10042             if(result==BlackWins) c = '-';
10043             if(resultDetails != NULL)
10044                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10045         }
10046         if (resultDetails != NULL) {
10047             gameInfo.result = result;
10048             gameInfo.resultDetails = StrSave(resultDetails);
10049
10050             /* display last move only if game was not loaded from file */
10051             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10052                 DisplayMove(currentMove - 1);
10053
10054             if (forwardMostMove != 0) {
10055                 if (gameMode != PlayFromGameFile && gameMode != EditGame
10056                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10057                                                                 ) {
10058                     if (*appData.saveGameFile != NULLCHAR) {
10059                         SaveGameToFile(appData.saveGameFile, TRUE);
10060                     } else if (appData.autoSaveGames) {
10061                         AutoSaveGame();
10062                     }
10063                     if (*appData.savePositionFile != NULLCHAR) {
10064                         SavePositionToFile(appData.savePositionFile);
10065                     }
10066                 }
10067             }
10068
10069             /* Tell program how game ended in case it is learning */
10070             /* [HGM] Moved this to after saving the PGN, just in case */
10071             /* engine died and we got here through time loss. In that */
10072             /* case we will get a fatal error writing the pipe, which */
10073             /* would otherwise lose us the PGN.                       */
10074             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
10075             /* output during GameEnds should never be fatal anymore   */
10076             if (gameMode == MachinePlaysWhite ||
10077                 gameMode == MachinePlaysBlack ||
10078                 gameMode == TwoMachinesPlay ||
10079                 gameMode == IcsPlayingWhite ||
10080                 gameMode == IcsPlayingBlack ||
10081                 gameMode == BeginningOfGame) {
10082                 char buf[MSG_SIZ];
10083                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10084                         resultDetails);
10085                 if (first.pr != NoProc) {
10086                     SendToProgram(buf, &first);
10087                 }
10088                 if (second.pr != NoProc &&
10089                     gameMode == TwoMachinesPlay) {
10090                     SendToProgram(buf, &second);
10091                 }
10092             }
10093         }
10094
10095         if (appData.icsActive) {
10096             if (appData.quietPlay &&
10097                 (gameMode == IcsPlayingWhite ||
10098                  gameMode == IcsPlayingBlack)) {
10099                 SendToICS(ics_prefix);
10100                 SendToICS("set shout 1\n");
10101             }
10102             nextGameMode = IcsIdle;
10103             ics_user_moved = FALSE;
10104             /* clean up premove.  It's ugly when the game has ended and the
10105              * premove highlights are still on the board.
10106              */
10107             if (gotPremove) {
10108               gotPremove = FALSE;
10109               ClearPremoveHighlights();
10110               DrawPosition(FALSE, boards[currentMove]);
10111             }
10112             if (whosays == GE_ICS) {
10113                 switch (result) {
10114                 case WhiteWins:
10115                     if (gameMode == IcsPlayingWhite)
10116                         PlayIcsWinSound();
10117                     else if(gameMode == IcsPlayingBlack)
10118                         PlayIcsLossSound();
10119                     break;
10120                 case BlackWins:
10121                     if (gameMode == IcsPlayingBlack)
10122                         PlayIcsWinSound();
10123                     else if(gameMode == IcsPlayingWhite)
10124                         PlayIcsLossSound();
10125                     break;
10126                 case GameIsDrawn:
10127                     PlayIcsDrawSound();
10128                     break;
10129                 default:
10130                     PlayIcsUnfinishedSound();
10131                 }
10132             }
10133         } else if (gameMode == EditGame ||
10134                    gameMode == PlayFromGameFile ||
10135                    gameMode == AnalyzeMode ||
10136                    gameMode == AnalyzeFile) {
10137             nextGameMode = gameMode;
10138         } else {
10139             nextGameMode = EndOfGame;
10140         }
10141         pausing = FALSE;
10142         ModeHighlight();
10143     } else {
10144         nextGameMode = gameMode;
10145     }
10146
10147     if (appData.noChessProgram) {
10148         gameMode = nextGameMode;
10149         ModeHighlight();
10150         endingGame = 0; /* [HGM] crash */
10151         return;
10152     }
10153
10154     if (first.reuse) {
10155         /* Put first chess program into idle state */
10156         if (first.pr != NoProc &&
10157             (gameMode == MachinePlaysWhite ||
10158              gameMode == MachinePlaysBlack ||
10159              gameMode == TwoMachinesPlay ||
10160              gameMode == IcsPlayingWhite ||
10161              gameMode == IcsPlayingBlack ||
10162              gameMode == BeginningOfGame)) {
10163             SendToProgram("force\n", &first);
10164             if (first.usePing) {
10165               char buf[MSG_SIZ];
10166               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10167               SendToProgram(buf, &first);
10168             }
10169         }
10170     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10171         /* Kill off first chess program */
10172         if (first.isr != NULL)
10173           RemoveInputSource(first.isr);
10174         first.isr = NULL;
10175
10176         if (first.pr != NoProc) {
10177             ExitAnalyzeMode();
10178             DoSleep( appData.delayBeforeQuit );
10179             SendToProgram("quit\n", &first);
10180             DoSleep( appData.delayAfterQuit );
10181             DestroyChildProcess(first.pr, first.useSigterm);
10182         }
10183         first.pr = NoProc;
10184     }
10185     if (second.reuse) {
10186         /* Put second chess program into idle state */
10187         if (second.pr != NoProc &&
10188             gameMode == TwoMachinesPlay) {
10189             SendToProgram("force\n", &second);
10190             if (second.usePing) {
10191               char buf[MSG_SIZ];
10192               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10193               SendToProgram(buf, &second);
10194             }
10195         }
10196     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10197         /* Kill off second chess program */
10198         if (second.isr != NULL)
10199           RemoveInputSource(second.isr);
10200         second.isr = NULL;
10201
10202         if (second.pr != NoProc) {
10203             DoSleep( appData.delayBeforeQuit );
10204             SendToProgram("quit\n", &second);
10205             DoSleep( appData.delayAfterQuit );
10206             DestroyChildProcess(second.pr, second.useSigterm);
10207         }
10208         second.pr = NoProc;
10209     }
10210
10211     if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10212         char resChar = '=';
10213         switch (result) {
10214         case WhiteWins:
10215           resChar = '+';
10216           if (first.twoMachinesColor[0] == 'w') {
10217             first.matchWins++;
10218           } else {
10219             second.matchWins++;
10220           }
10221           break;
10222         case BlackWins:
10223           resChar = '-';
10224           if (first.twoMachinesColor[0] == 'b') {
10225             first.matchWins++;
10226           } else {
10227             second.matchWins++;
10228           }
10229           break;
10230         case GameUnfinished:
10231           resChar = ' ';
10232         default:
10233           break;
10234         }
10235
10236         if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10237         if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10238             ReserveGame(nextGame, resChar); // sets nextGame
10239             if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10240             else ranking = strdup("busy"); //suppress popup when aborted but not finished
10241         } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10242
10243         if (nextGame <= appData.matchGames && !abortMatch) {
10244             gameMode = nextGameMode;
10245             matchGame = nextGame; // this will be overruled in tourney mode!
10246             GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10247             ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10248             endingGame = 0; /* [HGM] crash */
10249             return;
10250         } else {
10251             gameMode = nextGameMode;
10252             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10253                      first.tidy, second.tidy,
10254                      first.matchWins, second.matchWins,
10255                      appData.matchGames - (first.matchWins + second.matchWins));
10256             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10257             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10258                 first.twoMachinesColor = "black\n";
10259                 second.twoMachinesColor = "white\n";
10260             } else {
10261                 first.twoMachinesColor = "white\n";
10262                 second.twoMachinesColor = "black\n";
10263             }
10264         }
10265     }
10266     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10267         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10268       ExitAnalyzeMode();
10269     gameMode = nextGameMode;
10270     ModeHighlight();
10271     endingGame = 0;  /* [HGM] crash */
10272     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10273         if(matchMode == TRUE) { // match through command line: exit with or without popup
10274             if(ranking) {
10275                 ToNrEvent(forwardMostMove);
10276                 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10277                 else ExitEvent(0);
10278             } else DisplayFatalError(buf, 0, 0);
10279         } else { // match through menu; just stop, with or without popup
10280             matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10281             if(ranking){
10282                 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10283             } else DisplayNote(buf);
10284       }
10285       if(ranking) free(ranking);
10286     }
10287 }
10288
10289 /* Assumes program was just initialized (initString sent).
10290    Leaves program in force mode. */
10291 void
10292 FeedMovesToProgram(cps, upto)
10293      ChessProgramState *cps;
10294      int upto;
10295 {
10296     int i;
10297
10298     if (appData.debugMode)
10299       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10300               startedFromSetupPosition ? "position and " : "",
10301               backwardMostMove, upto, cps->which);
10302     if(currentlyInitializedVariant != gameInfo.variant) {
10303       char buf[MSG_SIZ];
10304         // [HGM] variantswitch: make engine aware of new variant
10305         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10306                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10307         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10308         SendToProgram(buf, cps);
10309         currentlyInitializedVariant = gameInfo.variant;
10310     }
10311     SendToProgram("force\n", cps);
10312     if (startedFromSetupPosition) {
10313         SendBoard(cps, backwardMostMove);
10314     if (appData.debugMode) {
10315         fprintf(debugFP, "feedMoves\n");
10316     }
10317     }
10318     for (i = backwardMostMove; i < upto; i++) {
10319         SendMoveToProgram(i, cps);
10320     }
10321 }
10322
10323
10324 int
10325 ResurrectChessProgram()
10326 {
10327      /* The chess program may have exited.
10328         If so, restart it and feed it all the moves made so far. */
10329     static int doInit = 0;
10330
10331     if (appData.noChessProgram) return 1;
10332
10333     if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10334         if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10335         if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10336         doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10337     } else {
10338         if (first.pr != NoProc) return 1;
10339         StartChessProgram(&first);
10340     }
10341     InitChessProgram(&first, FALSE);
10342     FeedMovesToProgram(&first, currentMove);
10343
10344     if (!first.sendTime) {
10345         /* can't tell gnuchess what its clock should read,
10346            so we bow to its notion. */
10347         ResetClocks();
10348         timeRemaining[0][currentMove] = whiteTimeRemaining;
10349         timeRemaining[1][currentMove] = blackTimeRemaining;
10350     }
10351
10352     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10353                 appData.icsEngineAnalyze) && first.analysisSupport) {
10354       SendToProgram("analyze\n", &first);
10355       first.analyzing = TRUE;
10356     }
10357     return 1;
10358 }
10359
10360 /*
10361  * Button procedures
10362  */
10363 void
10364 Reset(redraw, init)
10365      int redraw, init;
10366 {
10367     int i;
10368
10369     if (appData.debugMode) {
10370         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10371                 redraw, init, gameMode);
10372     }
10373     CleanupTail(); // [HGM] vari: delete any stored variations
10374     pausing = pauseExamInvalid = FALSE;
10375     startedFromSetupPosition = blackPlaysFirst = FALSE;
10376     firstMove = TRUE;
10377     whiteFlag = blackFlag = FALSE;
10378     userOfferedDraw = FALSE;
10379     hintRequested = bookRequested = FALSE;
10380     first.maybeThinking = FALSE;
10381     second.maybeThinking = FALSE;
10382     first.bookSuspend = FALSE; // [HGM] book
10383     second.bookSuspend = FALSE;
10384     thinkOutput[0] = NULLCHAR;
10385     lastHint[0] = NULLCHAR;
10386     ClearGameInfo(&gameInfo);
10387     gameInfo.variant = StringToVariant(appData.variant);
10388     ics_user_moved = ics_clock_paused = FALSE;
10389     ics_getting_history = H_FALSE;
10390     ics_gamenum = -1;
10391     white_holding[0] = black_holding[0] = NULLCHAR;
10392     ClearProgramStats();
10393     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10394
10395     ResetFrontEnd();
10396     ClearHighlights();
10397     flipView = appData.flipView;
10398     ClearPremoveHighlights();
10399     gotPremove = FALSE;
10400     alarmSounded = FALSE;
10401
10402     GameEnds(EndOfFile, NULL, GE_PLAYER);
10403     if(appData.serverMovesName != NULL) {
10404         /* [HGM] prepare to make moves file for broadcasting */
10405         clock_t t = clock();
10406         if(serverMoves != NULL) fclose(serverMoves);
10407         serverMoves = fopen(appData.serverMovesName, "r");
10408         if(serverMoves != NULL) {
10409             fclose(serverMoves);
10410             /* delay 15 sec before overwriting, so all clients can see end */
10411             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10412         }
10413         serverMoves = fopen(appData.serverMovesName, "w");
10414     }
10415
10416     ExitAnalyzeMode();
10417     gameMode = BeginningOfGame;
10418     ModeHighlight();
10419     if(appData.icsActive) gameInfo.variant = VariantNormal;
10420     currentMove = forwardMostMove = backwardMostMove = 0;
10421     InitPosition(redraw);
10422     for (i = 0; i < MAX_MOVES; i++) {
10423         if (commentList[i] != NULL) {
10424             free(commentList[i]);
10425             commentList[i] = NULL;
10426         }
10427     }
10428     ResetClocks();
10429     timeRemaining[0][0] = whiteTimeRemaining;
10430     timeRemaining[1][0] = blackTimeRemaining;
10431
10432     if (first.pr == NULL) {
10433         StartChessProgram(&first);
10434     }
10435     if (init) {
10436             InitChessProgram(&first, startedFromSetupPosition);
10437     }
10438     DisplayTitle("");
10439     DisplayMessage("", "");
10440     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10441     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10442 }
10443
10444 void
10445 AutoPlayGameLoop()
10446 {
10447     for (;;) {
10448         if (!AutoPlayOneMove())
10449           return;
10450         if (matchMode || appData.timeDelay == 0)
10451           continue;
10452         if (appData.timeDelay < 0)
10453           return;
10454         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10455         break;
10456     }
10457 }
10458
10459
10460 int
10461 AutoPlayOneMove()
10462 {
10463     int fromX, fromY, toX, toY;
10464
10465     if (appData.debugMode) {
10466       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10467     }
10468
10469     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10470       return FALSE;
10471
10472     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10473       pvInfoList[currentMove].depth = programStats.depth;
10474       pvInfoList[currentMove].score = programStats.score;
10475       pvInfoList[currentMove].time  = 0;
10476       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10477     }
10478
10479     if (currentMove >= forwardMostMove) {
10480       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10481       gameMode = EditGame;
10482       ModeHighlight();
10483
10484       /* [AS] Clear current move marker at the end of a game */
10485       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10486
10487       return FALSE;
10488     }
10489
10490     toX = moveList[currentMove][2] - AAA;
10491     toY = moveList[currentMove][3] - ONE;
10492
10493     if (moveList[currentMove][1] == '@') {
10494         if (appData.highlightLastMove) {
10495             SetHighlights(-1, -1, toX, toY);
10496         }
10497     } else {
10498         fromX = moveList[currentMove][0] - AAA;
10499         fromY = moveList[currentMove][1] - ONE;
10500
10501         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10502
10503         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10504
10505         if (appData.highlightLastMove) {
10506             SetHighlights(fromX, fromY, toX, toY);
10507         }
10508     }
10509     DisplayMove(currentMove);
10510     SendMoveToProgram(currentMove++, &first);
10511     DisplayBothClocks();
10512     DrawPosition(FALSE, boards[currentMove]);
10513     // [HGM] PV info: always display, routine tests if empty
10514     DisplayComment(currentMove - 1, commentList[currentMove]);
10515     return TRUE;
10516 }
10517
10518
10519 int
10520 LoadGameOneMove(readAhead)
10521      ChessMove readAhead;
10522 {
10523     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10524     char promoChar = NULLCHAR;
10525     ChessMove moveType;
10526     char move[MSG_SIZ];
10527     char *p, *q;
10528
10529     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10530         gameMode != AnalyzeMode && gameMode != Training) {
10531         gameFileFP = NULL;
10532         return FALSE;
10533     }
10534
10535     yyboardindex = forwardMostMove;
10536     if (readAhead != EndOfFile) {
10537       moveType = readAhead;
10538     } else {
10539       if (gameFileFP == NULL)
10540           return FALSE;
10541       moveType = (ChessMove) Myylex();
10542     }
10543
10544     done = FALSE;
10545     switch (moveType) {
10546       case Comment:
10547         if (appData.debugMode)
10548           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10549         p = yy_text;
10550
10551         /* append the comment but don't display it */
10552         AppendComment(currentMove, p, FALSE);
10553         return TRUE;
10554
10555       case WhiteCapturesEnPassant:
10556       case BlackCapturesEnPassant:
10557       case WhitePromotion:
10558       case BlackPromotion:
10559       case WhiteNonPromotion:
10560       case BlackNonPromotion:
10561       case NormalMove:
10562       case WhiteKingSideCastle:
10563       case WhiteQueenSideCastle:
10564       case BlackKingSideCastle:
10565       case BlackQueenSideCastle:
10566       case WhiteKingSideCastleWild:
10567       case WhiteQueenSideCastleWild:
10568       case BlackKingSideCastleWild:
10569       case BlackQueenSideCastleWild:
10570       /* PUSH Fabien */
10571       case WhiteHSideCastleFR:
10572       case WhiteASideCastleFR:
10573       case BlackHSideCastleFR:
10574       case BlackASideCastleFR:
10575       /* POP Fabien */
10576         if (appData.debugMode)
10577           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10578         fromX = currentMoveString[0] - AAA;
10579         fromY = currentMoveString[1] - ONE;
10580         toX = currentMoveString[2] - AAA;
10581         toY = currentMoveString[3] - ONE;
10582         promoChar = currentMoveString[4];
10583         break;
10584
10585       case WhiteDrop:
10586       case BlackDrop:
10587         if (appData.debugMode)
10588           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10589         fromX = moveType == WhiteDrop ?
10590           (int) CharToPiece(ToUpper(currentMoveString[0])) :
10591         (int) CharToPiece(ToLower(currentMoveString[0]));
10592         fromY = DROP_RANK;
10593         toX = currentMoveString[2] - AAA;
10594         toY = currentMoveString[3] - ONE;
10595         break;
10596
10597       case WhiteWins:
10598       case BlackWins:
10599       case GameIsDrawn:
10600       case GameUnfinished:
10601         if (appData.debugMode)
10602           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10603         p = strchr(yy_text, '{');
10604         if (p == NULL) p = strchr(yy_text, '(');
10605         if (p == NULL) {
10606             p = yy_text;
10607             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10608         } else {
10609             q = strchr(p, *p == '{' ? '}' : ')');
10610             if (q != NULL) *q = NULLCHAR;
10611             p++;
10612         }
10613         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10614         GameEnds(moveType, p, GE_FILE);
10615         done = TRUE;
10616         if (cmailMsgLoaded) {
10617             ClearHighlights();
10618             flipView = WhiteOnMove(currentMove);
10619             if (moveType == GameUnfinished) flipView = !flipView;
10620             if (appData.debugMode)
10621               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10622         }
10623         break;
10624
10625       case EndOfFile:
10626         if (appData.debugMode)
10627           fprintf(debugFP, "Parser hit end of file\n");
10628         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10629           case MT_NONE:
10630           case MT_CHECK:
10631             break;
10632           case MT_CHECKMATE:
10633           case MT_STAINMATE:
10634             if (WhiteOnMove(currentMove)) {
10635                 GameEnds(BlackWins, "Black mates", GE_FILE);
10636             } else {
10637                 GameEnds(WhiteWins, "White mates", GE_FILE);
10638             }
10639             break;
10640           case MT_STALEMATE:
10641             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10642             break;
10643         }
10644         done = TRUE;
10645         break;
10646
10647       case MoveNumberOne:
10648         if (lastLoadGameStart == GNUChessGame) {
10649             /* GNUChessGames have numbers, but they aren't move numbers */
10650             if (appData.debugMode)
10651               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10652                       yy_text, (int) moveType);
10653             return LoadGameOneMove(EndOfFile); /* tail recursion */
10654         }
10655         /* else fall thru */
10656
10657       case XBoardGame:
10658       case GNUChessGame:
10659       case PGNTag:
10660         /* Reached start of next game in file */
10661         if (appData.debugMode)
10662           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10663         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10664           case MT_NONE:
10665           case MT_CHECK:
10666             break;
10667           case MT_CHECKMATE:
10668           case MT_STAINMATE:
10669             if (WhiteOnMove(currentMove)) {
10670                 GameEnds(BlackWins, "Black mates", GE_FILE);
10671             } else {
10672                 GameEnds(WhiteWins, "White mates", GE_FILE);
10673             }
10674             break;
10675           case MT_STALEMATE:
10676             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10677             break;
10678         }
10679         done = TRUE;
10680         break;
10681
10682       case PositionDiagram:     /* should not happen; ignore */
10683       case ElapsedTime:         /* ignore */
10684       case NAG:                 /* ignore */
10685         if (appData.debugMode)
10686           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10687                   yy_text, (int) moveType);
10688         return LoadGameOneMove(EndOfFile); /* tail recursion */
10689
10690       case IllegalMove:
10691         if (appData.testLegality) {
10692             if (appData.debugMode)
10693               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10694             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10695                     (forwardMostMove / 2) + 1,
10696                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10697             DisplayError(move, 0);
10698             done = TRUE;
10699         } else {
10700             if (appData.debugMode)
10701               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10702                       yy_text, currentMoveString);
10703             fromX = currentMoveString[0] - AAA;
10704             fromY = currentMoveString[1] - ONE;
10705             toX = currentMoveString[2] - AAA;
10706             toY = currentMoveString[3] - ONE;
10707             promoChar = currentMoveString[4];
10708         }
10709         break;
10710
10711       case AmbiguousMove:
10712         if (appData.debugMode)
10713           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10714         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10715                 (forwardMostMove / 2) + 1,
10716                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10717         DisplayError(move, 0);
10718         done = TRUE;
10719         break;
10720
10721       default:
10722       case ImpossibleMove:
10723         if (appData.debugMode)
10724           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10725         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10726                 (forwardMostMove / 2) + 1,
10727                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10728         DisplayError(move, 0);
10729         done = TRUE;
10730         break;
10731     }
10732
10733     if (done) {
10734         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10735             DrawPosition(FALSE, boards[currentMove]);
10736             DisplayBothClocks();
10737             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10738               DisplayComment(currentMove - 1, commentList[currentMove]);
10739         }
10740         (void) StopLoadGameTimer();
10741         gameFileFP = NULL;
10742         cmailOldMove = forwardMostMove;
10743         return FALSE;
10744     } else {
10745         /* currentMoveString is set as a side-effect of yylex */
10746
10747         thinkOutput[0] = NULLCHAR;
10748         MakeMove(fromX, fromY, toX, toY, promoChar);
10749         currentMove = forwardMostMove;
10750         return TRUE;
10751     }
10752 }
10753
10754 /* Load the nth game from the given file */
10755 int
10756 LoadGameFromFile(filename, n, title, useList)
10757      char *filename;
10758      int n;
10759      char *title;
10760      /*Boolean*/ int useList;
10761 {
10762     FILE *f;
10763     char buf[MSG_SIZ];
10764
10765     if (strcmp(filename, "-") == 0) {
10766         f = stdin;
10767         title = "stdin";
10768     } else {
10769         f = fopen(filename, "rb");
10770         if (f == NULL) {
10771           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10772             DisplayError(buf, errno);
10773             return FALSE;
10774         }
10775     }
10776     if (fseek(f, 0, 0) == -1) {
10777         /* f is not seekable; probably a pipe */
10778         useList = FALSE;
10779     }
10780     if (useList && n == 0) {
10781         int error = GameListBuild(f);
10782         if (error) {
10783             DisplayError(_("Cannot build game list"), error);
10784         } else if (!ListEmpty(&gameList) &&
10785                    ((ListGame *) gameList.tailPred)->number > 1) {
10786             GameListPopUp(f, title);
10787             return TRUE;
10788         }
10789         GameListDestroy();
10790         n = 1;
10791     }
10792     if (n == 0) n = 1;
10793     return LoadGame(f, n, title, FALSE);
10794 }
10795
10796
10797 void
10798 MakeRegisteredMove()
10799 {
10800     int fromX, fromY, toX, toY;
10801     char promoChar;
10802     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10803         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10804           case CMAIL_MOVE:
10805           case CMAIL_DRAW:
10806             if (appData.debugMode)
10807               fprintf(debugFP, "Restoring %s for game %d\n",
10808                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10809
10810             thinkOutput[0] = NULLCHAR;
10811             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10812             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10813             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10814             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10815             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10816             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10817             MakeMove(fromX, fromY, toX, toY, promoChar);
10818             ShowMove(fromX, fromY, toX, toY);
10819
10820             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10821               case MT_NONE:
10822               case MT_CHECK:
10823                 break;
10824
10825               case MT_CHECKMATE:
10826               case MT_STAINMATE:
10827                 if (WhiteOnMove(currentMove)) {
10828                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10829                 } else {
10830                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10831                 }
10832                 break;
10833
10834               case MT_STALEMATE:
10835                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10836                 break;
10837             }
10838
10839             break;
10840
10841           case CMAIL_RESIGN:
10842             if (WhiteOnMove(currentMove)) {
10843                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10844             } else {
10845                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10846             }
10847             break;
10848
10849           case CMAIL_ACCEPT:
10850             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10851             break;
10852
10853           default:
10854             break;
10855         }
10856     }
10857
10858     return;
10859 }
10860
10861 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10862 int
10863 CmailLoadGame(f, gameNumber, title, useList)
10864      FILE *f;
10865      int gameNumber;
10866      char *title;
10867      int useList;
10868 {
10869     int retVal;
10870
10871     if (gameNumber > nCmailGames) {
10872         DisplayError(_("No more games in this message"), 0);
10873         return FALSE;
10874     }
10875     if (f == lastLoadGameFP) {
10876         int offset = gameNumber - lastLoadGameNumber;
10877         if (offset == 0) {
10878             cmailMsg[0] = NULLCHAR;
10879             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10880                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10881                 nCmailMovesRegistered--;
10882             }
10883             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10884             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10885                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10886             }
10887         } else {
10888             if (! RegisterMove()) return FALSE;
10889         }
10890     }
10891
10892     retVal = LoadGame(f, gameNumber, title, useList);
10893
10894     /* Make move registered during previous look at this game, if any */
10895     MakeRegisteredMove();
10896
10897     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10898         commentList[currentMove]
10899           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10900         DisplayComment(currentMove - 1, commentList[currentMove]);
10901     }
10902
10903     return retVal;
10904 }
10905
10906 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10907 int
10908 ReloadGame(offset)
10909      int offset;
10910 {
10911     int gameNumber = lastLoadGameNumber + offset;
10912     if (lastLoadGameFP == NULL) {
10913         DisplayError(_("No game has been loaded yet"), 0);
10914         return FALSE;
10915     }
10916     if (gameNumber <= 0) {
10917         DisplayError(_("Can't back up any further"), 0);
10918         return FALSE;
10919     }
10920     if (cmailMsgLoaded) {
10921         return CmailLoadGame(lastLoadGameFP, gameNumber,
10922                              lastLoadGameTitle, lastLoadGameUseList);
10923     } else {
10924         return LoadGame(lastLoadGameFP, gameNumber,
10925                         lastLoadGameTitle, lastLoadGameUseList);
10926     }
10927 }
10928
10929
10930
10931 /* Load the nth game from open file f */
10932 int
10933 LoadGame(f, gameNumber, title, useList)
10934      FILE *f;
10935      int gameNumber;
10936      char *title;
10937      int useList;
10938 {
10939     ChessMove cm;
10940     char buf[MSG_SIZ];
10941     int gn = gameNumber;
10942     ListGame *lg = NULL;
10943     int numPGNTags = 0;
10944     int err;
10945     GameMode oldGameMode;
10946     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10947
10948     if (appData.debugMode)
10949         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10950
10951     if (gameMode == Training )
10952         SetTrainingModeOff();
10953
10954     oldGameMode = gameMode;
10955     if (gameMode != BeginningOfGame) {
10956       Reset(FALSE, TRUE);
10957     }
10958
10959     gameFileFP = f;
10960     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10961         fclose(lastLoadGameFP);
10962     }
10963
10964     if (useList) {
10965         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10966
10967         if (lg) {
10968             fseek(f, lg->offset, 0);
10969             GameListHighlight(gameNumber);
10970             gn = 1;
10971         }
10972         else {
10973             DisplayError(_("Game number out of range"), 0);
10974             return FALSE;
10975         }
10976     } else {
10977         GameListDestroy();
10978         if (fseek(f, 0, 0) == -1) {
10979             if (f == lastLoadGameFP ?
10980                 gameNumber == lastLoadGameNumber + 1 :
10981                 gameNumber == 1) {
10982                 gn = 1;
10983             } else {
10984                 DisplayError(_("Can't seek on game file"), 0);
10985                 return FALSE;
10986             }
10987         }
10988     }
10989     lastLoadGameFP = f;
10990     lastLoadGameNumber = gameNumber;
10991     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10992     lastLoadGameUseList = useList;
10993
10994     yynewfile(f);
10995
10996     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10997       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10998                 lg->gameInfo.black);
10999             DisplayTitle(buf);
11000     } else if (*title != NULLCHAR) {
11001         if (gameNumber > 1) {
11002           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11003             DisplayTitle(buf);
11004         } else {
11005             DisplayTitle(title);
11006         }
11007     }
11008
11009     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11010         gameMode = PlayFromGameFile;
11011         ModeHighlight();
11012     }
11013
11014     currentMove = forwardMostMove = backwardMostMove = 0;
11015     CopyBoard(boards[0], initialPosition);
11016     StopClocks();
11017
11018     /*
11019      * Skip the first gn-1 games in the file.
11020      * Also skip over anything that precedes an identifiable
11021      * start of game marker, to avoid being confused by
11022      * garbage at the start of the file.  Currently
11023      * recognized start of game markers are the move number "1",
11024      * the pattern "gnuchess .* game", the pattern
11025      * "^[#;%] [^ ]* game file", and a PGN tag block.
11026      * A game that starts with one of the latter two patterns
11027      * will also have a move number 1, possibly
11028      * following a position diagram.
11029      * 5-4-02: Let's try being more lenient and allowing a game to
11030      * start with an unnumbered move.  Does that break anything?
11031      */
11032     cm = lastLoadGameStart = EndOfFile;
11033     while (gn > 0) {
11034         yyboardindex = forwardMostMove;
11035         cm = (ChessMove) Myylex();
11036         switch (cm) {
11037           case EndOfFile:
11038             if (cmailMsgLoaded) {
11039                 nCmailGames = CMAIL_MAX_GAMES - gn;
11040             } else {
11041                 Reset(TRUE, TRUE);
11042                 DisplayError(_("Game not found in file"), 0);
11043             }
11044             return FALSE;
11045
11046           case GNUChessGame:
11047           case XBoardGame:
11048             gn--;
11049             lastLoadGameStart = cm;
11050             break;
11051
11052           case MoveNumberOne:
11053             switch (lastLoadGameStart) {
11054               case GNUChessGame:
11055               case XBoardGame:
11056               case PGNTag:
11057                 break;
11058               case MoveNumberOne:
11059               case EndOfFile:
11060                 gn--;           /* count this game */
11061                 lastLoadGameStart = cm;
11062                 break;
11063               default:
11064                 /* impossible */
11065                 break;
11066             }
11067             break;
11068
11069           case PGNTag:
11070             switch (lastLoadGameStart) {
11071               case GNUChessGame:
11072               case PGNTag:
11073               case MoveNumberOne:
11074               case EndOfFile:
11075                 gn--;           /* count this game */
11076                 lastLoadGameStart = cm;
11077                 break;
11078               case XBoardGame:
11079                 lastLoadGameStart = cm; /* game counted already */
11080                 break;
11081               default:
11082                 /* impossible */
11083                 break;
11084             }
11085             if (gn > 0) {
11086                 do {
11087                     yyboardindex = forwardMostMove;
11088                     cm = (ChessMove) Myylex();
11089                 } while (cm == PGNTag || cm == Comment);
11090             }
11091             break;
11092
11093           case WhiteWins:
11094           case BlackWins:
11095           case GameIsDrawn:
11096             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11097                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
11098                     != CMAIL_OLD_RESULT) {
11099                     nCmailResults ++ ;
11100                     cmailResult[  CMAIL_MAX_GAMES
11101                                 - gn - 1] = CMAIL_OLD_RESULT;
11102                 }
11103             }
11104             break;
11105
11106           case NormalMove:
11107             /* Only a NormalMove can be at the start of a game
11108              * without a position diagram. */
11109             if (lastLoadGameStart == EndOfFile ) {
11110               gn--;
11111               lastLoadGameStart = MoveNumberOne;
11112             }
11113             break;
11114
11115           default:
11116             break;
11117         }
11118     }
11119
11120     if (appData.debugMode)
11121       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11122
11123     if (cm == XBoardGame) {
11124         /* Skip any header junk before position diagram and/or move 1 */
11125         for (;;) {
11126             yyboardindex = forwardMostMove;
11127             cm = (ChessMove) Myylex();
11128
11129             if (cm == EndOfFile ||
11130                 cm == GNUChessGame || cm == XBoardGame) {
11131                 /* Empty game; pretend end-of-file and handle later */
11132                 cm = EndOfFile;
11133                 break;
11134             }
11135
11136             if (cm == MoveNumberOne || cm == PositionDiagram ||
11137                 cm == PGNTag || cm == Comment)
11138               break;
11139         }
11140     } else if (cm == GNUChessGame) {
11141         if (gameInfo.event != NULL) {
11142             free(gameInfo.event);
11143         }
11144         gameInfo.event = StrSave(yy_text);
11145     }
11146
11147     startedFromSetupPosition = FALSE;
11148     while (cm == PGNTag) {
11149         if (appData.debugMode)
11150           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11151         err = ParsePGNTag(yy_text, &gameInfo);
11152         if (!err) numPGNTags++;
11153
11154         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11155         if(gameInfo.variant != oldVariant) {
11156             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11157             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11158             InitPosition(TRUE);
11159             oldVariant = gameInfo.variant;
11160             if (appData.debugMode)
11161               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11162         }
11163
11164
11165         if (gameInfo.fen != NULL) {
11166           Board initial_position;
11167           startedFromSetupPosition = TRUE;
11168           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11169             Reset(TRUE, TRUE);
11170             DisplayError(_("Bad FEN position in file"), 0);
11171             return FALSE;
11172           }
11173           CopyBoard(boards[0], initial_position);
11174           if (blackPlaysFirst) {
11175             currentMove = forwardMostMove = backwardMostMove = 1;
11176             CopyBoard(boards[1], initial_position);
11177             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11178             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11179             timeRemaining[0][1] = whiteTimeRemaining;
11180             timeRemaining[1][1] = blackTimeRemaining;
11181             if (commentList[0] != NULL) {
11182               commentList[1] = commentList[0];
11183               commentList[0] = NULL;
11184             }
11185           } else {
11186             currentMove = forwardMostMove = backwardMostMove = 0;
11187           }
11188           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11189           {   int i;
11190               initialRulePlies = FENrulePlies;
11191               for( i=0; i< nrCastlingRights; i++ )
11192                   initialRights[i] = initial_position[CASTLING][i];
11193           }
11194           yyboardindex = forwardMostMove;
11195           free(gameInfo.fen);
11196           gameInfo.fen = NULL;
11197         }
11198
11199         yyboardindex = forwardMostMove;
11200         cm = (ChessMove) Myylex();
11201
11202         /* Handle comments interspersed among the tags */
11203         while (cm == Comment) {
11204             char *p;
11205             if (appData.debugMode)
11206               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11207             p = yy_text;
11208             AppendComment(currentMove, p, FALSE);
11209             yyboardindex = forwardMostMove;
11210             cm = (ChessMove) Myylex();
11211         }
11212     }
11213
11214     /* don't rely on existence of Event tag since if game was
11215      * pasted from clipboard the Event tag may not exist
11216      */
11217     if (numPGNTags > 0){
11218         char *tags;
11219         if (gameInfo.variant == VariantNormal) {
11220           VariantClass v = StringToVariant(gameInfo.event);
11221           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11222           if(v < VariantShogi) gameInfo.variant = v;
11223         }
11224         if (!matchMode) {
11225           if( appData.autoDisplayTags ) {
11226             tags = PGNTags(&gameInfo);
11227             TagsPopUp(tags, CmailMsg());
11228             free(tags);
11229           }
11230         }
11231     } else {
11232         /* Make something up, but don't display it now */
11233         SetGameInfo();
11234         TagsPopDown();
11235     }
11236
11237     if (cm == PositionDiagram) {
11238         int i, j;
11239         char *p;
11240         Board initial_position;
11241
11242         if (appData.debugMode)
11243           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11244
11245         if (!startedFromSetupPosition) {
11246             p = yy_text;
11247             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11248               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11249                 switch (*p) {
11250                   case '{':
11251                   case '[':
11252                   case '-':
11253                   case ' ':
11254                   case '\t':
11255                   case '\n':
11256                   case '\r':
11257                     break;
11258                   default:
11259                     initial_position[i][j++] = CharToPiece(*p);
11260                     break;
11261                 }
11262             while (*p == ' ' || *p == '\t' ||
11263                    *p == '\n' || *p == '\r') p++;
11264
11265             if (strncmp(p, "black", strlen("black"))==0)
11266               blackPlaysFirst = TRUE;
11267             else
11268               blackPlaysFirst = FALSE;
11269             startedFromSetupPosition = TRUE;
11270
11271             CopyBoard(boards[0], initial_position);
11272             if (blackPlaysFirst) {
11273                 currentMove = forwardMostMove = backwardMostMove = 1;
11274                 CopyBoard(boards[1], initial_position);
11275                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11276                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11277                 timeRemaining[0][1] = whiteTimeRemaining;
11278                 timeRemaining[1][1] = blackTimeRemaining;
11279                 if (commentList[0] != NULL) {
11280                     commentList[1] = commentList[0];
11281                     commentList[0] = NULL;
11282                 }
11283             } else {
11284                 currentMove = forwardMostMove = backwardMostMove = 0;
11285             }
11286         }
11287         yyboardindex = forwardMostMove;
11288         cm = (ChessMove) Myylex();
11289     }
11290
11291     if (first.pr == NoProc) {
11292         StartChessProgram(&first);
11293     }
11294     InitChessProgram(&first, FALSE);
11295     SendToProgram("force\n", &first);
11296     if (startedFromSetupPosition) {
11297         SendBoard(&first, forwardMostMove);
11298     if (appData.debugMode) {
11299         fprintf(debugFP, "Load Game\n");
11300     }
11301         DisplayBothClocks();
11302     }
11303
11304     /* [HGM] server: flag to write setup moves in broadcast file as one */
11305     loadFlag = appData.suppressLoadMoves;
11306
11307     while (cm == Comment) {
11308         char *p;
11309         if (appData.debugMode)
11310           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11311         p = yy_text;
11312         AppendComment(currentMove, p, FALSE);
11313         yyboardindex = forwardMostMove;
11314         cm = (ChessMove) Myylex();
11315     }
11316
11317     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11318         cm == WhiteWins || cm == BlackWins ||
11319         cm == GameIsDrawn || cm == GameUnfinished) {
11320         DisplayMessage("", _("No moves in game"));
11321         if (cmailMsgLoaded) {
11322             if (appData.debugMode)
11323               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11324             ClearHighlights();
11325             flipView = FALSE;
11326         }
11327         DrawPosition(FALSE, boards[currentMove]);
11328         DisplayBothClocks();
11329         gameMode = EditGame;
11330         ModeHighlight();
11331         gameFileFP = NULL;
11332         cmailOldMove = 0;
11333         return TRUE;
11334     }
11335
11336     // [HGM] PV info: routine tests if comment empty
11337     if (!matchMode && (pausing || appData.timeDelay != 0)) {
11338         DisplayComment(currentMove - 1, commentList[currentMove]);
11339     }
11340     if (!matchMode && appData.timeDelay != 0)
11341       DrawPosition(FALSE, boards[currentMove]);
11342
11343     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11344       programStats.ok_to_send = 1;
11345     }
11346
11347     /* if the first token after the PGN tags is a move
11348      * and not move number 1, retrieve it from the parser
11349      */
11350     if (cm != MoveNumberOne)
11351         LoadGameOneMove(cm);
11352
11353     /* load the remaining moves from the file */
11354     while (LoadGameOneMove(EndOfFile)) {
11355       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11356       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11357     }
11358
11359     /* rewind to the start of the game */
11360     currentMove = backwardMostMove;
11361
11362     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11363
11364     if (oldGameMode == AnalyzeFile ||
11365         oldGameMode == AnalyzeMode) {
11366       AnalyzeFileEvent();
11367     }
11368
11369     if (matchMode || appData.timeDelay == 0) {
11370       ToEndEvent();
11371       gameMode = EditGame;
11372       ModeHighlight();
11373     } else if (appData.timeDelay > 0) {
11374       AutoPlayGameLoop();
11375     }
11376
11377     if (appData.debugMode)
11378         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11379
11380     loadFlag = 0; /* [HGM] true game starts */
11381     return TRUE;
11382 }
11383
11384 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11385 int
11386 ReloadPosition(offset)
11387      int offset;
11388 {
11389     int positionNumber = lastLoadPositionNumber + offset;
11390     if (lastLoadPositionFP == NULL) {
11391         DisplayError(_("No position has been loaded yet"), 0);
11392         return FALSE;
11393     }
11394     if (positionNumber <= 0) {
11395         DisplayError(_("Can't back up any further"), 0);
11396         return FALSE;
11397     }
11398     return LoadPosition(lastLoadPositionFP, positionNumber,
11399                         lastLoadPositionTitle);
11400 }
11401
11402 /* Load the nth position from the given file */
11403 int
11404 LoadPositionFromFile(filename, n, title)
11405      char *filename;
11406      int n;
11407      char *title;
11408 {
11409     FILE *f;
11410     char buf[MSG_SIZ];
11411
11412     if (strcmp(filename, "-") == 0) {
11413         return LoadPosition(stdin, n, "stdin");
11414     } else {
11415         f = fopen(filename, "rb");
11416         if (f == NULL) {
11417             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11418             DisplayError(buf, errno);
11419             return FALSE;
11420         } else {
11421             return LoadPosition(f, n, title);
11422         }
11423     }
11424 }
11425
11426 /* Load the nth position from the given open file, and close it */
11427 int
11428 LoadPosition(f, positionNumber, title)
11429      FILE *f;
11430      int positionNumber;
11431      char *title;
11432 {
11433     char *p, line[MSG_SIZ];
11434     Board initial_position;
11435     int i, j, fenMode, pn;
11436
11437     if (gameMode == Training )
11438         SetTrainingModeOff();
11439
11440     if (gameMode != BeginningOfGame) {
11441         Reset(FALSE, TRUE);
11442     }
11443     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11444         fclose(lastLoadPositionFP);
11445     }
11446     if (positionNumber == 0) positionNumber = 1;
11447     lastLoadPositionFP = f;
11448     lastLoadPositionNumber = positionNumber;
11449     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11450     if (first.pr == NoProc) {
11451       StartChessProgram(&first);
11452       InitChessProgram(&first, FALSE);
11453     }
11454     pn = positionNumber;
11455     if (positionNumber < 0) {
11456         /* Negative position number means to seek to that byte offset */
11457         if (fseek(f, -positionNumber, 0) == -1) {
11458             DisplayError(_("Can't seek on position file"), 0);
11459             return FALSE;
11460         };
11461         pn = 1;
11462     } else {
11463         if (fseek(f, 0, 0) == -1) {
11464             if (f == lastLoadPositionFP ?
11465                 positionNumber == lastLoadPositionNumber + 1 :
11466                 positionNumber == 1) {
11467                 pn = 1;
11468             } else {
11469                 DisplayError(_("Can't seek on position file"), 0);
11470                 return FALSE;
11471             }
11472         }
11473     }
11474     /* See if this file is FEN or old-style xboard */
11475     if (fgets(line, MSG_SIZ, f) == NULL) {
11476         DisplayError(_("Position not found in file"), 0);
11477         return FALSE;
11478     }
11479     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11480     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11481
11482     if (pn >= 2) {
11483         if (fenMode || line[0] == '#') pn--;
11484         while (pn > 0) {
11485             /* skip positions before number pn */
11486             if (fgets(line, MSG_SIZ, f) == NULL) {
11487                 Reset(TRUE, TRUE);
11488                 DisplayError(_("Position not found in file"), 0);
11489                 return FALSE;
11490             }
11491             if (fenMode || line[0] == '#') pn--;
11492         }
11493     }
11494
11495     if (fenMode) {
11496         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11497             DisplayError(_("Bad FEN position in file"), 0);
11498             return FALSE;
11499         }
11500     } else {
11501         (void) fgets(line, MSG_SIZ, f);
11502         (void) fgets(line, MSG_SIZ, f);
11503
11504         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11505             (void) fgets(line, MSG_SIZ, f);
11506             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11507                 if (*p == ' ')
11508                   continue;
11509                 initial_position[i][j++] = CharToPiece(*p);
11510             }
11511         }
11512
11513         blackPlaysFirst = FALSE;
11514         if (!feof(f)) {
11515             (void) fgets(line, MSG_SIZ, f);
11516             if (strncmp(line, "black", strlen("black"))==0)
11517               blackPlaysFirst = TRUE;
11518         }
11519     }
11520     startedFromSetupPosition = TRUE;
11521
11522     SendToProgram("force\n", &first);
11523     CopyBoard(boards[0], initial_position);
11524     if (blackPlaysFirst) {
11525         currentMove = forwardMostMove = backwardMostMove = 1;
11526         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11527         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11528         CopyBoard(boards[1], initial_position);
11529         DisplayMessage("", _("Black to play"));
11530     } else {
11531         currentMove = forwardMostMove = backwardMostMove = 0;
11532         DisplayMessage("", _("White to play"));
11533     }
11534     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11535     SendBoard(&first, forwardMostMove);
11536     if (appData.debugMode) {
11537 int i, j;
11538   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11539   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11540         fprintf(debugFP, "Load Position\n");
11541     }
11542
11543     if (positionNumber > 1) {
11544       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11545         DisplayTitle(line);
11546     } else {
11547         DisplayTitle(title);
11548     }
11549     gameMode = EditGame;
11550     ModeHighlight();
11551     ResetClocks();
11552     timeRemaining[0][1] = whiteTimeRemaining;
11553     timeRemaining[1][1] = blackTimeRemaining;
11554     DrawPosition(FALSE, boards[currentMove]);
11555
11556     return TRUE;
11557 }
11558
11559
11560 void
11561 CopyPlayerNameIntoFileName(dest, src)
11562      char **dest, *src;
11563 {
11564     while (*src != NULLCHAR && *src != ',') {
11565         if (*src == ' ') {
11566             *(*dest)++ = '_';
11567             src++;
11568         } else {
11569             *(*dest)++ = *src++;
11570         }
11571     }
11572 }
11573
11574 char *DefaultFileName(ext)
11575      char *ext;
11576 {
11577     static char def[MSG_SIZ];
11578     char *p;
11579
11580     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11581         p = def;
11582         CopyPlayerNameIntoFileName(&p, gameInfo.white);
11583         *p++ = '-';
11584         CopyPlayerNameIntoFileName(&p, gameInfo.black);
11585         *p++ = '.';
11586         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11587     } else {
11588         def[0] = NULLCHAR;
11589     }
11590     return def;
11591 }
11592
11593 /* Save the current game to the given file */
11594 int
11595 SaveGameToFile(filename, append)
11596      char *filename;
11597      int append;
11598 {
11599     FILE *f;
11600     char buf[MSG_SIZ];
11601     int result;
11602
11603     if (strcmp(filename, "-") == 0) {
11604         return SaveGame(stdout, 0, NULL);
11605     } else {
11606         f = fopen(filename, append ? "a" : "w");
11607         if (f == NULL) {
11608             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11609             DisplayError(buf, errno);
11610             return FALSE;
11611         } else {
11612             safeStrCpy(buf, lastMsg, MSG_SIZ);
11613             DisplayMessage(_("Waiting for access to save file"), "");
11614             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11615             DisplayMessage(_("Saving game"), "");
11616             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11617             result = SaveGame(f, 0, NULL);
11618             DisplayMessage(buf, "");
11619             return result;
11620         }
11621     }
11622 }
11623
11624 char *
11625 SavePart(str)
11626      char *str;
11627 {
11628     static char buf[MSG_SIZ];
11629     char *p;
11630
11631     p = strchr(str, ' ');
11632     if (p == NULL) return str;
11633     strncpy(buf, str, p - str);
11634     buf[p - str] = NULLCHAR;
11635     return buf;
11636 }
11637
11638 #define PGN_MAX_LINE 75
11639
11640 #define PGN_SIDE_WHITE  0
11641 #define PGN_SIDE_BLACK  1
11642
11643 /* [AS] */
11644 static int FindFirstMoveOutOfBook( int side )
11645 {
11646     int result = -1;
11647
11648     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11649         int index = backwardMostMove;
11650         int has_book_hit = 0;
11651
11652         if( (index % 2) != side ) {
11653             index++;
11654         }
11655
11656         while( index < forwardMostMove ) {
11657             /* Check to see if engine is in book */
11658             int depth = pvInfoList[index].depth;
11659             int score = pvInfoList[index].score;
11660             int in_book = 0;
11661
11662             if( depth <= 2 ) {
11663                 in_book = 1;
11664             }
11665             else if( score == 0 && depth == 63 ) {
11666                 in_book = 1; /* Zappa */
11667             }
11668             else if( score == 2 && depth == 99 ) {
11669                 in_book = 1; /* Abrok */
11670             }
11671
11672             has_book_hit += in_book;
11673
11674             if( ! in_book ) {
11675                 result = index;
11676
11677                 break;
11678             }
11679
11680             index += 2;
11681         }
11682     }
11683
11684     return result;
11685 }
11686
11687 /* [AS] */
11688 void GetOutOfBookInfo( char * buf )
11689 {
11690     int oob[2];
11691     int i;
11692     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11693
11694     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11695     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11696
11697     *buf = '\0';
11698
11699     if( oob[0] >= 0 || oob[1] >= 0 ) {
11700         for( i=0; i<2; i++ ) {
11701             int idx = oob[i];
11702
11703             if( idx >= 0 ) {
11704                 if( i > 0 && oob[0] >= 0 ) {
11705                     strcat( buf, "   " );
11706                 }
11707
11708                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11709                 sprintf( buf+strlen(buf), "%s%.2f",
11710                     pvInfoList[idx].score >= 0 ? "+" : "",
11711                     pvInfoList[idx].score / 100.0 );
11712             }
11713         }
11714     }
11715 }
11716
11717 /* Save game in PGN style and close the file */
11718 int
11719 SaveGamePGN(f)
11720      FILE *f;
11721 {
11722     int i, offset, linelen, newblock;
11723     time_t tm;
11724 //    char *movetext;
11725     char numtext[32];
11726     int movelen, numlen, blank;
11727     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11728
11729     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11730
11731     tm = time((time_t *) NULL);
11732
11733     PrintPGNTags(f, &gameInfo);
11734
11735     if (backwardMostMove > 0 || startedFromSetupPosition) {
11736         char *fen = PositionToFEN(backwardMostMove, NULL);
11737         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11738         fprintf(f, "\n{--------------\n");
11739         PrintPosition(f, backwardMostMove);
11740         fprintf(f, "--------------}\n");
11741         free(fen);
11742     }
11743     else {
11744         /* [AS] Out of book annotation */
11745         if( appData.saveOutOfBookInfo ) {
11746             char buf[64];
11747
11748             GetOutOfBookInfo( buf );
11749
11750             if( buf[0] != '\0' ) {
11751                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11752             }
11753         }
11754
11755         fprintf(f, "\n");
11756     }
11757
11758     i = backwardMostMove;
11759     linelen = 0;
11760     newblock = TRUE;
11761
11762     while (i < forwardMostMove) {
11763         /* Print comments preceding this move */
11764         if (commentList[i] != NULL) {
11765             if (linelen > 0) fprintf(f, "\n");
11766             fprintf(f, "%s", commentList[i]);
11767             linelen = 0;
11768             newblock = TRUE;
11769         }
11770
11771         /* Format move number */
11772         if ((i % 2) == 0)
11773           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11774         else
11775           if (newblock)
11776             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11777           else
11778             numtext[0] = NULLCHAR;
11779
11780         numlen = strlen(numtext);
11781         newblock = FALSE;
11782
11783         /* Print move number */
11784         blank = linelen > 0 && numlen > 0;
11785         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11786             fprintf(f, "\n");
11787             linelen = 0;
11788             blank = 0;
11789         }
11790         if (blank) {
11791             fprintf(f, " ");
11792             linelen++;
11793         }
11794         fprintf(f, "%s", numtext);
11795         linelen += numlen;
11796
11797         /* Get move */
11798         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11799         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11800
11801         /* Print move */
11802         blank = linelen > 0 && movelen > 0;
11803         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11804             fprintf(f, "\n");
11805             linelen = 0;
11806             blank = 0;
11807         }
11808         if (blank) {
11809             fprintf(f, " ");
11810             linelen++;
11811         }
11812         fprintf(f, "%s", move_buffer);
11813         linelen += movelen;
11814
11815         /* [AS] Add PV info if present */
11816         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11817             /* [HGM] add time */
11818             char buf[MSG_SIZ]; int seconds;
11819
11820             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11821
11822             if( seconds <= 0)
11823               buf[0] = 0;
11824             else
11825               if( seconds < 30 )
11826                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11827               else
11828                 {
11829                   seconds = (seconds + 4)/10; // round to full seconds
11830                   if( seconds < 60 )
11831                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11832                   else
11833                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11834                 }
11835
11836             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11837                       pvInfoList[i].score >= 0 ? "+" : "",
11838                       pvInfoList[i].score / 100.0,
11839                       pvInfoList[i].depth,
11840                       buf );
11841
11842             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11843
11844             /* Print score/depth */
11845             blank = linelen > 0 && movelen > 0;
11846             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11847                 fprintf(f, "\n");
11848                 linelen = 0;
11849                 blank = 0;
11850             }
11851             if (blank) {
11852                 fprintf(f, " ");
11853                 linelen++;
11854             }
11855             fprintf(f, "%s", move_buffer);
11856             linelen += movelen;
11857         }
11858
11859         i++;
11860     }
11861
11862     /* Start a new line */
11863     if (linelen > 0) fprintf(f, "\n");
11864
11865     /* Print comments after last move */
11866     if (commentList[i] != NULL) {
11867         fprintf(f, "%s\n", commentList[i]);
11868     }
11869
11870     /* Print result */
11871     if (gameInfo.resultDetails != NULL &&
11872         gameInfo.resultDetails[0] != NULLCHAR) {
11873         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11874                 PGNResult(gameInfo.result));
11875     } else {
11876         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11877     }
11878
11879     fclose(f);
11880     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11881     return TRUE;
11882 }
11883
11884 /* Save game in old style and close the file */
11885 int
11886 SaveGameOldStyle(f)
11887      FILE *f;
11888 {
11889     int i, offset;
11890     time_t tm;
11891
11892     tm = time((time_t *) NULL);
11893
11894     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11895     PrintOpponents(f);
11896
11897     if (backwardMostMove > 0 || startedFromSetupPosition) {
11898         fprintf(f, "\n[--------------\n");
11899         PrintPosition(f, backwardMostMove);
11900         fprintf(f, "--------------]\n");
11901     } else {
11902         fprintf(f, "\n");
11903     }
11904
11905     i = backwardMostMove;
11906     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11907
11908     while (i < forwardMostMove) {
11909         if (commentList[i] != NULL) {
11910             fprintf(f, "[%s]\n", commentList[i]);
11911         }
11912
11913         if ((i % 2) == 1) {
11914             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11915             i++;
11916         } else {
11917             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11918             i++;
11919             if (commentList[i] != NULL) {
11920                 fprintf(f, "\n");
11921                 continue;
11922             }
11923             if (i >= forwardMostMove) {
11924                 fprintf(f, "\n");
11925                 break;
11926             }
11927             fprintf(f, "%s\n", parseList[i]);
11928             i++;
11929         }
11930     }
11931
11932     if (commentList[i] != NULL) {
11933         fprintf(f, "[%s]\n", commentList[i]);
11934     }
11935
11936     /* This isn't really the old style, but it's close enough */
11937     if (gameInfo.resultDetails != NULL &&
11938         gameInfo.resultDetails[0] != NULLCHAR) {
11939         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11940                 gameInfo.resultDetails);
11941     } else {
11942         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11943     }
11944
11945     fclose(f);
11946     return TRUE;
11947 }
11948
11949 /* Save the current game to open file f and close the file */
11950 int
11951 SaveGame(f, dummy, dummy2)
11952      FILE *f;
11953      int dummy;
11954      char *dummy2;
11955 {
11956     if (gameMode == EditPosition) EditPositionDone(TRUE);
11957     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11958     if (appData.oldSaveStyle)
11959       return SaveGameOldStyle(f);
11960     else
11961       return SaveGamePGN(f);
11962 }
11963
11964 /* Save the current position to the given file */
11965 int
11966 SavePositionToFile(filename)
11967      char *filename;
11968 {
11969     FILE *f;
11970     char buf[MSG_SIZ];
11971
11972     if (strcmp(filename, "-") == 0) {
11973         return SavePosition(stdout, 0, NULL);
11974     } else {
11975         f = fopen(filename, "a");
11976         if (f == NULL) {
11977             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11978             DisplayError(buf, errno);
11979             return FALSE;
11980         } else {
11981             safeStrCpy(buf, lastMsg, MSG_SIZ);
11982             DisplayMessage(_("Waiting for access to save file"), "");
11983             flock(fileno(f), LOCK_EX); // [HGM] lock
11984             DisplayMessage(_("Saving position"), "");
11985             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11986             SavePosition(f, 0, NULL);
11987             DisplayMessage(buf, "");
11988             return TRUE;
11989         }
11990     }
11991 }
11992
11993 /* Save the current position to the given open file and close the file */
11994 int
11995 SavePosition(f, dummy, dummy2)
11996      FILE *f;
11997      int dummy;
11998      char *dummy2;
11999 {
12000     time_t tm;
12001     char *fen;
12002
12003     if (gameMode == EditPosition) EditPositionDone(TRUE);
12004     if (appData.oldSaveStyle) {
12005         tm = time((time_t *) NULL);
12006
12007         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12008         PrintOpponents(f);
12009         fprintf(f, "[--------------\n");
12010         PrintPosition(f, currentMove);
12011         fprintf(f, "--------------]\n");
12012     } else {
12013         fen = PositionToFEN(currentMove, NULL);
12014         fprintf(f, "%s\n", fen);
12015         free(fen);
12016     }
12017     fclose(f);
12018     return TRUE;
12019 }
12020
12021 void
12022 ReloadCmailMsgEvent(unregister)
12023      int unregister;
12024 {
12025 #if !WIN32
12026     static char *inFilename = NULL;
12027     static char *outFilename;
12028     int i;
12029     struct stat inbuf, outbuf;
12030     int status;
12031
12032     /* Any registered moves are unregistered if unregister is set, */
12033     /* i.e. invoked by the signal handler */
12034     if (unregister) {
12035         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12036             cmailMoveRegistered[i] = FALSE;
12037             if (cmailCommentList[i] != NULL) {
12038                 free(cmailCommentList[i]);
12039                 cmailCommentList[i] = NULL;
12040             }
12041         }
12042         nCmailMovesRegistered = 0;
12043     }
12044
12045     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12046         cmailResult[i] = CMAIL_NOT_RESULT;
12047     }
12048     nCmailResults = 0;
12049
12050     if (inFilename == NULL) {
12051         /* Because the filenames are static they only get malloced once  */
12052         /* and they never get freed                                      */
12053         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12054         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12055
12056         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12057         sprintf(outFilename, "%s.out", appData.cmailGameName);
12058     }
12059
12060     status = stat(outFilename, &outbuf);
12061     if (status < 0) {
12062         cmailMailedMove = FALSE;
12063     } else {
12064         status = stat(inFilename, &inbuf);
12065         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12066     }
12067
12068     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12069        counts the games, notes how each one terminated, etc.
12070
12071        It would be nice to remove this kludge and instead gather all
12072        the information while building the game list.  (And to keep it
12073        in the game list nodes instead of having a bunch of fixed-size
12074        parallel arrays.)  Note this will require getting each game's
12075        termination from the PGN tags, as the game list builder does
12076        not process the game moves.  --mann
12077        */
12078     cmailMsgLoaded = TRUE;
12079     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12080
12081     /* Load first game in the file or popup game menu */
12082     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12083
12084 #endif /* !WIN32 */
12085     return;
12086 }
12087
12088 int
12089 RegisterMove()
12090 {
12091     FILE *f;
12092     char string[MSG_SIZ];
12093
12094     if (   cmailMailedMove
12095         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12096         return TRUE;            /* Allow free viewing  */
12097     }
12098
12099     /* Unregister move to ensure that we don't leave RegisterMove        */
12100     /* with the move registered when the conditions for registering no   */
12101     /* longer hold                                                       */
12102     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12103         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12104         nCmailMovesRegistered --;
12105
12106         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12107           {
12108               free(cmailCommentList[lastLoadGameNumber - 1]);
12109               cmailCommentList[lastLoadGameNumber - 1] = NULL;
12110           }
12111     }
12112
12113     if (cmailOldMove == -1) {
12114         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12115         return FALSE;
12116     }
12117
12118     if (currentMove > cmailOldMove + 1) {
12119         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12120         return FALSE;
12121     }
12122
12123     if (currentMove < cmailOldMove) {
12124         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12125         return FALSE;
12126     }
12127
12128     if (forwardMostMove > currentMove) {
12129         /* Silently truncate extra moves */
12130         TruncateGame();
12131     }
12132
12133     if (   (currentMove == cmailOldMove + 1)
12134         || (   (currentMove == cmailOldMove)
12135             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12136                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12137         if (gameInfo.result != GameUnfinished) {
12138             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12139         }
12140
12141         if (commentList[currentMove] != NULL) {
12142             cmailCommentList[lastLoadGameNumber - 1]
12143               = StrSave(commentList[currentMove]);
12144         }
12145         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12146
12147         if (appData.debugMode)
12148           fprintf(debugFP, "Saving %s for game %d\n",
12149                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12150
12151         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12152
12153         f = fopen(string, "w");
12154         if (appData.oldSaveStyle) {
12155             SaveGameOldStyle(f); /* also closes the file */
12156
12157             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12158             f = fopen(string, "w");
12159             SavePosition(f, 0, NULL); /* also closes the file */
12160         } else {
12161             fprintf(f, "{--------------\n");
12162             PrintPosition(f, currentMove);
12163             fprintf(f, "--------------}\n\n");
12164
12165             SaveGame(f, 0, NULL); /* also closes the file*/
12166         }
12167
12168         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12169         nCmailMovesRegistered ++;
12170     } else if (nCmailGames == 1) {
12171         DisplayError(_("You have not made a move yet"), 0);
12172         return FALSE;
12173     }
12174
12175     return TRUE;
12176 }
12177
12178 void
12179 MailMoveEvent()
12180 {
12181 #if !WIN32
12182     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12183     FILE *commandOutput;
12184     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12185     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
12186     int nBuffers;
12187     int i;
12188     int archived;
12189     char *arcDir;
12190
12191     if (! cmailMsgLoaded) {
12192         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12193         return;
12194     }
12195
12196     if (nCmailGames == nCmailResults) {
12197         DisplayError(_("No unfinished games"), 0);
12198         return;
12199     }
12200
12201 #if CMAIL_PROHIBIT_REMAIL
12202     if (cmailMailedMove) {
12203       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);
12204         DisplayError(msg, 0);
12205         return;
12206     }
12207 #endif
12208
12209     if (! (cmailMailedMove || RegisterMove())) return;
12210
12211     if (   cmailMailedMove
12212         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12213       snprintf(string, MSG_SIZ, partCommandString,
12214                appData.debugMode ? " -v" : "", appData.cmailGameName);
12215         commandOutput = popen(string, "r");
12216
12217         if (commandOutput == NULL) {
12218             DisplayError(_("Failed to invoke cmail"), 0);
12219         } else {
12220             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12221                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12222             }
12223             if (nBuffers > 1) {
12224                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12225                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12226                 nBytes = MSG_SIZ - 1;
12227             } else {
12228                 (void) memcpy(msg, buffer, nBytes);
12229             }
12230             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12231
12232             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12233                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
12234
12235                 archived = TRUE;
12236                 for (i = 0; i < nCmailGames; i ++) {
12237                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
12238                         archived = FALSE;
12239                     }
12240                 }
12241                 if (   archived
12242                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12243                         != NULL)) {
12244                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12245                            arcDir,
12246                            appData.cmailGameName,
12247                            gameInfo.date);
12248                     LoadGameFromFile(buffer, 1, buffer, FALSE);
12249                     cmailMsgLoaded = FALSE;
12250                 }
12251             }
12252
12253             DisplayInformation(msg);
12254             pclose(commandOutput);
12255         }
12256     } else {
12257         if ((*cmailMsg) != '\0') {
12258             DisplayInformation(cmailMsg);
12259         }
12260     }
12261
12262     return;
12263 #endif /* !WIN32 */
12264 }
12265
12266 char *
12267 CmailMsg()
12268 {
12269 #if WIN32
12270     return NULL;
12271 #else
12272     int  prependComma = 0;
12273     char number[5];
12274     char string[MSG_SIZ];       /* Space for game-list */
12275     int  i;
12276
12277     if (!cmailMsgLoaded) return "";
12278
12279     if (cmailMailedMove) {
12280       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12281     } else {
12282         /* Create a list of games left */
12283       snprintf(string, MSG_SIZ, "[");
12284         for (i = 0; i < nCmailGames; i ++) {
12285             if (! (   cmailMoveRegistered[i]
12286                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12287                 if (prependComma) {
12288                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12289                 } else {
12290                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12291                     prependComma = 1;
12292                 }
12293
12294                 strcat(string, number);
12295             }
12296         }
12297         strcat(string, "]");
12298
12299         if (nCmailMovesRegistered + nCmailResults == 0) {
12300             switch (nCmailGames) {
12301               case 1:
12302                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12303                 break;
12304
12305               case 2:
12306                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12307                 break;
12308
12309               default:
12310                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12311                          nCmailGames);
12312                 break;
12313             }
12314         } else {
12315             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12316               case 1:
12317                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12318                          string);
12319                 break;
12320
12321               case 0:
12322                 if (nCmailResults == nCmailGames) {
12323                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12324                 } else {
12325                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12326                 }
12327                 break;
12328
12329               default:
12330                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12331                          string);
12332             }
12333         }
12334     }
12335     return cmailMsg;
12336 #endif /* WIN32 */
12337 }
12338
12339 void
12340 ResetGameEvent()
12341 {
12342     if (gameMode == Training)
12343       SetTrainingModeOff();
12344
12345     Reset(TRUE, TRUE);
12346     cmailMsgLoaded = FALSE;
12347     if (appData.icsActive) {
12348       SendToICS(ics_prefix);
12349       SendToICS("refresh\n");
12350     }
12351 }
12352
12353 void
12354 ExitEvent(status)
12355      int status;
12356 {
12357     exiting++;
12358     if (exiting > 2) {
12359       /* Give up on clean exit */
12360       exit(status);
12361     }
12362     if (exiting > 1) {
12363       /* Keep trying for clean exit */
12364       return;
12365     }
12366
12367     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12368
12369     if (telnetISR != NULL) {
12370       RemoveInputSource(telnetISR);
12371     }
12372     if (icsPR != NoProc) {
12373       DestroyChildProcess(icsPR, TRUE);
12374     }
12375
12376     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12377     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12378
12379     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12380     /* make sure this other one finishes before killing it!                  */
12381     if(endingGame) { int count = 0;
12382         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12383         while(endingGame && count++ < 10) DoSleep(1);
12384         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12385     }
12386
12387     /* Kill off chess programs */
12388     if (first.pr != NoProc) {
12389         ExitAnalyzeMode();
12390
12391         DoSleep( appData.delayBeforeQuit );
12392         SendToProgram("quit\n", &first);
12393         DoSleep( appData.delayAfterQuit );
12394         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12395     }
12396     if (second.pr != NoProc) {
12397         DoSleep( appData.delayBeforeQuit );
12398         SendToProgram("quit\n", &second);
12399         DoSleep( appData.delayAfterQuit );
12400         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12401     }
12402     if (first.isr != NULL) {
12403         RemoveInputSource(first.isr);
12404     }
12405     if (second.isr != NULL) {
12406         RemoveInputSource(second.isr);
12407     }
12408
12409     if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12410     if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12411
12412     ShutDownFrontEnd();
12413     exit(status);
12414 }
12415
12416 void
12417 PauseEvent()
12418 {
12419     if (appData.debugMode)
12420         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12421     if (pausing) {
12422         pausing = FALSE;
12423         ModeHighlight();
12424         if (gameMode == MachinePlaysWhite ||
12425             gameMode == MachinePlaysBlack) {
12426             StartClocks();
12427         } else {
12428             DisplayBothClocks();
12429         }
12430         if (gameMode == PlayFromGameFile) {
12431             if (appData.timeDelay >= 0)
12432                 AutoPlayGameLoop();
12433         } else if (gameMode == IcsExamining && pauseExamInvalid) {
12434             Reset(FALSE, TRUE);
12435             SendToICS(ics_prefix);
12436             SendToICS("refresh\n");
12437         } else if (currentMove < forwardMostMove) {
12438             ForwardInner(forwardMostMove);
12439         }
12440         pauseExamInvalid = FALSE;
12441     } else {
12442         switch (gameMode) {
12443           default:
12444             return;
12445           case IcsExamining:
12446             pauseExamForwardMostMove = forwardMostMove;
12447             pauseExamInvalid = FALSE;
12448             /* fall through */
12449           case IcsObserving:
12450           case IcsPlayingWhite:
12451           case IcsPlayingBlack:
12452             pausing = TRUE;
12453             ModeHighlight();
12454             return;
12455           case PlayFromGameFile:
12456             (void) StopLoadGameTimer();
12457             pausing = TRUE;
12458             ModeHighlight();
12459             break;
12460           case BeginningOfGame:
12461             if (appData.icsActive) return;
12462             /* else fall through */
12463           case MachinePlaysWhite:
12464           case MachinePlaysBlack:
12465           case TwoMachinesPlay:
12466             if (forwardMostMove == 0)
12467               return;           /* don't pause if no one has moved */
12468             if ((gameMode == MachinePlaysWhite &&
12469                  !WhiteOnMove(forwardMostMove)) ||
12470                 (gameMode == MachinePlaysBlack &&
12471                  WhiteOnMove(forwardMostMove))) {
12472                 StopClocks();
12473             }
12474             pausing = TRUE;
12475             ModeHighlight();
12476             break;
12477         }
12478     }
12479 }
12480
12481 void
12482 EditCommentEvent()
12483 {
12484     char title[MSG_SIZ];
12485
12486     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12487       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12488     } else {
12489       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12490                WhiteOnMove(currentMove - 1) ? " " : ".. ",
12491                parseList[currentMove - 1]);
12492     }
12493
12494     EditCommentPopUp(currentMove, title, commentList[currentMove]);
12495 }
12496
12497
12498 void
12499 EditTagsEvent()
12500 {
12501     char *tags = PGNTags(&gameInfo);
12502     bookUp = FALSE;
12503     EditTagsPopUp(tags, NULL);
12504     free(tags);
12505 }
12506
12507 void
12508 AnalyzeModeEvent()
12509 {
12510     if (appData.noChessProgram || gameMode == AnalyzeMode)
12511       return;
12512
12513     if (gameMode != AnalyzeFile) {
12514         if (!appData.icsEngineAnalyze) {
12515                EditGameEvent();
12516                if (gameMode != EditGame) return;
12517         }
12518         ResurrectChessProgram();
12519         SendToProgram("analyze\n", &first);
12520         first.analyzing = TRUE;
12521         /*first.maybeThinking = TRUE;*/
12522         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12523         EngineOutputPopUp();
12524     }
12525     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12526     pausing = FALSE;
12527     ModeHighlight();
12528     SetGameInfo();
12529
12530     StartAnalysisClock();
12531     GetTimeMark(&lastNodeCountTime);
12532     lastNodeCount = 0;
12533 }
12534
12535 void
12536 AnalyzeFileEvent()
12537 {
12538     if (appData.noChessProgram || gameMode == AnalyzeFile)
12539       return;
12540
12541     if (gameMode != AnalyzeMode) {
12542         EditGameEvent();
12543         if (gameMode != EditGame) return;
12544         ResurrectChessProgram();
12545         SendToProgram("analyze\n", &first);
12546         first.analyzing = TRUE;
12547         /*first.maybeThinking = TRUE;*/
12548         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12549         EngineOutputPopUp();
12550     }
12551     gameMode = AnalyzeFile;
12552     pausing = FALSE;
12553     ModeHighlight();
12554     SetGameInfo();
12555
12556     StartAnalysisClock();
12557     GetTimeMark(&lastNodeCountTime);
12558     lastNodeCount = 0;
12559 }
12560
12561 void
12562 MachineWhiteEvent()
12563 {
12564     char buf[MSG_SIZ];
12565     char *bookHit = NULL;
12566
12567     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12568       return;
12569
12570
12571     if (gameMode == PlayFromGameFile ||
12572         gameMode == TwoMachinesPlay  ||
12573         gameMode == Training         ||
12574         gameMode == AnalyzeMode      ||
12575         gameMode == EndOfGame)
12576         EditGameEvent();
12577
12578     if (gameMode == EditPosition)
12579         EditPositionDone(TRUE);
12580
12581     if (!WhiteOnMove(currentMove)) {
12582         DisplayError(_("It is not White's turn"), 0);
12583         return;
12584     }
12585
12586     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12587       ExitAnalyzeMode();
12588
12589     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12590         gameMode == AnalyzeFile)
12591         TruncateGame();
12592
12593     ResurrectChessProgram();    /* in case it isn't running */
12594     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12595         gameMode = MachinePlaysWhite;
12596         ResetClocks();
12597     } else
12598     gameMode = MachinePlaysWhite;
12599     pausing = FALSE;
12600     ModeHighlight();
12601     SetGameInfo();
12602     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12603     DisplayTitle(buf);
12604     if (first.sendName) {
12605       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12606       SendToProgram(buf, &first);
12607     }
12608     if (first.sendTime) {
12609       if (first.useColors) {
12610         SendToProgram("black\n", &first); /*gnu kludge*/
12611       }
12612       SendTimeRemaining(&first, TRUE);
12613     }
12614     if (first.useColors) {
12615       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12616     }
12617     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12618     SetMachineThinkingEnables();
12619     first.maybeThinking = TRUE;
12620     StartClocks();
12621     firstMove = FALSE;
12622
12623     if (appData.autoFlipView && !flipView) {
12624       flipView = !flipView;
12625       DrawPosition(FALSE, NULL);
12626       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12627     }
12628
12629     if(bookHit) { // [HGM] book: simulate book reply
12630         static char bookMove[MSG_SIZ]; // a bit generous?
12631
12632         programStats.nodes = programStats.depth = programStats.time =
12633         programStats.score = programStats.got_only_move = 0;
12634         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12635
12636         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12637         strcat(bookMove, bookHit);
12638         HandleMachineMove(bookMove, &first);
12639     }
12640 }
12641
12642 void
12643 MachineBlackEvent()
12644 {
12645   char buf[MSG_SIZ];
12646   char *bookHit = NULL;
12647
12648     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12649         return;
12650
12651
12652     if (gameMode == PlayFromGameFile ||
12653         gameMode == TwoMachinesPlay  ||
12654         gameMode == Training         ||
12655         gameMode == AnalyzeMode      ||
12656         gameMode == EndOfGame)
12657         EditGameEvent();
12658
12659     if (gameMode == EditPosition)
12660         EditPositionDone(TRUE);
12661
12662     if (WhiteOnMove(currentMove)) {
12663         DisplayError(_("It is not Black's turn"), 0);
12664         return;
12665     }
12666
12667     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12668       ExitAnalyzeMode();
12669
12670     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12671         gameMode == AnalyzeFile)
12672         TruncateGame();
12673
12674     ResurrectChessProgram();    /* in case it isn't running */
12675     gameMode = MachinePlaysBlack;
12676     pausing = FALSE;
12677     ModeHighlight();
12678     SetGameInfo();
12679     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12680     DisplayTitle(buf);
12681     if (first.sendName) {
12682       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12683       SendToProgram(buf, &first);
12684     }
12685     if (first.sendTime) {
12686       if (first.useColors) {
12687         SendToProgram("white\n", &first); /*gnu kludge*/
12688       }
12689       SendTimeRemaining(&first, FALSE);
12690     }
12691     if (first.useColors) {
12692       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12693     }
12694     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12695     SetMachineThinkingEnables();
12696     first.maybeThinking = TRUE;
12697     StartClocks();
12698
12699     if (appData.autoFlipView && flipView) {
12700       flipView = !flipView;
12701       DrawPosition(FALSE, NULL);
12702       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12703     }
12704     if(bookHit) { // [HGM] book: simulate book reply
12705         static char bookMove[MSG_SIZ]; // a bit generous?
12706
12707         programStats.nodes = programStats.depth = programStats.time =
12708         programStats.score = programStats.got_only_move = 0;
12709         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12710
12711         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12712         strcat(bookMove, bookHit);
12713         HandleMachineMove(bookMove, &first);
12714     }
12715 }
12716
12717
12718 void
12719 DisplayTwoMachinesTitle()
12720 {
12721     char buf[MSG_SIZ];
12722     if (appData.matchGames > 0) {
12723         if(appData.tourneyFile[0]) {
12724           snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12725                    gameInfo.white, gameInfo.black,
12726                    nextGame+1, appData.matchGames+1,
12727                    appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12728         } else 
12729         if (first.twoMachinesColor[0] == 'w') {
12730           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12731                    gameInfo.white, gameInfo.black,
12732                    first.matchWins, second.matchWins,
12733                    matchGame - 1 - (first.matchWins + second.matchWins));
12734         } else {
12735           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12736                    gameInfo.white, gameInfo.black,
12737                    second.matchWins, first.matchWins,
12738                    matchGame - 1 - (first.matchWins + second.matchWins));
12739         }
12740     } else {
12741       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12742     }
12743     DisplayTitle(buf);
12744 }
12745
12746 void
12747 SettingsMenuIfReady()
12748 {
12749   if (second.lastPing != second.lastPong) {
12750     DisplayMessage("", _("Waiting for second chess program"));
12751     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12752     return;
12753   }
12754   ThawUI();
12755   DisplayMessage("", "");
12756   SettingsPopUp(&second);
12757 }
12758
12759 int
12760 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12761 {
12762     char buf[MSG_SIZ];
12763     if (cps->pr == NULL) {
12764         StartChessProgram(cps);
12765         if (cps->protocolVersion == 1) {
12766           retry();
12767         } else {
12768           /* kludge: allow timeout for initial "feature" command */
12769           FreezeUI();
12770           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12771           DisplayMessage("", buf);
12772           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12773         }
12774         return 1;
12775     }
12776     return 0;
12777 }
12778
12779 void
12780 TwoMachinesEvent P((void))
12781 {
12782     int i;
12783     char buf[MSG_SIZ];
12784     ChessProgramState *onmove;
12785     char *bookHit = NULL;
12786     static int stalling = 0;
12787     TimeMark now;
12788     long wait;
12789
12790     if (appData.noChessProgram) return;
12791
12792     switch (gameMode) {
12793       case TwoMachinesPlay:
12794         return;
12795       case MachinePlaysWhite:
12796       case MachinePlaysBlack:
12797         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12798             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12799             return;
12800         }
12801         /* fall through */
12802       case BeginningOfGame:
12803       case PlayFromGameFile:
12804       case EndOfGame:
12805         EditGameEvent();
12806         if (gameMode != EditGame) return;
12807         break;
12808       case EditPosition:
12809         EditPositionDone(TRUE);
12810         break;
12811       case AnalyzeMode:
12812       case AnalyzeFile:
12813         ExitAnalyzeMode();
12814         break;
12815       case EditGame:
12816       default:
12817         break;
12818     }
12819
12820 //    forwardMostMove = currentMove;
12821     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12822
12823     if(!ResurrectChessProgram()) return;   /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12824
12825     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12826     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12827       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12828       return;
12829     }
12830     if(!stalling) {
12831       InitChessProgram(&second, FALSE); // unbalances ping of second engine
12832       SendToProgram("force\n", &second);
12833       stalling = 1;
12834       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12835       return;
12836     }
12837     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12838     if(appData.matchPause>10000 || appData.matchPause<10)
12839                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12840     wait = SubtractTimeMarks(&now, &pauseStart);
12841     if(wait < appData.matchPause) {
12842         ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12843         return;
12844     }
12845     stalling = 0;
12846     DisplayMessage("", "");
12847     if (startedFromSetupPosition) {
12848         SendBoard(&second, backwardMostMove);
12849     if (appData.debugMode) {
12850         fprintf(debugFP, "Two Machines\n");
12851     }
12852     }
12853     for (i = backwardMostMove; i < forwardMostMove; i++) {
12854         SendMoveToProgram(i, &second);
12855     }
12856
12857     gameMode = TwoMachinesPlay;
12858     pausing = FALSE;
12859     ModeHighlight(); // [HGM] logo: this triggers display update of logos
12860     SetGameInfo();
12861     DisplayTwoMachinesTitle();
12862     firstMove = TRUE;
12863     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12864         onmove = &first;
12865     } else {
12866         onmove = &second;
12867     }
12868     if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12869     SendToProgram(first.computerString, &first);
12870     if (first.sendName) {
12871       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12872       SendToProgram(buf, &first);
12873     }
12874     SendToProgram(second.computerString, &second);
12875     if (second.sendName) {
12876       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12877       SendToProgram(buf, &second);
12878     }
12879
12880     ResetClocks();
12881     if (!first.sendTime || !second.sendTime) {
12882         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12883         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12884     }
12885     if (onmove->sendTime) {
12886       if (onmove->useColors) {
12887         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12888       }
12889       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12890     }
12891     if (onmove->useColors) {
12892       SendToProgram(onmove->twoMachinesColor, onmove);
12893     }
12894     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12895 //    SendToProgram("go\n", onmove);
12896     onmove->maybeThinking = TRUE;
12897     SetMachineThinkingEnables();
12898
12899     StartClocks();
12900
12901     if(bookHit) { // [HGM] book: simulate book reply
12902         static char bookMove[MSG_SIZ]; // a bit generous?
12903
12904         programStats.nodes = programStats.depth = programStats.time =
12905         programStats.score = programStats.got_only_move = 0;
12906         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12907
12908         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12909         strcat(bookMove, bookHit);
12910         savedMessage = bookMove; // args for deferred call
12911         savedState = onmove;
12912         ScheduleDelayedEvent(DeferredBookMove, 1);
12913     }
12914 }
12915
12916 void
12917 TrainingEvent()
12918 {
12919     if (gameMode == Training) {
12920       SetTrainingModeOff();
12921       gameMode = PlayFromGameFile;
12922       DisplayMessage("", _("Training mode off"));
12923     } else {
12924       gameMode = Training;
12925       animateTraining = appData.animate;
12926
12927       /* make sure we are not already at the end of the game */
12928       if (currentMove < forwardMostMove) {
12929         SetTrainingModeOn();
12930         DisplayMessage("", _("Training mode on"));
12931       } else {
12932         gameMode = PlayFromGameFile;
12933         DisplayError(_("Already at end of game"), 0);
12934       }
12935     }
12936     ModeHighlight();
12937 }
12938
12939 void
12940 IcsClientEvent()
12941 {
12942     if (!appData.icsActive) return;
12943     switch (gameMode) {
12944       case IcsPlayingWhite:
12945       case IcsPlayingBlack:
12946       case IcsObserving:
12947       case IcsIdle:
12948       case BeginningOfGame:
12949       case IcsExamining:
12950         return;
12951
12952       case EditGame:
12953         break;
12954
12955       case EditPosition:
12956         EditPositionDone(TRUE);
12957         break;
12958
12959       case AnalyzeMode:
12960       case AnalyzeFile:
12961         ExitAnalyzeMode();
12962         break;
12963
12964       default:
12965         EditGameEvent();
12966         break;
12967     }
12968
12969     gameMode = IcsIdle;
12970     ModeHighlight();
12971     return;
12972 }
12973
12974
12975 void
12976 EditGameEvent()
12977 {
12978     int i;
12979
12980     switch (gameMode) {
12981       case Training:
12982         SetTrainingModeOff();
12983         break;
12984       case MachinePlaysWhite:
12985       case MachinePlaysBlack:
12986       case BeginningOfGame:
12987         SendToProgram("force\n", &first);
12988         SetUserThinkingEnables();
12989         break;
12990       case PlayFromGameFile:
12991         (void) StopLoadGameTimer();
12992         if (gameFileFP != NULL) {
12993             gameFileFP = NULL;
12994         }
12995         break;
12996       case EditPosition:
12997         EditPositionDone(TRUE);
12998         break;
12999       case AnalyzeMode:
13000       case AnalyzeFile:
13001         ExitAnalyzeMode();
13002         SendToProgram("force\n", &first);
13003         break;
13004       case TwoMachinesPlay:
13005         GameEnds(EndOfFile, NULL, GE_PLAYER);
13006         ResurrectChessProgram();
13007         SetUserThinkingEnables();
13008         break;
13009       case EndOfGame:
13010         ResurrectChessProgram();
13011         break;
13012       case IcsPlayingBlack:
13013       case IcsPlayingWhite:
13014         DisplayError(_("Warning: You are still playing a game"), 0);
13015         break;
13016       case IcsObserving:
13017         DisplayError(_("Warning: You are still observing a game"), 0);
13018         break;
13019       case IcsExamining:
13020         DisplayError(_("Warning: You are still examining a game"), 0);
13021         break;
13022       case IcsIdle:
13023         break;
13024       case EditGame:
13025       default:
13026         return;
13027     }
13028
13029     pausing = FALSE;
13030     StopClocks();
13031     first.offeredDraw = second.offeredDraw = 0;
13032
13033     if (gameMode == PlayFromGameFile) {
13034         whiteTimeRemaining = timeRemaining[0][currentMove];
13035         blackTimeRemaining = timeRemaining[1][currentMove];
13036         DisplayTitle("");
13037     }
13038
13039     if (gameMode == MachinePlaysWhite ||
13040         gameMode == MachinePlaysBlack ||
13041         gameMode == TwoMachinesPlay ||
13042         gameMode == EndOfGame) {
13043         i = forwardMostMove;
13044         while (i > currentMove) {
13045             SendToProgram("undo\n", &first);
13046             i--;
13047         }
13048         whiteTimeRemaining = timeRemaining[0][currentMove];
13049         blackTimeRemaining = timeRemaining[1][currentMove];
13050         DisplayBothClocks();
13051         if (whiteFlag || blackFlag) {
13052             whiteFlag = blackFlag = 0;
13053         }
13054         DisplayTitle("");
13055     }
13056
13057     gameMode = EditGame;
13058     ModeHighlight();
13059     SetGameInfo();
13060 }
13061
13062
13063 void
13064 EditPositionEvent()
13065 {
13066     if (gameMode == EditPosition) {
13067         EditGameEvent();
13068         return;
13069     }
13070
13071     EditGameEvent();
13072     if (gameMode != EditGame) return;
13073
13074     gameMode = EditPosition;
13075     ModeHighlight();
13076     SetGameInfo();
13077     if (currentMove > 0)
13078       CopyBoard(boards[0], boards[currentMove]);
13079
13080     blackPlaysFirst = !WhiteOnMove(currentMove);
13081     ResetClocks();
13082     currentMove = forwardMostMove = backwardMostMove = 0;
13083     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13084     DisplayMove(-1);
13085 }
13086
13087 void
13088 ExitAnalyzeMode()
13089 {
13090     /* [DM] icsEngineAnalyze - possible call from other functions */
13091     if (appData.icsEngineAnalyze) {
13092         appData.icsEngineAnalyze = FALSE;
13093
13094         DisplayMessage("",_("Close ICS engine analyze..."));
13095     }
13096     if (first.analysisSupport && first.analyzing) {
13097       SendToProgram("exit\n", &first);
13098       first.analyzing = FALSE;
13099     }
13100     thinkOutput[0] = NULLCHAR;
13101 }
13102
13103 void
13104 EditPositionDone(Boolean fakeRights)
13105 {
13106     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13107
13108     startedFromSetupPosition = TRUE;
13109     InitChessProgram(&first, FALSE);
13110     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13111       boards[0][EP_STATUS] = EP_NONE;
13112       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13113     if(boards[0][0][BOARD_WIDTH>>1] == king) {
13114         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13115         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13116       } else boards[0][CASTLING][2] = NoRights;
13117     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13118         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13119         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13120       } else boards[0][CASTLING][5] = NoRights;
13121     }
13122     SendToProgram("force\n", &first);
13123     if (blackPlaysFirst) {
13124         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13125         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13126         currentMove = forwardMostMove = backwardMostMove = 1;
13127         CopyBoard(boards[1], boards[0]);
13128     } else {
13129         currentMove = forwardMostMove = backwardMostMove = 0;
13130     }
13131     SendBoard(&first, forwardMostMove);
13132     if (appData.debugMode) {
13133         fprintf(debugFP, "EditPosDone\n");
13134     }
13135     DisplayTitle("");
13136     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13137     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13138     gameMode = EditGame;
13139     ModeHighlight();
13140     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13141     ClearHighlights(); /* [AS] */
13142 }
13143
13144 /* Pause for `ms' milliseconds */
13145 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13146 void
13147 TimeDelay(ms)
13148      long ms;
13149 {
13150     TimeMark m1, m2;
13151
13152     GetTimeMark(&m1);
13153     do {
13154         GetTimeMark(&m2);
13155     } while (SubtractTimeMarks(&m2, &m1) < ms);
13156 }
13157
13158 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13159 void
13160 SendMultiLineToICS(buf)
13161      char *buf;
13162 {
13163     char temp[MSG_SIZ+1], *p;
13164     int len;
13165
13166     len = strlen(buf);
13167     if (len > MSG_SIZ)
13168       len = MSG_SIZ;
13169
13170     strncpy(temp, buf, len);
13171     temp[len] = 0;
13172
13173     p = temp;
13174     while (*p) {
13175         if (*p == '\n' || *p == '\r')
13176           *p = ' ';
13177         ++p;
13178     }
13179
13180     strcat(temp, "\n");
13181     SendToICS(temp);
13182     SendToPlayer(temp, strlen(temp));
13183 }
13184
13185 void
13186 SetWhiteToPlayEvent()
13187 {
13188     if (gameMode == EditPosition) {
13189         blackPlaysFirst = FALSE;
13190         DisplayBothClocks();    /* works because currentMove is 0 */
13191     } else if (gameMode == IcsExamining) {
13192         SendToICS(ics_prefix);
13193         SendToICS("tomove white\n");
13194     }
13195 }
13196
13197 void
13198 SetBlackToPlayEvent()
13199 {
13200     if (gameMode == EditPosition) {
13201         blackPlaysFirst = TRUE;
13202         currentMove = 1;        /* kludge */
13203         DisplayBothClocks();
13204         currentMove = 0;
13205     } else if (gameMode == IcsExamining) {
13206         SendToICS(ics_prefix);
13207         SendToICS("tomove black\n");
13208     }
13209 }
13210
13211 void
13212 EditPositionMenuEvent(selection, x, y)
13213      ChessSquare selection;
13214      int x, y;
13215 {
13216     char buf[MSG_SIZ];
13217     ChessSquare piece = boards[0][y][x];
13218
13219     if (gameMode != EditPosition && gameMode != IcsExamining) return;
13220
13221     switch (selection) {
13222       case ClearBoard:
13223         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13224             SendToICS(ics_prefix);
13225             SendToICS("bsetup clear\n");
13226         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13227             SendToICS(ics_prefix);
13228             SendToICS("clearboard\n");
13229         } else {
13230             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13231                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13232                 for (y = 0; y < BOARD_HEIGHT; y++) {
13233                     if (gameMode == IcsExamining) {
13234                         if (boards[currentMove][y][x] != EmptySquare) {
13235                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13236                                     AAA + x, ONE + y);
13237                             SendToICS(buf);
13238                         }
13239                     } else {
13240                         boards[0][y][x] = p;
13241                     }
13242                 }
13243             }
13244         }
13245         if (gameMode == EditPosition) {
13246             DrawPosition(FALSE, boards[0]);
13247         }
13248         break;
13249
13250       case WhitePlay:
13251         SetWhiteToPlayEvent();
13252         break;
13253
13254       case BlackPlay:
13255         SetBlackToPlayEvent();
13256         break;
13257
13258       case EmptySquare:
13259         if (gameMode == IcsExamining) {
13260             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13261             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13262             SendToICS(buf);
13263         } else {
13264             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13265                 if(x == BOARD_LEFT-2) {
13266                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13267                     boards[0][y][1] = 0;
13268                 } else
13269                 if(x == BOARD_RGHT+1) {
13270                     if(y >= gameInfo.holdingsSize) break;
13271                     boards[0][y][BOARD_WIDTH-2] = 0;
13272                 } else break;
13273             }
13274             boards[0][y][x] = EmptySquare;
13275             DrawPosition(FALSE, boards[0]);
13276         }
13277         break;
13278
13279       case PromotePiece:
13280         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13281            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
13282             selection = (ChessSquare) (PROMOTED piece);
13283         } else if(piece == EmptySquare) selection = WhiteSilver;
13284         else selection = (ChessSquare)((int)piece - 1);
13285         goto defaultlabel;
13286
13287       case DemotePiece:
13288         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13289            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
13290             selection = (ChessSquare) (DEMOTED piece);
13291         } else if(piece == EmptySquare) selection = BlackSilver;
13292         else selection = (ChessSquare)((int)piece + 1);
13293         goto defaultlabel;
13294
13295       case WhiteQueen:
13296       case BlackQueen:
13297         if(gameInfo.variant == VariantShatranj ||
13298            gameInfo.variant == VariantXiangqi  ||
13299            gameInfo.variant == VariantCourier  ||
13300            gameInfo.variant == VariantMakruk     )
13301             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13302         goto defaultlabel;
13303
13304       case WhiteKing:
13305       case BlackKing:
13306         if(gameInfo.variant == VariantXiangqi)
13307             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13308         if(gameInfo.variant == VariantKnightmate)
13309             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13310       default:
13311         defaultlabel:
13312         if (gameMode == IcsExamining) {
13313             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13314             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13315                      PieceToChar(selection), AAA + x, ONE + y);
13316             SendToICS(buf);
13317         } else {
13318             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13319                 int n;
13320                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13321                     n = PieceToNumber(selection - BlackPawn);
13322                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13323                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
13324                     boards[0][BOARD_HEIGHT-1-n][1]++;
13325                 } else
13326                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13327                     n = PieceToNumber(selection);
13328                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13329                     boards[0][n][BOARD_WIDTH-1] = selection;
13330                     boards[0][n][BOARD_WIDTH-2]++;
13331                 }
13332             } else
13333             boards[0][y][x] = selection;
13334             DrawPosition(TRUE, boards[0]);
13335         }
13336         break;
13337     }
13338 }
13339
13340
13341 void
13342 DropMenuEvent(selection, x, y)
13343      ChessSquare selection;
13344      int x, y;
13345 {
13346     ChessMove moveType;
13347
13348     switch (gameMode) {
13349       case IcsPlayingWhite:
13350       case MachinePlaysBlack:
13351         if (!WhiteOnMove(currentMove)) {
13352             DisplayMoveError(_("It is Black's turn"));
13353             return;
13354         }
13355         moveType = WhiteDrop;
13356         break;
13357       case IcsPlayingBlack:
13358       case MachinePlaysWhite:
13359         if (WhiteOnMove(currentMove)) {
13360             DisplayMoveError(_("It is White's turn"));
13361             return;
13362         }
13363         moveType = BlackDrop;
13364         break;
13365       case EditGame:
13366         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13367         break;
13368       default:
13369         return;
13370     }
13371
13372     if (moveType == BlackDrop && selection < BlackPawn) {
13373       selection = (ChessSquare) ((int) selection
13374                                  + (int) BlackPawn - (int) WhitePawn);
13375     }
13376     if (boards[currentMove][y][x] != EmptySquare) {
13377         DisplayMoveError(_("That square is occupied"));
13378         return;
13379     }
13380
13381     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13382 }
13383
13384 void
13385 AcceptEvent()
13386 {
13387     /* Accept a pending offer of any kind from opponent */
13388
13389     if (appData.icsActive) {
13390         SendToICS(ics_prefix);
13391         SendToICS("accept\n");
13392     } else if (cmailMsgLoaded) {
13393         if (currentMove == cmailOldMove &&
13394             commentList[cmailOldMove] != NULL &&
13395             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13396                    "Black offers a draw" : "White offers a draw")) {
13397             TruncateGame();
13398             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13399             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13400         } else {
13401             DisplayError(_("There is no pending offer on this move"), 0);
13402             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13403         }
13404     } else {
13405         /* Not used for offers from chess program */
13406     }
13407 }
13408
13409 void
13410 DeclineEvent()
13411 {
13412     /* Decline a pending offer of any kind from opponent */
13413
13414     if (appData.icsActive) {
13415         SendToICS(ics_prefix);
13416         SendToICS("decline\n");
13417     } else if (cmailMsgLoaded) {
13418         if (currentMove == cmailOldMove &&
13419             commentList[cmailOldMove] != NULL &&
13420             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13421                    "Black offers a draw" : "White offers a draw")) {
13422 #ifdef NOTDEF
13423             AppendComment(cmailOldMove, "Draw declined", TRUE);
13424             DisplayComment(cmailOldMove - 1, "Draw declined");
13425 #endif /*NOTDEF*/
13426         } else {
13427             DisplayError(_("There is no pending offer on this move"), 0);
13428         }
13429     } else {
13430         /* Not used for offers from chess program */
13431     }
13432 }
13433
13434 void
13435 RematchEvent()
13436 {
13437     /* Issue ICS rematch command */
13438     if (appData.icsActive) {
13439         SendToICS(ics_prefix);
13440         SendToICS("rematch\n");
13441     }
13442 }
13443
13444 void
13445 CallFlagEvent()
13446 {
13447     /* Call your opponent's flag (claim a win on time) */
13448     if (appData.icsActive) {
13449         SendToICS(ics_prefix);
13450         SendToICS("flag\n");
13451     } else {
13452         switch (gameMode) {
13453           default:
13454             return;
13455           case MachinePlaysWhite:
13456             if (whiteFlag) {
13457                 if (blackFlag)
13458                   GameEnds(GameIsDrawn, "Both players ran out of time",
13459                            GE_PLAYER);
13460                 else
13461                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13462             } else {
13463                 DisplayError(_("Your opponent is not out of time"), 0);
13464             }
13465             break;
13466           case MachinePlaysBlack:
13467             if (blackFlag) {
13468                 if (whiteFlag)
13469                   GameEnds(GameIsDrawn, "Both players ran out of time",
13470                            GE_PLAYER);
13471                 else
13472                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13473             } else {
13474                 DisplayError(_("Your opponent is not out of time"), 0);
13475             }
13476             break;
13477         }
13478     }
13479 }
13480
13481 void
13482 ClockClick(int which)
13483 {       // [HGM] code moved to back-end from winboard.c
13484         if(which) { // black clock
13485           if (gameMode == EditPosition || gameMode == IcsExamining) {
13486             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13487             SetBlackToPlayEvent();
13488           } else if (gameMode == EditGame || shiftKey) {
13489             AdjustClock(which, -1);
13490           } else if (gameMode == IcsPlayingWhite ||
13491                      gameMode == MachinePlaysBlack) {
13492             CallFlagEvent();
13493           }
13494         } else { // white clock
13495           if (gameMode == EditPosition || gameMode == IcsExamining) {
13496             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13497             SetWhiteToPlayEvent();
13498           } else if (gameMode == EditGame || shiftKey) {
13499             AdjustClock(which, -1);
13500           } else if (gameMode == IcsPlayingBlack ||
13501                    gameMode == MachinePlaysWhite) {
13502             CallFlagEvent();
13503           }
13504         }
13505 }
13506
13507 void
13508 DrawEvent()
13509 {
13510     /* Offer draw or accept pending draw offer from opponent */
13511
13512     if (appData.icsActive) {
13513         /* Note: tournament rules require draw offers to be
13514            made after you make your move but before you punch
13515            your clock.  Currently ICS doesn't let you do that;
13516            instead, you immediately punch your clock after making
13517            a move, but you can offer a draw at any time. */
13518
13519         SendToICS(ics_prefix);
13520         SendToICS("draw\n");
13521         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13522     } else if (cmailMsgLoaded) {
13523         if (currentMove == cmailOldMove &&
13524             commentList[cmailOldMove] != NULL &&
13525             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13526                    "Black offers a draw" : "White offers a draw")) {
13527             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13528             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13529         } else if (currentMove == cmailOldMove + 1) {
13530             char *offer = WhiteOnMove(cmailOldMove) ?
13531               "White offers a draw" : "Black offers a draw";
13532             AppendComment(currentMove, offer, TRUE);
13533             DisplayComment(currentMove - 1, offer);
13534             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13535         } else {
13536             DisplayError(_("You must make your move before offering a draw"), 0);
13537             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13538         }
13539     } else if (first.offeredDraw) {
13540         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13541     } else {
13542         if (first.sendDrawOffers) {
13543             SendToProgram("draw\n", &first);
13544             userOfferedDraw = TRUE;
13545         }
13546     }
13547 }
13548
13549 void
13550 AdjournEvent()
13551 {
13552     /* Offer Adjourn or accept pending Adjourn offer from opponent */
13553
13554     if (appData.icsActive) {
13555         SendToICS(ics_prefix);
13556         SendToICS("adjourn\n");
13557     } else {
13558         /* Currently GNU Chess doesn't offer or accept Adjourns */
13559     }
13560 }
13561
13562
13563 void
13564 AbortEvent()
13565 {
13566     /* Offer Abort or accept pending Abort offer from opponent */
13567
13568     if (appData.icsActive) {
13569         SendToICS(ics_prefix);
13570         SendToICS("abort\n");
13571     } else {
13572         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13573     }
13574 }
13575
13576 void
13577 ResignEvent()
13578 {
13579     /* Resign.  You can do this even if it's not your turn. */
13580
13581     if (appData.icsActive) {
13582         SendToICS(ics_prefix);
13583         SendToICS("resign\n");
13584     } else {
13585         switch (gameMode) {
13586           case MachinePlaysWhite:
13587             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13588             break;
13589           case MachinePlaysBlack:
13590             GameEnds(BlackWins, "White resigns", GE_PLAYER);
13591             break;
13592           case EditGame:
13593             if (cmailMsgLoaded) {
13594                 TruncateGame();
13595                 if (WhiteOnMove(cmailOldMove)) {
13596                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
13597                 } else {
13598                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13599                 }
13600                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13601             }
13602             break;
13603           default:
13604             break;
13605         }
13606     }
13607 }
13608
13609
13610 void
13611 StopObservingEvent()
13612 {
13613     /* Stop observing current games */
13614     SendToICS(ics_prefix);
13615     SendToICS("unobserve\n");
13616 }
13617
13618 void
13619 StopExaminingEvent()
13620 {
13621     /* Stop observing current game */
13622     SendToICS(ics_prefix);
13623     SendToICS("unexamine\n");
13624 }
13625
13626 void
13627 ForwardInner(target)
13628      int target;
13629 {
13630     int limit;
13631
13632     if (appData.debugMode)
13633         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13634                 target, currentMove, forwardMostMove);
13635
13636     if (gameMode == EditPosition)
13637       return;
13638
13639     if (gameMode == PlayFromGameFile && !pausing)
13640       PauseEvent();
13641
13642     if (gameMode == IcsExamining && pausing)
13643       limit = pauseExamForwardMostMove;
13644     else
13645       limit = forwardMostMove;
13646
13647     if (target > limit) target = limit;
13648
13649     if (target > 0 && moveList[target - 1][0]) {
13650         int fromX, fromY, toX, toY;
13651         toX = moveList[target - 1][2] - AAA;
13652         toY = moveList[target - 1][3] - ONE;
13653         if (moveList[target - 1][1] == '@') {
13654             if (appData.highlightLastMove) {
13655                 SetHighlights(-1, -1, toX, toY);
13656             }
13657         } else {
13658             fromX = moveList[target - 1][0] - AAA;
13659             fromY = moveList[target - 1][1] - ONE;
13660             if (target == currentMove + 1) {
13661                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13662             }
13663             if (appData.highlightLastMove) {
13664                 SetHighlights(fromX, fromY, toX, toY);
13665             }
13666         }
13667     }
13668     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13669         gameMode == Training || gameMode == PlayFromGameFile ||
13670         gameMode == AnalyzeFile) {
13671         while (currentMove < target) {
13672             SendMoveToProgram(currentMove++, &first);
13673         }
13674     } else {
13675         currentMove = target;
13676     }
13677
13678     if (gameMode == EditGame || gameMode == EndOfGame) {
13679         whiteTimeRemaining = timeRemaining[0][currentMove];
13680         blackTimeRemaining = timeRemaining[1][currentMove];
13681     }
13682     DisplayBothClocks();
13683     DisplayMove(currentMove - 1);
13684     DrawPosition(FALSE, boards[currentMove]);
13685     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13686     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13687         DisplayComment(currentMove - 1, commentList[currentMove]);
13688     }
13689     DisplayBook(currentMove);
13690 }
13691
13692
13693 void
13694 ForwardEvent()
13695 {
13696     if (gameMode == IcsExamining && !pausing) {
13697         SendToICS(ics_prefix);
13698         SendToICS("forward\n");
13699     } else {
13700         ForwardInner(currentMove + 1);
13701     }
13702 }
13703
13704 void
13705 ToEndEvent()
13706 {
13707     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13708         /* to optimze, we temporarily turn off analysis mode while we feed
13709          * the remaining moves to the engine. Otherwise we get analysis output
13710          * after each move.
13711          */
13712         if (first.analysisSupport) {
13713           SendToProgram("exit\nforce\n", &first);
13714           first.analyzing = FALSE;
13715         }
13716     }
13717
13718     if (gameMode == IcsExamining && !pausing) {
13719         SendToICS(ics_prefix);
13720         SendToICS("forward 999999\n");
13721     } else {
13722         ForwardInner(forwardMostMove);
13723     }
13724
13725     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13726         /* we have fed all the moves, so reactivate analysis mode */
13727         SendToProgram("analyze\n", &first);
13728         first.analyzing = TRUE;
13729         /*first.maybeThinking = TRUE;*/
13730         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13731     }
13732 }
13733
13734 void
13735 BackwardInner(target)
13736      int target;
13737 {
13738     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13739
13740     if (appData.debugMode)
13741         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13742                 target, currentMove, forwardMostMove);
13743
13744     if (gameMode == EditPosition) return;
13745     if (currentMove <= backwardMostMove) {
13746         ClearHighlights();
13747         DrawPosition(full_redraw, boards[currentMove]);
13748         return;
13749     }
13750     if (gameMode == PlayFromGameFile && !pausing)
13751       PauseEvent();
13752
13753     if (moveList[target][0]) {
13754         int fromX, fromY, toX, toY;
13755         toX = moveList[target][2] - AAA;
13756         toY = moveList[target][3] - ONE;
13757         if (moveList[target][1] == '@') {
13758             if (appData.highlightLastMove) {
13759                 SetHighlights(-1, -1, toX, toY);
13760             }
13761         } else {
13762             fromX = moveList[target][0] - AAA;
13763             fromY = moveList[target][1] - ONE;
13764             if (target == currentMove - 1) {
13765                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13766             }
13767             if (appData.highlightLastMove) {
13768                 SetHighlights(fromX, fromY, toX, toY);
13769             }
13770         }
13771     }
13772     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13773         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13774         while (currentMove > target) {
13775             SendToProgram("undo\n", &first);
13776             currentMove--;
13777         }
13778     } else {
13779         currentMove = target;
13780     }
13781
13782     if (gameMode == EditGame || gameMode == EndOfGame) {
13783         whiteTimeRemaining = timeRemaining[0][currentMove];
13784         blackTimeRemaining = timeRemaining[1][currentMove];
13785     }
13786     DisplayBothClocks();
13787     DisplayMove(currentMove - 1);
13788     DrawPosition(full_redraw, boards[currentMove]);
13789     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13790     // [HGM] PV info: routine tests if comment empty
13791     DisplayComment(currentMove - 1, commentList[currentMove]);
13792     DisplayBook(currentMove);
13793 }
13794
13795 void
13796 BackwardEvent()
13797 {
13798     if (gameMode == IcsExamining && !pausing) {
13799         SendToICS(ics_prefix);
13800         SendToICS("backward\n");
13801     } else {
13802         BackwardInner(currentMove - 1);
13803     }
13804 }
13805
13806 void
13807 ToStartEvent()
13808 {
13809     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13810         /* to optimize, we temporarily turn off analysis mode while we undo
13811          * all the moves. Otherwise we get analysis output after each undo.
13812          */
13813         if (first.analysisSupport) {
13814           SendToProgram("exit\nforce\n", &first);
13815           first.analyzing = FALSE;
13816         }
13817     }
13818
13819     if (gameMode == IcsExamining && !pausing) {
13820         SendToICS(ics_prefix);
13821         SendToICS("backward 999999\n");
13822     } else {
13823         BackwardInner(backwardMostMove);
13824     }
13825
13826     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13827         /* we have fed all the moves, so reactivate analysis mode */
13828         SendToProgram("analyze\n", &first);
13829         first.analyzing = TRUE;
13830         /*first.maybeThinking = TRUE;*/
13831         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13832     }
13833 }
13834
13835 void
13836 ToNrEvent(int to)
13837 {
13838   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13839   if (to >= forwardMostMove) to = forwardMostMove;
13840   if (to <= backwardMostMove) to = backwardMostMove;
13841   if (to < currentMove) {
13842     BackwardInner(to);
13843   } else {
13844     ForwardInner(to);
13845   }
13846 }
13847
13848 void
13849 RevertEvent(Boolean annotate)
13850 {
13851     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13852         return;
13853     }
13854     if (gameMode != IcsExamining) {
13855         DisplayError(_("You are not examining a game"), 0);
13856         return;
13857     }
13858     if (pausing) {
13859         DisplayError(_("You can't revert while pausing"), 0);
13860         return;
13861     }
13862     SendToICS(ics_prefix);
13863     SendToICS("revert\n");
13864 }
13865
13866 void
13867 RetractMoveEvent()
13868 {
13869     switch (gameMode) {
13870       case MachinePlaysWhite:
13871       case MachinePlaysBlack:
13872         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13873             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13874             return;
13875         }
13876         if (forwardMostMove < 2) return;
13877         currentMove = forwardMostMove = forwardMostMove - 2;
13878         whiteTimeRemaining = timeRemaining[0][currentMove];
13879         blackTimeRemaining = timeRemaining[1][currentMove];
13880         DisplayBothClocks();
13881         DisplayMove(currentMove - 1);
13882         ClearHighlights();/*!! could figure this out*/
13883         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13884         SendToProgram("remove\n", &first);
13885         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13886         break;
13887
13888       case BeginningOfGame:
13889       default:
13890         break;
13891
13892       case IcsPlayingWhite:
13893       case IcsPlayingBlack:
13894         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13895             SendToICS(ics_prefix);
13896             SendToICS("takeback 2\n");
13897         } else {
13898             SendToICS(ics_prefix);
13899             SendToICS("takeback 1\n");
13900         }
13901         break;
13902     }
13903 }
13904
13905 void
13906 MoveNowEvent()
13907 {
13908     ChessProgramState *cps;
13909
13910     switch (gameMode) {
13911       case MachinePlaysWhite:
13912         if (!WhiteOnMove(forwardMostMove)) {
13913             DisplayError(_("It is your turn"), 0);
13914             return;
13915         }
13916         cps = &first;
13917         break;
13918       case MachinePlaysBlack:
13919         if (WhiteOnMove(forwardMostMove)) {
13920             DisplayError(_("It is your turn"), 0);
13921             return;
13922         }
13923         cps = &first;
13924         break;
13925       case TwoMachinesPlay:
13926         if (WhiteOnMove(forwardMostMove) ==
13927             (first.twoMachinesColor[0] == 'w')) {
13928             cps = &first;
13929         } else {
13930             cps = &second;
13931         }
13932         break;
13933       case BeginningOfGame:
13934       default:
13935         return;
13936     }
13937     SendToProgram("?\n", cps);
13938 }
13939
13940 void
13941 TruncateGameEvent()
13942 {
13943     EditGameEvent();
13944     if (gameMode != EditGame) return;
13945     TruncateGame();
13946 }
13947
13948 void
13949 TruncateGame()
13950 {
13951     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13952     if (forwardMostMove > currentMove) {
13953         if (gameInfo.resultDetails != NULL) {
13954             free(gameInfo.resultDetails);
13955             gameInfo.resultDetails = NULL;
13956             gameInfo.result = GameUnfinished;
13957         }
13958         forwardMostMove = currentMove;
13959         HistorySet(parseList, backwardMostMove, forwardMostMove,
13960                    currentMove-1);
13961     }
13962 }
13963
13964 void
13965 HintEvent()
13966 {
13967     if (appData.noChessProgram) return;
13968     switch (gameMode) {
13969       case MachinePlaysWhite:
13970         if (WhiteOnMove(forwardMostMove)) {
13971             DisplayError(_("Wait until your turn"), 0);
13972             return;
13973         }
13974         break;
13975       case BeginningOfGame:
13976       case MachinePlaysBlack:
13977         if (!WhiteOnMove(forwardMostMove)) {
13978             DisplayError(_("Wait until your turn"), 0);
13979             return;
13980         }
13981         break;
13982       default:
13983         DisplayError(_("No hint available"), 0);
13984         return;
13985     }
13986     SendToProgram("hint\n", &first);
13987     hintRequested = TRUE;
13988 }
13989
13990 void
13991 BookEvent()
13992 {
13993     if (appData.noChessProgram) return;
13994     switch (gameMode) {
13995       case MachinePlaysWhite:
13996         if (WhiteOnMove(forwardMostMove)) {
13997             DisplayError(_("Wait until your turn"), 0);
13998             return;
13999         }
14000         break;
14001       case BeginningOfGame:
14002       case MachinePlaysBlack:
14003         if (!WhiteOnMove(forwardMostMove)) {
14004             DisplayError(_("Wait until your turn"), 0);
14005             return;
14006         }
14007         break;
14008       case EditPosition:
14009         EditPositionDone(TRUE);
14010         break;
14011       case TwoMachinesPlay:
14012         return;
14013       default:
14014         break;
14015     }
14016     SendToProgram("bk\n", &first);
14017     bookOutput[0] = NULLCHAR;
14018     bookRequested = TRUE;
14019 }
14020
14021 void
14022 AboutGameEvent()
14023 {
14024     char *tags = PGNTags(&gameInfo);
14025     TagsPopUp(tags, CmailMsg());
14026     free(tags);
14027 }
14028
14029 /* end button procedures */
14030
14031 void
14032 PrintPosition(fp, move)
14033      FILE *fp;
14034      int move;
14035 {
14036     int i, j;
14037
14038     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14039         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14040             char c = PieceToChar(boards[move][i][j]);
14041             fputc(c == 'x' ? '.' : c, fp);
14042             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14043         }
14044     }
14045     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14046       fprintf(fp, "white to play\n");
14047     else
14048       fprintf(fp, "black to play\n");
14049 }
14050
14051 void
14052 PrintOpponents(fp)
14053      FILE *fp;
14054 {
14055     if (gameInfo.white != NULL) {
14056         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14057     } else {
14058         fprintf(fp, "\n");
14059     }
14060 }
14061
14062 /* Find last component of program's own name, using some heuristics */
14063 void
14064 TidyProgramName(prog, host, buf)
14065      char *prog, *host, buf[MSG_SIZ];
14066 {
14067     char *p, *q;
14068     int local = (strcmp(host, "localhost") == 0);
14069     while (!local && (p = strchr(prog, ';')) != NULL) {
14070         p++;
14071         while (*p == ' ') p++;
14072         prog = p;
14073     }
14074     if (*prog == '"' || *prog == '\'') {
14075         q = strchr(prog + 1, *prog);
14076     } else {
14077         q = strchr(prog, ' ');
14078     }
14079     if (q == NULL) q = prog + strlen(prog);
14080     p = q;
14081     while (p >= prog && *p != '/' && *p != '\\') p--;
14082     p++;
14083     if(p == prog && *p == '"') p++;
14084     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14085     memcpy(buf, p, q - p);
14086     buf[q - p] = NULLCHAR;
14087     if (!local) {
14088         strcat(buf, "@");
14089         strcat(buf, host);
14090     }
14091 }
14092
14093 char *
14094 TimeControlTagValue()
14095 {
14096     char buf[MSG_SIZ];
14097     if (!appData.clockMode) {
14098       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14099     } else if (movesPerSession > 0) {
14100       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14101     } else if (timeIncrement == 0) {
14102       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14103     } else {
14104       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14105     }
14106     return StrSave(buf);
14107 }
14108
14109 void
14110 SetGameInfo()
14111 {
14112     /* This routine is used only for certain modes */
14113     VariantClass v = gameInfo.variant;
14114     ChessMove r = GameUnfinished;
14115     char *p = NULL;
14116
14117     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14118         r = gameInfo.result;
14119         p = gameInfo.resultDetails;
14120         gameInfo.resultDetails = NULL;
14121     }
14122     ClearGameInfo(&gameInfo);
14123     gameInfo.variant = v;
14124
14125     switch (gameMode) {
14126       case MachinePlaysWhite:
14127         gameInfo.event = StrSave( appData.pgnEventHeader );
14128         gameInfo.site = StrSave(HostName());
14129         gameInfo.date = PGNDate();
14130         gameInfo.round = StrSave("-");
14131         gameInfo.white = StrSave(first.tidy);
14132         gameInfo.black = StrSave(UserName());
14133         gameInfo.timeControl = TimeControlTagValue();
14134         break;
14135
14136       case MachinePlaysBlack:
14137         gameInfo.event = StrSave( appData.pgnEventHeader );
14138         gameInfo.site = StrSave(HostName());
14139         gameInfo.date = PGNDate();
14140         gameInfo.round = StrSave("-");
14141         gameInfo.white = StrSave(UserName());
14142         gameInfo.black = StrSave(first.tidy);
14143         gameInfo.timeControl = TimeControlTagValue();
14144         break;
14145
14146       case TwoMachinesPlay:
14147         gameInfo.event = StrSave( appData.pgnEventHeader );
14148         gameInfo.site = StrSave(HostName());
14149         gameInfo.date = PGNDate();
14150         if (roundNr > 0) {
14151             char buf[MSG_SIZ];
14152             snprintf(buf, MSG_SIZ, "%d", roundNr);
14153             gameInfo.round = StrSave(buf);
14154         } else {
14155             gameInfo.round = StrSave("-");
14156         }
14157         if (first.twoMachinesColor[0] == 'w') {
14158             gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14159             gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14160         } else {
14161             gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14162             gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14163         }
14164         gameInfo.timeControl = TimeControlTagValue();
14165         break;
14166
14167       case EditGame:
14168         gameInfo.event = StrSave("Edited game");
14169         gameInfo.site = StrSave(HostName());
14170         gameInfo.date = PGNDate();
14171         gameInfo.round = StrSave("-");
14172         gameInfo.white = StrSave("-");
14173         gameInfo.black = StrSave("-");
14174         gameInfo.result = r;
14175         gameInfo.resultDetails = p;
14176         break;
14177
14178       case EditPosition:
14179         gameInfo.event = StrSave("Edited position");
14180         gameInfo.site = StrSave(HostName());
14181         gameInfo.date = PGNDate();
14182         gameInfo.round = StrSave("-");
14183         gameInfo.white = StrSave("-");
14184         gameInfo.black = StrSave("-");
14185         break;
14186
14187       case IcsPlayingWhite:
14188       case IcsPlayingBlack:
14189       case IcsObserving:
14190       case IcsExamining:
14191         break;
14192
14193       case PlayFromGameFile:
14194         gameInfo.event = StrSave("Game from non-PGN file");
14195         gameInfo.site = StrSave(HostName());
14196         gameInfo.date = PGNDate();
14197         gameInfo.round = StrSave("-");
14198         gameInfo.white = StrSave("?");
14199         gameInfo.black = StrSave("?");
14200         break;
14201
14202       default:
14203         break;
14204     }
14205 }
14206
14207 void
14208 ReplaceComment(index, text)
14209      int index;
14210      char *text;
14211 {
14212     int len;
14213     char *p;
14214     float score;
14215
14216     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
14217        pvInfoList[index-1].depth == len &&
14218        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14219        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14220     while (*text == '\n') text++;
14221     len = strlen(text);
14222     while (len > 0 && text[len - 1] == '\n') len--;
14223
14224     if (commentList[index] != NULL)
14225       free(commentList[index]);
14226
14227     if (len == 0) {
14228         commentList[index] = NULL;
14229         return;
14230     }
14231   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14232       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14233       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14234     commentList[index] = (char *) malloc(len + 2);
14235     strncpy(commentList[index], text, len);
14236     commentList[index][len] = '\n';
14237     commentList[index][len + 1] = NULLCHAR;
14238   } else {
14239     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14240     char *p;
14241     commentList[index] = (char *) malloc(len + 7);
14242     safeStrCpy(commentList[index], "{\n", 3);
14243     safeStrCpy(commentList[index]+2, text, len+1);
14244     commentList[index][len+2] = NULLCHAR;
14245     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14246     strcat(commentList[index], "\n}\n");
14247   }
14248 }
14249
14250 void
14251 CrushCRs(text)
14252      char *text;
14253 {
14254   char *p = text;
14255   char *q = text;
14256   char ch;
14257
14258   do {
14259     ch = *p++;
14260     if (ch == '\r') continue;
14261     *q++ = ch;
14262   } while (ch != '\0');
14263 }
14264
14265 void
14266 AppendComment(index, text, addBraces)
14267      int index;
14268      char *text;
14269      Boolean addBraces; // [HGM] braces: tells if we should add {}
14270 {
14271     int oldlen, len;
14272     char *old;
14273
14274 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14275     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14276
14277     CrushCRs(text);
14278     while (*text == '\n') text++;
14279     len = strlen(text);
14280     while (len > 0 && text[len - 1] == '\n') len--;
14281
14282     if (len == 0) return;
14283
14284     if (commentList[index] != NULL) {
14285         old = commentList[index];
14286         oldlen = strlen(old);
14287         while(commentList[index][oldlen-1] ==  '\n')
14288           commentList[index][--oldlen] = NULLCHAR;
14289         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14290         safeStrCpy(commentList[index], old, oldlen + len + 6);
14291         free(old);
14292         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14293         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14294           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14295           while (*text == '\n') { text++; len--; }
14296           commentList[index][--oldlen] = NULLCHAR;
14297       }
14298         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14299         else          strcat(commentList[index], "\n");
14300         strcat(commentList[index], text);
14301         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14302         else          strcat(commentList[index], "\n");
14303     } else {
14304         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14305         if(addBraces)
14306           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14307         else commentList[index][0] = NULLCHAR;
14308         strcat(commentList[index], text);
14309         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14310         if(addBraces == TRUE) strcat(commentList[index], "}\n");
14311     }
14312 }
14313
14314 static char * FindStr( char * text, char * sub_text )
14315 {
14316     char * result = strstr( text, sub_text );
14317
14318     if( result != NULL ) {
14319         result += strlen( sub_text );
14320     }
14321
14322     return result;
14323 }
14324
14325 /* [AS] Try to extract PV info from PGN comment */
14326 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14327 char *GetInfoFromComment( int index, char * text )
14328 {
14329     char * sep = text, *p;
14330
14331     if( text != NULL && index > 0 ) {
14332         int score = 0;
14333         int depth = 0;
14334         int time = -1, sec = 0, deci;
14335         char * s_eval = FindStr( text, "[%eval " );
14336         char * s_emt = FindStr( text, "[%emt " );
14337
14338         if( s_eval != NULL || s_emt != NULL ) {
14339             /* New style */
14340             char delim;
14341
14342             if( s_eval != NULL ) {
14343                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14344                     return text;
14345                 }
14346
14347                 if( delim != ']' ) {
14348                     return text;
14349                 }
14350             }
14351
14352             if( s_emt != NULL ) {
14353             }
14354                 return text;
14355         }
14356         else {
14357             /* We expect something like: [+|-]nnn.nn/dd */
14358             int score_lo = 0;
14359
14360             if(*text != '{') return text; // [HGM] braces: must be normal comment
14361
14362             sep = strchr( text, '/' );
14363             if( sep == NULL || sep < (text+4) ) {
14364                 return text;
14365             }
14366
14367             p = text;
14368             if(p[1] == '(') { // comment starts with PV
14369                p = strchr(p, ')'); // locate end of PV
14370                if(p == NULL || sep < p+5) return text;
14371                // at this point we have something like "{(.*) +0.23/6 ..."
14372                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14373                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14374                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14375             }
14376             time = -1; sec = -1; deci = -1;
14377             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14378                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14379                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14380                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
14381                 return text;
14382             }
14383
14384             if( score_lo < 0 || score_lo >= 100 ) {
14385                 return text;
14386             }
14387
14388             if(sec >= 0) time = 600*time + 10*sec; else
14389             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14390
14391             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14392
14393             /* [HGM] PV time: now locate end of PV info */
14394             while( *++sep >= '0' && *sep <= '9'); // strip depth
14395             if(time >= 0)
14396             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14397             if(sec >= 0)
14398             while( *++sep >= '0' && *sep <= '9'); // strip seconds
14399             if(deci >= 0)
14400             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14401             while(*sep == ' ') sep++;
14402         }
14403
14404         if( depth <= 0 ) {
14405             return text;
14406         }
14407
14408         if( time < 0 ) {
14409             time = -1;
14410         }
14411
14412         pvInfoList[index-1].depth = depth;
14413         pvInfoList[index-1].score = score;
14414         pvInfoList[index-1].time  = 10*time; // centi-sec
14415         if(*sep == '}') *sep = 0; else *--sep = '{';
14416         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14417     }
14418     return sep;
14419 }
14420
14421 void
14422 SendToProgram(message, cps)
14423      char *message;
14424      ChessProgramState *cps;
14425 {
14426     int count, outCount, error;
14427     char buf[MSG_SIZ];
14428
14429     if (cps->pr == NULL) return;
14430     Attention(cps);
14431
14432     if (appData.debugMode) {
14433         TimeMark now;
14434         GetTimeMark(&now);
14435         fprintf(debugFP, "%ld >%-6s: %s",
14436                 SubtractTimeMarks(&now, &programStartTime),
14437                 cps->which, message);
14438     }
14439
14440     count = strlen(message);
14441     outCount = OutputToProcess(cps->pr, message, count, &error);
14442     if (outCount < count && !exiting
14443                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14444       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14445       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14446         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14447             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14448                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14449                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14450                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14451             } else {
14452                 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14453                 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14454                 gameInfo.result = res;
14455             }
14456             gameInfo.resultDetails = StrSave(buf);
14457         }
14458         if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14459         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14460     }
14461 }
14462
14463 void
14464 ReceiveFromProgram(isr, closure, message, count, error)
14465      InputSourceRef isr;
14466      VOIDSTAR closure;
14467      char *message;
14468      int count;
14469      int error;
14470 {
14471     char *end_str;
14472     char buf[MSG_SIZ];
14473     ChessProgramState *cps = (ChessProgramState *)closure;
14474
14475     if (isr != cps->isr) return; /* Killed intentionally */
14476     if (count <= 0) {
14477         if (count == 0) {
14478             RemoveInputSource(cps->isr);
14479             if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14480             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14481                     _(cps->which), cps->program);
14482         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14483                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14484                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14485                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14486                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14487                 } else {
14488                     ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14489                     if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14490                     gameInfo.result = res;
14491                 }
14492                 gameInfo.resultDetails = StrSave(buf);
14493             }
14494             if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14495             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14496         } else {
14497             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14498                     _(cps->which), cps->program);
14499             RemoveInputSource(cps->isr);
14500
14501             /* [AS] Program is misbehaving badly... kill it */
14502             if( count == -2 ) {
14503                 DestroyChildProcess( cps->pr, 9 );
14504                 cps->pr = NoProc;
14505             }
14506
14507             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14508         }
14509         return;
14510     }
14511
14512     if ((end_str = strchr(message, '\r')) != NULL)
14513       *end_str = NULLCHAR;
14514     if ((end_str = strchr(message, '\n')) != NULL)
14515       *end_str = NULLCHAR;
14516
14517     if (appData.debugMode) {
14518         TimeMark now; int print = 1;
14519         char *quote = ""; char c; int i;
14520
14521         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14522                 char start = message[0];
14523                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14524                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14525                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
14526                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14527                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14528                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
14529                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14530                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
14531                    sscanf(message, "hint: %c", &c)!=1 && 
14532                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
14533                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14534                     print = (appData.engineComments >= 2);
14535                 }
14536                 message[0] = start; // restore original message
14537         }
14538         if(print) {
14539                 GetTimeMark(&now);
14540                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14541                         SubtractTimeMarks(&now, &programStartTime), cps->which,
14542                         quote,
14543                         message);
14544         }
14545     }
14546
14547     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14548     if (appData.icsEngineAnalyze) {
14549         if (strstr(message, "whisper") != NULL ||
14550              strstr(message, "kibitz") != NULL ||
14551             strstr(message, "tellics") != NULL) return;
14552     }
14553
14554     HandleMachineMove(message, cps);
14555 }
14556
14557
14558 void
14559 SendTimeControl(cps, mps, tc, inc, sd, st)
14560      ChessProgramState *cps;
14561      int mps, inc, sd, st;
14562      long tc;
14563 {
14564     char buf[MSG_SIZ];
14565     int seconds;
14566
14567     if( timeControl_2 > 0 ) {
14568         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14569             tc = timeControl_2;
14570         }
14571     }
14572     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14573     inc /= cps->timeOdds;
14574     st  /= cps->timeOdds;
14575
14576     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14577
14578     if (st > 0) {
14579       /* Set exact time per move, normally using st command */
14580       if (cps->stKludge) {
14581         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14582         seconds = st % 60;
14583         if (seconds == 0) {
14584           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14585         } else {
14586           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14587         }
14588       } else {
14589         snprintf(buf, MSG_SIZ, "st %d\n", st);
14590       }
14591     } else {
14592       /* Set conventional or incremental time control, using level command */
14593       if (seconds == 0) {
14594         /* Note old gnuchess bug -- minutes:seconds used to not work.
14595            Fixed in later versions, but still avoid :seconds
14596            when seconds is 0. */
14597         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14598       } else {
14599         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14600                  seconds, inc/1000.);
14601       }
14602     }
14603     SendToProgram(buf, cps);
14604
14605     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14606     /* Orthogonally, limit search to given depth */
14607     if (sd > 0) {
14608       if (cps->sdKludge) {
14609         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14610       } else {
14611         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14612       }
14613       SendToProgram(buf, cps);
14614     }
14615
14616     if(cps->nps >= 0) { /* [HGM] nps */
14617         if(cps->supportsNPS == FALSE)
14618           cps->nps = -1; // don't use if engine explicitly says not supported!
14619         else {
14620           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14621           SendToProgram(buf, cps);
14622         }
14623     }
14624 }
14625
14626 ChessProgramState *WhitePlayer()
14627 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14628 {
14629     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14630        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14631         return &second;
14632     return &first;
14633 }
14634
14635 void
14636 SendTimeRemaining(cps, machineWhite)
14637      ChessProgramState *cps;
14638      int /*boolean*/ machineWhite;
14639 {
14640     char message[MSG_SIZ];
14641     long time, otime;
14642
14643     /* Note: this routine must be called when the clocks are stopped
14644        or when they have *just* been set or switched; otherwise
14645        it will be off by the time since the current tick started.
14646     */
14647     if (machineWhite) {
14648         time = whiteTimeRemaining / 10;
14649         otime = blackTimeRemaining / 10;
14650     } else {
14651         time = blackTimeRemaining / 10;
14652         otime = whiteTimeRemaining / 10;
14653     }
14654     /* [HGM] translate opponent's time by time-odds factor */
14655     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14656     if (appData.debugMode) {
14657         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14658     }
14659
14660     if (time <= 0) time = 1;
14661     if (otime <= 0) otime = 1;
14662
14663     snprintf(message, MSG_SIZ, "time %ld\n", time);
14664     SendToProgram(message, cps);
14665
14666     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14667     SendToProgram(message, cps);
14668 }
14669
14670 int
14671 BoolFeature(p, name, loc, cps)
14672      char **p;
14673      char *name;
14674      int *loc;
14675      ChessProgramState *cps;
14676 {
14677   char buf[MSG_SIZ];
14678   int len = strlen(name);
14679   int val;
14680
14681   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14682     (*p) += len + 1;
14683     sscanf(*p, "%d", &val);
14684     *loc = (val != 0);
14685     while (**p && **p != ' ')
14686       (*p)++;
14687     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14688     SendToProgram(buf, cps);
14689     return TRUE;
14690   }
14691   return FALSE;
14692 }
14693
14694 int
14695 IntFeature(p, name, loc, cps)
14696      char **p;
14697      char *name;
14698      int *loc;
14699      ChessProgramState *cps;
14700 {
14701   char buf[MSG_SIZ];
14702   int len = strlen(name);
14703   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14704     (*p) += len + 1;
14705     sscanf(*p, "%d", loc);
14706     while (**p && **p != ' ') (*p)++;
14707     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14708     SendToProgram(buf, cps);
14709     return TRUE;
14710   }
14711   return FALSE;
14712 }
14713
14714 int
14715 StringFeature(p, name, loc, cps)
14716      char **p;
14717      char *name;
14718      char loc[];
14719      ChessProgramState *cps;
14720 {
14721   char buf[MSG_SIZ];
14722   int len = strlen(name);
14723   if (strncmp((*p), name, len) == 0
14724       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14725     (*p) += len + 2;
14726     sscanf(*p, "%[^\"]", loc);
14727     while (**p && **p != '\"') (*p)++;
14728     if (**p == '\"') (*p)++;
14729     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14730     SendToProgram(buf, cps);
14731     return TRUE;
14732   }
14733   return FALSE;
14734 }
14735
14736 int
14737 ParseOption(Option *opt, ChessProgramState *cps)
14738 // [HGM] options: process the string that defines an engine option, and determine
14739 // name, type, default value, and allowed value range
14740 {
14741         char *p, *q, buf[MSG_SIZ];
14742         int n, min = (-1)<<31, max = 1<<31, def;
14743
14744         if(p = strstr(opt->name, " -spin ")) {
14745             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14746             if(max < min) max = min; // enforce consistency
14747             if(def < min) def = min;
14748             if(def > max) def = max;
14749             opt->value = def;
14750             opt->min = min;
14751             opt->max = max;
14752             opt->type = Spin;
14753         } else if((p = strstr(opt->name, " -slider "))) {
14754             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14755             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14756             if(max < min) max = min; // enforce consistency
14757             if(def < min) def = min;
14758             if(def > max) def = max;
14759             opt->value = def;
14760             opt->min = min;
14761             opt->max = max;
14762             opt->type = Spin; // Slider;
14763         } else if((p = strstr(opt->name, " -string "))) {
14764             opt->textValue = p+9;
14765             opt->type = TextBox;
14766         } else if((p = strstr(opt->name, " -file "))) {
14767             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14768             opt->textValue = p+7;
14769             opt->type = FileName; // FileName;
14770         } else if((p = strstr(opt->name, " -path "))) {
14771             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14772             opt->textValue = p+7;
14773             opt->type = PathName; // PathName;
14774         } else if(p = strstr(opt->name, " -check ")) {
14775             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14776             opt->value = (def != 0);
14777             opt->type = CheckBox;
14778         } else if(p = strstr(opt->name, " -combo ")) {
14779             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14780             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14781             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14782             opt->value = n = 0;
14783             while(q = StrStr(q, " /// ")) {
14784                 n++; *q = 0;    // count choices, and null-terminate each of them
14785                 q += 5;
14786                 if(*q == '*') { // remember default, which is marked with * prefix
14787                     q++;
14788                     opt->value = n;
14789                 }
14790                 cps->comboList[cps->comboCnt++] = q;
14791             }
14792             cps->comboList[cps->comboCnt++] = NULL;
14793             opt->max = n + 1;
14794             opt->type = ComboBox;
14795         } else if(p = strstr(opt->name, " -button")) {
14796             opt->type = Button;
14797         } else if(p = strstr(opt->name, " -save")) {
14798             opt->type = SaveButton;
14799         } else return FALSE;
14800         *p = 0; // terminate option name
14801         // now look if the command-line options define a setting for this engine option.
14802         if(cps->optionSettings && cps->optionSettings[0])
14803             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14804         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14805           snprintf(buf, MSG_SIZ, "option %s", p);
14806                 if(p = strstr(buf, ",")) *p = 0;
14807                 if(q = strchr(buf, '=')) switch(opt->type) {
14808                     case ComboBox:
14809                         for(n=0; n<opt->max; n++)
14810                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14811                         break;
14812                     case TextBox:
14813                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14814                         break;
14815                     case Spin:
14816                     case CheckBox:
14817                         opt->value = atoi(q+1);
14818                     default:
14819                         break;
14820                 }
14821                 strcat(buf, "\n");
14822                 SendToProgram(buf, cps);
14823         }
14824         return TRUE;
14825 }
14826
14827 void
14828 FeatureDone(cps, val)
14829      ChessProgramState* cps;
14830      int val;
14831 {
14832   DelayedEventCallback cb = GetDelayedEvent();
14833   if ((cb == InitBackEnd3 && cps == &first) ||
14834       (cb == SettingsMenuIfReady && cps == &second) ||
14835       (cb == LoadEngine) ||
14836       (cb == TwoMachinesEventIfReady)) {
14837     CancelDelayedEvent();
14838     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14839   }
14840   cps->initDone = val;
14841 }
14842
14843 /* Parse feature command from engine */
14844 void
14845 ParseFeatures(args, cps)
14846      char* args;
14847      ChessProgramState *cps;
14848 {
14849   char *p = args;
14850   char *q;
14851   int val;
14852   char buf[MSG_SIZ];
14853
14854   for (;;) {
14855     while (*p == ' ') p++;
14856     if (*p == NULLCHAR) return;
14857
14858     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14859     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14860     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14861     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14862     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14863     if (BoolFeature(&p, "reuse", &val, cps)) {
14864       /* Engine can disable reuse, but can't enable it if user said no */
14865       if (!val) cps->reuse = FALSE;
14866       continue;
14867     }
14868     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14869     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14870       if (gameMode == TwoMachinesPlay) {
14871         DisplayTwoMachinesTitle();
14872       } else {
14873         DisplayTitle("");
14874       }
14875       continue;
14876     }
14877     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14878     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14879     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14880     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14881     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14882     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14883     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14884     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14885     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14886     if (IntFeature(&p, "done", &val, cps)) {
14887       FeatureDone(cps, val);
14888       continue;
14889     }
14890     /* Added by Tord: */
14891     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14892     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14893     /* End of additions by Tord */
14894
14895     /* [HGM] added features: */
14896     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14897     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14898     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14899     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14900     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14901     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14902     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14903         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14904           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14905             SendToProgram(buf, cps);
14906             continue;
14907         }
14908         if(cps->nrOptions >= MAX_OPTIONS) {
14909             cps->nrOptions--;
14910             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14911             DisplayError(buf, 0);
14912         }
14913         continue;
14914     }
14915     /* End of additions by HGM */
14916
14917     /* unknown feature: complain and skip */
14918     q = p;
14919     while (*q && *q != '=') q++;
14920     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14921     SendToProgram(buf, cps);
14922     p = q;
14923     if (*p == '=') {
14924       p++;
14925       if (*p == '\"') {
14926         p++;
14927         while (*p && *p != '\"') p++;
14928         if (*p == '\"') p++;
14929       } else {
14930         while (*p && *p != ' ') p++;
14931       }
14932     }
14933   }
14934
14935 }
14936
14937 void
14938 PeriodicUpdatesEvent(newState)
14939      int newState;
14940 {
14941     if (newState == appData.periodicUpdates)
14942       return;
14943
14944     appData.periodicUpdates=newState;
14945
14946     /* Display type changes, so update it now */
14947 //    DisplayAnalysis();
14948
14949     /* Get the ball rolling again... */
14950     if (newState) {
14951         AnalysisPeriodicEvent(1);
14952         StartAnalysisClock();
14953     }
14954 }
14955
14956 void
14957 PonderNextMoveEvent(newState)
14958      int newState;
14959 {
14960     if (newState == appData.ponderNextMove) return;
14961     if (gameMode == EditPosition) EditPositionDone(TRUE);
14962     if (newState) {
14963         SendToProgram("hard\n", &first);
14964         if (gameMode == TwoMachinesPlay) {
14965             SendToProgram("hard\n", &second);
14966         }
14967     } else {
14968         SendToProgram("easy\n", &first);
14969         thinkOutput[0] = NULLCHAR;
14970         if (gameMode == TwoMachinesPlay) {
14971             SendToProgram("easy\n", &second);
14972         }
14973     }
14974     appData.ponderNextMove = newState;
14975 }
14976
14977 void
14978 NewSettingEvent(option, feature, command, value)
14979      char *command;
14980      int option, value, *feature;
14981 {
14982     char buf[MSG_SIZ];
14983
14984     if (gameMode == EditPosition) EditPositionDone(TRUE);
14985     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14986     if(feature == NULL || *feature) SendToProgram(buf, &first);
14987     if (gameMode == TwoMachinesPlay) {
14988         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14989     }
14990 }
14991
14992 void
14993 ShowThinkingEvent()
14994 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14995 {
14996     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14997     int newState = appData.showThinking
14998         // [HGM] thinking: other features now need thinking output as well
14999         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15000
15001     if (oldState == newState) return;
15002     oldState = newState;
15003     if (gameMode == EditPosition) EditPositionDone(TRUE);
15004     if (oldState) {
15005         SendToProgram("post\n", &first);
15006         if (gameMode == TwoMachinesPlay) {
15007             SendToProgram("post\n", &second);
15008         }
15009     } else {
15010         SendToProgram("nopost\n", &first);
15011         thinkOutput[0] = NULLCHAR;
15012         if (gameMode == TwoMachinesPlay) {
15013             SendToProgram("nopost\n", &second);
15014         }
15015     }
15016 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15017 }
15018
15019 void
15020 AskQuestionEvent(title, question, replyPrefix, which)
15021      char *title; char *question; char *replyPrefix; char *which;
15022 {
15023   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15024   if (pr == NoProc) return;
15025   AskQuestion(title, question, replyPrefix, pr);
15026 }
15027
15028 void
15029 TypeInEvent(char firstChar)
15030 {
15031     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
15032         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
15033         gameMode == AnalyzeMode || gameMode == EditGame || \r
15034         gameMode == EditPosition || gameMode == IcsExamining ||\r
15035         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
15036         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
15037                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
15038                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
15039         gameMode == Training) PopUpMoveDialog(firstChar);
15040 }
15041
15042 void
15043 TypeInDoneEvent(char *move)
15044 {
15045         Board board;
15046         int n, fromX, fromY, toX, toY;
15047         char promoChar;
15048         ChessMove moveType;\r
15049
15050         // [HGM] FENedit\r
15051         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
15052                 EditPositionPasteFEN(move);\r
15053                 return;\r
15054         }\r
15055         // [HGM] movenum: allow move number to be typed in any mode\r
15056         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
15057           ToNrEvent(2*n-1);\r
15058           return;\r
15059         }\r
15060
15061       if (gameMode != EditGame && currentMove != forwardMostMove && \r
15062         gameMode != Training) {\r
15063         DisplayMoveError(_("Displayed move is not current"));\r
15064       } else {\r
15065         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15066           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
15067         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
15068         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
15069           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
15070           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
15071         } else {\r
15072           DisplayMoveError(_("Could not parse move"));\r
15073         }
15074       }\r
15075 }\r
15076
15077 void
15078 DisplayMove(moveNumber)
15079      int moveNumber;
15080 {
15081     char message[MSG_SIZ];
15082     char res[MSG_SIZ];
15083     char cpThinkOutput[MSG_SIZ];
15084
15085     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15086
15087     if (moveNumber == forwardMostMove - 1 ||
15088         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15089
15090         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15091
15092         if (strchr(cpThinkOutput, '\n')) {
15093             *strchr(cpThinkOutput, '\n') = NULLCHAR;
15094         }
15095     } else {
15096         *cpThinkOutput = NULLCHAR;
15097     }
15098
15099     /* [AS] Hide thinking from human user */
15100     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15101         *cpThinkOutput = NULLCHAR;
15102         if( thinkOutput[0] != NULLCHAR ) {
15103             int i;
15104
15105             for( i=0; i<=hiddenThinkOutputState; i++ ) {
15106                 cpThinkOutput[i] = '.';
15107             }
15108             cpThinkOutput[i] = NULLCHAR;
15109             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15110         }
15111     }
15112
15113     if (moveNumber == forwardMostMove - 1 &&
15114         gameInfo.resultDetails != NULL) {
15115         if (gameInfo.resultDetails[0] == NULLCHAR) {
15116           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15117         } else {
15118           snprintf(res, MSG_SIZ, " {%s} %s",
15119                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15120         }
15121     } else {
15122         res[0] = NULLCHAR;
15123     }
15124
15125     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15126         DisplayMessage(res, cpThinkOutput);
15127     } else {
15128       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15129                 WhiteOnMove(moveNumber) ? " " : ".. ",
15130                 parseList[moveNumber], res);
15131         DisplayMessage(message, cpThinkOutput);
15132     }
15133 }
15134
15135 void
15136 DisplayComment(moveNumber, text)
15137      int moveNumber;
15138      char *text;
15139 {
15140     char title[MSG_SIZ];
15141     char buf[8000]; // comment can be long!
15142     int score, depth;
15143
15144     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15145       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15146     } else {
15147       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15148               WhiteOnMove(moveNumber) ? " " : ".. ",
15149               parseList[moveNumber]);
15150     }
15151     // [HGM] PV info: display PV info together with (or as) comment
15152     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
15153       if(text == NULL) text = "";
15154       score = pvInfoList[moveNumber].score;
15155       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
15156               depth, (pvInfoList[moveNumber].time+50)/100, text);
15157       text = buf;
15158     }
15159     if (text != NULL && (appData.autoDisplayComment || commentUp))
15160         CommentPopUp(title, text);
15161 }
15162
15163 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15164  * might be busy thinking or pondering.  It can be omitted if your
15165  * gnuchess is configured to stop thinking immediately on any user
15166  * input.  However, that gnuchess feature depends on the FIONREAD
15167  * ioctl, which does not work properly on some flavors of Unix.
15168  */
15169 void
15170 Attention(cps)
15171      ChessProgramState *cps;
15172 {
15173 #if ATTENTION
15174     if (!cps->useSigint) return;
15175     if (appData.noChessProgram || (cps->pr == NoProc)) return;
15176     switch (gameMode) {
15177       case MachinePlaysWhite:
15178       case MachinePlaysBlack:
15179       case TwoMachinesPlay:
15180       case IcsPlayingWhite:
15181       case IcsPlayingBlack:
15182       case AnalyzeMode:
15183       case AnalyzeFile:
15184         /* Skip if we know it isn't thinking */
15185         if (!cps->maybeThinking) return;
15186         if (appData.debugMode)
15187           fprintf(debugFP, "Interrupting %s\n", cps->which);
15188         InterruptChildProcess(cps->pr);
15189         cps->maybeThinking = FALSE;
15190         break;
15191       default:
15192         break;
15193     }
15194 #endif /*ATTENTION*/
15195 }
15196
15197 int
15198 CheckFlags()
15199 {
15200     if (whiteTimeRemaining <= 0) {
15201         if (!whiteFlag) {
15202             whiteFlag = TRUE;
15203             if (appData.icsActive) {
15204                 if (appData.autoCallFlag &&
15205                     gameMode == IcsPlayingBlack && !blackFlag) {
15206                   SendToICS(ics_prefix);
15207                   SendToICS("flag\n");
15208                 }
15209             } else {
15210                 if (blackFlag) {
15211                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15212                 } else {
15213                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15214                     if (appData.autoCallFlag) {
15215                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15216                         return TRUE;
15217                     }
15218                 }
15219             }
15220         }
15221     }
15222     if (blackTimeRemaining <= 0) {
15223         if (!blackFlag) {
15224             blackFlag = TRUE;
15225             if (appData.icsActive) {
15226                 if (appData.autoCallFlag &&
15227                     gameMode == IcsPlayingWhite && !whiteFlag) {
15228                   SendToICS(ics_prefix);
15229                   SendToICS("flag\n");
15230                 }
15231             } else {
15232                 if (whiteFlag) {
15233                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15234                 } else {
15235                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15236                     if (appData.autoCallFlag) {
15237                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15238                         return TRUE;
15239                     }
15240                 }
15241             }
15242         }
15243     }
15244     return FALSE;
15245 }
15246
15247 void
15248 CheckTimeControl()
15249 {
15250     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15251         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15252
15253     /*
15254      * add time to clocks when time control is achieved ([HGM] now also used for increment)
15255      */
15256     if ( !WhiteOnMove(forwardMostMove) ) {
15257         /* White made time control */
15258         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15259         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15260         /* [HGM] time odds: correct new time quota for time odds! */
15261                                             / WhitePlayer()->timeOdds;
15262         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15263     } else {
15264         lastBlack -= blackTimeRemaining;
15265         /* Black made time control */
15266         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15267                                             / WhitePlayer()->other->timeOdds;
15268         lastWhite = whiteTimeRemaining;
15269     }
15270 }
15271
15272 void
15273 DisplayBothClocks()
15274 {
15275     int wom = gameMode == EditPosition ?
15276       !blackPlaysFirst : WhiteOnMove(currentMove);
15277     DisplayWhiteClock(whiteTimeRemaining, wom);
15278     DisplayBlackClock(blackTimeRemaining, !wom);
15279 }
15280
15281
15282 /* Timekeeping seems to be a portability nightmare.  I think everyone
15283    has ftime(), but I'm really not sure, so I'm including some ifdefs
15284    to use other calls if you don't.  Clocks will be less accurate if
15285    you have neither ftime nor gettimeofday.
15286 */
15287
15288 /* VS 2008 requires the #include outside of the function */
15289 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15290 #include <sys/timeb.h>
15291 #endif
15292
15293 /* Get the current time as a TimeMark */
15294 void
15295 GetTimeMark(tm)
15296      TimeMark *tm;
15297 {
15298 #if HAVE_GETTIMEOFDAY
15299
15300     struct timeval timeVal;
15301     struct timezone timeZone;
15302
15303     gettimeofday(&timeVal, &timeZone);
15304     tm->sec = (long) timeVal.tv_sec;
15305     tm->ms = (int) (timeVal.tv_usec / 1000L);
15306
15307 #else /*!HAVE_GETTIMEOFDAY*/
15308 #if HAVE_FTIME
15309
15310 // include <sys/timeb.h> / moved to just above start of function
15311     struct timeb timeB;
15312
15313     ftime(&timeB);
15314     tm->sec = (long) timeB.time;
15315     tm->ms = (int) timeB.millitm;
15316
15317 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15318     tm->sec = (long) time(NULL);
15319     tm->ms = 0;
15320 #endif
15321 #endif
15322 }
15323
15324 /* Return the difference in milliseconds between two
15325    time marks.  We assume the difference will fit in a long!
15326 */
15327 long
15328 SubtractTimeMarks(tm2, tm1)
15329      TimeMark *tm2, *tm1;
15330 {
15331     return 1000L*(tm2->sec - tm1->sec) +
15332            (long) (tm2->ms - tm1->ms);
15333 }
15334
15335
15336 /*
15337  * Code to manage the game clocks.
15338  *
15339  * In tournament play, black starts the clock and then white makes a move.
15340  * We give the human user a slight advantage if he is playing white---the
15341  * clocks don't run until he makes his first move, so it takes zero time.
15342  * Also, we don't account for network lag, so we could get out of sync
15343  * with GNU Chess's clock -- but then, referees are always right.
15344  */
15345
15346 static TimeMark tickStartTM;
15347 static long intendedTickLength;
15348
15349 long
15350 NextTickLength(timeRemaining)
15351      long timeRemaining;
15352 {
15353     long nominalTickLength, nextTickLength;
15354
15355     if (timeRemaining > 0L && timeRemaining <= 10000L)
15356       nominalTickLength = 100L;
15357     else
15358       nominalTickLength = 1000L;
15359     nextTickLength = timeRemaining % nominalTickLength;
15360     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15361
15362     return nextTickLength;
15363 }
15364
15365 /* Adjust clock one minute up or down */
15366 void
15367 AdjustClock(Boolean which, int dir)
15368 {
15369     if(which) blackTimeRemaining += 60000*dir;
15370     else      whiteTimeRemaining += 60000*dir;
15371     DisplayBothClocks();
15372 }
15373
15374 /* Stop clocks and reset to a fresh time control */
15375 void
15376 ResetClocks()
15377 {
15378     (void) StopClockTimer();
15379     if (appData.icsActive) {
15380         whiteTimeRemaining = blackTimeRemaining = 0;
15381     } else if (searchTime) {
15382         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15383         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15384     } else { /* [HGM] correct new time quote for time odds */
15385         whiteTC = blackTC = fullTimeControlString;
15386         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15387         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15388     }
15389     if (whiteFlag || blackFlag) {
15390         DisplayTitle("");
15391         whiteFlag = blackFlag = FALSE;
15392     }
15393     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15394     DisplayBothClocks();
15395 }
15396
15397 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15398
15399 /* Decrement running clock by amount of time that has passed */
15400 void
15401 DecrementClocks()
15402 {
15403     long timeRemaining;
15404     long lastTickLength, fudge;
15405     TimeMark now;
15406
15407     if (!appData.clockMode) return;
15408     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15409
15410     GetTimeMark(&now);
15411
15412     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15413
15414     /* Fudge if we woke up a little too soon */
15415     fudge = intendedTickLength - lastTickLength;
15416     if (fudge < 0 || fudge > FUDGE) fudge = 0;
15417
15418     if (WhiteOnMove(forwardMostMove)) {
15419         if(whiteNPS >= 0) lastTickLength = 0;
15420         timeRemaining = whiteTimeRemaining -= lastTickLength;
15421         if(timeRemaining < 0 && !appData.icsActive) {
15422             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15423             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15424                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15425                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15426             }
15427         }
15428         DisplayWhiteClock(whiteTimeRemaining - fudge,
15429                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15430     } else {
15431         if(blackNPS >= 0) lastTickLength = 0;
15432         timeRemaining = blackTimeRemaining -= lastTickLength;
15433         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15434             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15435             if(suddenDeath) {
15436                 blackStartMove = forwardMostMove;
15437                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15438             }
15439         }
15440         DisplayBlackClock(blackTimeRemaining - fudge,
15441                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15442     }
15443     if (CheckFlags()) return;
15444
15445     tickStartTM = now;
15446     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15447     StartClockTimer(intendedTickLength);
15448
15449     /* if the time remaining has fallen below the alarm threshold, sound the
15450      * alarm. if the alarm has sounded and (due to a takeback or time control
15451      * with increment) the time remaining has increased to a level above the
15452      * threshold, reset the alarm so it can sound again.
15453      */
15454
15455     if (appData.icsActive && appData.icsAlarm) {
15456
15457         /* make sure we are dealing with the user's clock */
15458         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15459                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15460            )) return;
15461
15462         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15463             alarmSounded = FALSE;
15464         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15465             PlayAlarmSound();
15466             alarmSounded = TRUE;
15467         }
15468     }
15469 }
15470
15471
15472 /* A player has just moved, so stop the previously running
15473    clock and (if in clock mode) start the other one.
15474    We redisplay both clocks in case we're in ICS mode, because
15475    ICS gives us an update to both clocks after every move.
15476    Note that this routine is called *after* forwardMostMove
15477    is updated, so the last fractional tick must be subtracted
15478    from the color that is *not* on move now.
15479 */
15480 void
15481 SwitchClocks(int newMoveNr)
15482 {
15483     long lastTickLength;
15484     TimeMark now;
15485     int flagged = FALSE;
15486
15487     GetTimeMark(&now);
15488
15489     if (StopClockTimer() && appData.clockMode) {
15490         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15491         if (!WhiteOnMove(forwardMostMove)) {
15492             if(blackNPS >= 0) lastTickLength = 0;
15493             blackTimeRemaining -= lastTickLength;
15494            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15495 //         if(pvInfoList[forwardMostMove].time == -1)
15496                  pvInfoList[forwardMostMove].time =               // use GUI time
15497                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15498         } else {
15499            if(whiteNPS >= 0) lastTickLength = 0;
15500            whiteTimeRemaining -= lastTickLength;
15501            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15502 //         if(pvInfoList[forwardMostMove].time == -1)
15503                  pvInfoList[forwardMostMove].time =
15504                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15505         }
15506         flagged = CheckFlags();
15507     }
15508     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15509     CheckTimeControl();
15510
15511     if (flagged || !appData.clockMode) return;
15512
15513     switch (gameMode) {
15514       case MachinePlaysBlack:
15515       case MachinePlaysWhite:
15516       case BeginningOfGame:
15517         if (pausing) return;
15518         break;
15519
15520       case EditGame:
15521       case PlayFromGameFile:
15522       case IcsExamining:
15523         return;
15524
15525       default:
15526         break;
15527     }
15528
15529     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15530         if(WhiteOnMove(forwardMostMove))
15531              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15532         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15533     }
15534
15535     tickStartTM = now;
15536     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15537       whiteTimeRemaining : blackTimeRemaining);
15538     StartClockTimer(intendedTickLength);
15539 }
15540
15541
15542 /* Stop both clocks */
15543 void
15544 StopClocks()
15545 {
15546     long lastTickLength;
15547     TimeMark now;
15548
15549     if (!StopClockTimer()) return;
15550     if (!appData.clockMode) return;
15551
15552     GetTimeMark(&now);
15553
15554     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15555     if (WhiteOnMove(forwardMostMove)) {
15556         if(whiteNPS >= 0) lastTickLength = 0;
15557         whiteTimeRemaining -= lastTickLength;
15558         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15559     } else {
15560         if(blackNPS >= 0) lastTickLength = 0;
15561         blackTimeRemaining -= lastTickLength;
15562         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15563     }
15564     CheckFlags();
15565 }
15566
15567 /* Start clock of player on move.  Time may have been reset, so
15568    if clock is already running, stop and restart it. */
15569 void
15570 StartClocks()
15571 {
15572     (void) StopClockTimer(); /* in case it was running already */
15573     DisplayBothClocks();
15574     if (CheckFlags()) return;
15575
15576     if (!appData.clockMode) return;
15577     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15578
15579     GetTimeMark(&tickStartTM);
15580     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15581       whiteTimeRemaining : blackTimeRemaining);
15582
15583    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15584     whiteNPS = blackNPS = -1;
15585     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15586        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15587         whiteNPS = first.nps;
15588     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15589        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15590         blackNPS = first.nps;
15591     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15592         whiteNPS = second.nps;
15593     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15594         blackNPS = second.nps;
15595     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15596
15597     StartClockTimer(intendedTickLength);
15598 }
15599
15600 char *
15601 TimeString(ms)
15602      long ms;
15603 {
15604     long second, minute, hour, day;
15605     char *sign = "";
15606     static char buf[32];
15607
15608     if (ms > 0 && ms <= 9900) {
15609       /* convert milliseconds to tenths, rounding up */
15610       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15611
15612       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15613       return buf;
15614     }
15615
15616     /* convert milliseconds to seconds, rounding up */
15617     /* use floating point to avoid strangeness of integer division
15618        with negative dividends on many machines */
15619     second = (long) floor(((double) (ms + 999L)) / 1000.0);
15620
15621     if (second < 0) {
15622         sign = "-";
15623         second = -second;
15624     }
15625
15626     day = second / (60 * 60 * 24);
15627     second = second % (60 * 60 * 24);
15628     hour = second / (60 * 60);
15629     second = second % (60 * 60);
15630     minute = second / 60;
15631     second = second % 60;
15632
15633     if (day > 0)
15634       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15635               sign, day, hour, minute, second);
15636     else if (hour > 0)
15637       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15638     else
15639       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15640
15641     return buf;
15642 }
15643
15644
15645 /*
15646  * This is necessary because some C libraries aren't ANSI C compliant yet.
15647  */
15648 char *
15649 StrStr(string, match)
15650      char *string, *match;
15651 {
15652     int i, length;
15653
15654     length = strlen(match);
15655
15656     for (i = strlen(string) - length; i >= 0; i--, string++)
15657       if (!strncmp(match, string, length))
15658         return string;
15659
15660     return NULL;
15661 }
15662
15663 char *
15664 StrCaseStr(string, match)
15665      char *string, *match;
15666 {
15667     int i, j, length;
15668
15669     length = strlen(match);
15670
15671     for (i = strlen(string) - length; i >= 0; i--, string++) {
15672         for (j = 0; j < length; j++) {
15673             if (ToLower(match[j]) != ToLower(string[j]))
15674               break;
15675         }
15676         if (j == length) return string;
15677     }
15678
15679     return NULL;
15680 }
15681
15682 #ifndef _amigados
15683 int
15684 StrCaseCmp(s1, s2)
15685      char *s1, *s2;
15686 {
15687     char c1, c2;
15688
15689     for (;;) {
15690         c1 = ToLower(*s1++);
15691         c2 = ToLower(*s2++);
15692         if (c1 > c2) return 1;
15693         if (c1 < c2) return -1;
15694         if (c1 == NULLCHAR) return 0;
15695     }
15696 }
15697
15698
15699 int
15700 ToLower(c)
15701      int c;
15702 {
15703     return isupper(c) ? tolower(c) : c;
15704 }
15705
15706
15707 int
15708 ToUpper(c)
15709      int c;
15710 {
15711     return islower(c) ? toupper(c) : c;
15712 }
15713 #endif /* !_amigados    */
15714
15715 char *
15716 StrSave(s)
15717      char *s;
15718 {
15719   char *ret;
15720
15721   if ((ret = (char *) malloc(strlen(s) + 1)))
15722     {
15723       safeStrCpy(ret, s, strlen(s)+1);
15724     }
15725   return ret;
15726 }
15727
15728 char *
15729 StrSavePtr(s, savePtr)
15730      char *s, **savePtr;
15731 {
15732     if (*savePtr) {
15733         free(*savePtr);
15734     }
15735     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15736       safeStrCpy(*savePtr, s, strlen(s)+1);
15737     }
15738     return(*savePtr);
15739 }
15740
15741 char *
15742 PGNDate()
15743 {
15744     time_t clock;
15745     struct tm *tm;
15746     char buf[MSG_SIZ];
15747
15748     clock = time((time_t *)NULL);
15749     tm = localtime(&clock);
15750     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15751             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15752     return StrSave(buf);
15753 }
15754
15755
15756 char *
15757 PositionToFEN(move, overrideCastling)
15758      int move;
15759      char *overrideCastling;
15760 {
15761     int i, j, fromX, fromY, toX, toY;
15762     int whiteToPlay;
15763     char buf[128];
15764     char *p, *q;
15765     int emptycount;
15766     ChessSquare piece;
15767
15768     whiteToPlay = (gameMode == EditPosition) ?
15769       !blackPlaysFirst : (move % 2 == 0);
15770     p = buf;
15771
15772     /* Piece placement data */
15773     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15774         emptycount = 0;
15775         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15776             if (boards[move][i][j] == EmptySquare) {
15777                 emptycount++;
15778             } else { ChessSquare piece = boards[move][i][j];
15779                 if (emptycount > 0) {
15780                     if(emptycount<10) /* [HGM] can be >= 10 */
15781                         *p++ = '0' + emptycount;
15782                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15783                     emptycount = 0;
15784                 }
15785                 if(PieceToChar(piece) == '+') {
15786                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15787                     *p++ = '+';
15788                     piece = (ChessSquare)(DEMOTED piece);
15789                 }
15790                 *p++ = PieceToChar(piece);
15791                 if(p[-1] == '~') {
15792                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15793                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15794                     *p++ = '~';
15795                 }
15796             }
15797         }
15798         if (emptycount > 0) {
15799             if(emptycount<10) /* [HGM] can be >= 10 */
15800                 *p++ = '0' + emptycount;
15801             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15802             emptycount = 0;
15803         }
15804         *p++ = '/';
15805     }
15806     *(p - 1) = ' ';
15807
15808     /* [HGM] print Crazyhouse or Shogi holdings */
15809     if( gameInfo.holdingsWidth ) {
15810         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15811         q = p;
15812         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15813             piece = boards[move][i][BOARD_WIDTH-1];
15814             if( piece != EmptySquare )
15815               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15816                   *p++ = PieceToChar(piece);
15817         }
15818         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15819             piece = boards[move][BOARD_HEIGHT-i-1][0];
15820             if( piece != EmptySquare )
15821               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15822                   *p++ = PieceToChar(piece);
15823         }
15824
15825         if( q == p ) *p++ = '-';
15826         *p++ = ']';
15827         *p++ = ' ';
15828     }
15829
15830     /* Active color */
15831     *p++ = whiteToPlay ? 'w' : 'b';
15832     *p++ = ' ';
15833
15834   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15835     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15836   } else {
15837   if(nrCastlingRights) {
15838      q = p;
15839      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15840        /* [HGM] write directly from rights */
15841            if(boards[move][CASTLING][2] != NoRights &&
15842               boards[move][CASTLING][0] != NoRights   )
15843                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15844            if(boards[move][CASTLING][2] != NoRights &&
15845               boards[move][CASTLING][1] != NoRights   )
15846                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15847            if(boards[move][CASTLING][5] != NoRights &&
15848               boards[move][CASTLING][3] != NoRights   )
15849                 *p++ = boards[move][CASTLING][3] + AAA;
15850            if(boards[move][CASTLING][5] != NoRights &&
15851               boards[move][CASTLING][4] != NoRights   )
15852                 *p++ = boards[move][CASTLING][4] + AAA;
15853      } else {
15854
15855         /* [HGM] write true castling rights */
15856         if( nrCastlingRights == 6 ) {
15857             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15858                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15859             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15860                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15861             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15862                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15863             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15864                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15865         }
15866      }
15867      if (q == p) *p++ = '-'; /* No castling rights */
15868      *p++ = ' ';
15869   }
15870
15871   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15872      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15873     /* En passant target square */
15874     if (move > backwardMostMove) {
15875         fromX = moveList[move - 1][0] - AAA;
15876         fromY = moveList[move - 1][1] - ONE;
15877         toX = moveList[move - 1][2] - AAA;
15878         toY = moveList[move - 1][3] - ONE;
15879         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15880             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15881             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15882             fromX == toX) {
15883             /* 2-square pawn move just happened */
15884             *p++ = toX + AAA;
15885             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15886         } else {
15887             *p++ = '-';
15888         }
15889     } else if(move == backwardMostMove) {
15890         // [HGM] perhaps we should always do it like this, and forget the above?
15891         if((signed char)boards[move][EP_STATUS] >= 0) {
15892             *p++ = boards[move][EP_STATUS] + AAA;
15893             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15894         } else {
15895             *p++ = '-';
15896         }
15897     } else {
15898         *p++ = '-';
15899     }
15900     *p++ = ' ';
15901   }
15902   }
15903
15904     /* [HGM] find reversible plies */
15905     {   int i = 0, j=move;
15906
15907         if (appData.debugMode) { int k;
15908             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15909             for(k=backwardMostMove; k<=forwardMostMove; k++)
15910                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15911
15912         }
15913
15914         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15915         if( j == backwardMostMove ) i += initialRulePlies;
15916         sprintf(p, "%d ", i);
15917         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15918     }
15919     /* Fullmove number */
15920     sprintf(p, "%d", (move / 2) + 1);
15921
15922     return StrSave(buf);
15923 }
15924
15925 Boolean
15926 ParseFEN(board, blackPlaysFirst, fen)
15927     Board board;
15928      int *blackPlaysFirst;
15929      char *fen;
15930 {
15931     int i, j;
15932     char *p, c;
15933     int emptycount;
15934     ChessSquare piece;
15935
15936     p = fen;
15937
15938     /* [HGM] by default clear Crazyhouse holdings, if present */
15939     if(gameInfo.holdingsWidth) {
15940        for(i=0; i<BOARD_HEIGHT; i++) {
15941            board[i][0]             = EmptySquare; /* black holdings */
15942            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15943            board[i][1]             = (ChessSquare) 0; /* black counts */
15944            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15945        }
15946     }
15947
15948     /* Piece placement data */
15949     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15950         j = 0;
15951         for (;;) {
15952             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15953                 if (*p == '/') p++;
15954                 emptycount = gameInfo.boardWidth - j;
15955                 while (emptycount--)
15956                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15957                 break;
15958 #if(BOARD_FILES >= 10)
15959             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15960                 p++; emptycount=10;
15961                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15962                 while (emptycount--)
15963                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15964 #endif
15965             } else if (isdigit(*p)) {
15966                 emptycount = *p++ - '0';
15967                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15968                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15969                 while (emptycount--)
15970                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15971             } else if (*p == '+' || isalpha(*p)) {
15972                 if (j >= gameInfo.boardWidth) return FALSE;
15973                 if(*p=='+') {
15974                     piece = CharToPiece(*++p);
15975                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15976                     piece = (ChessSquare) (PROMOTED piece ); p++;
15977                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15978                 } else piece = CharToPiece(*p++);
15979
15980                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15981                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15982                     piece = (ChessSquare) (PROMOTED piece);
15983                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15984                     p++;
15985                 }
15986                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15987             } else {
15988                 return FALSE;
15989             }
15990         }
15991     }
15992     while (*p == '/' || *p == ' ') p++;
15993
15994     /* [HGM] look for Crazyhouse holdings here */
15995     while(*p==' ') p++;
15996     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15997         if(*p == '[') p++;
15998         if(*p == '-' ) p++; /* empty holdings */ else {
15999             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16000             /* if we would allow FEN reading to set board size, we would   */
16001             /* have to add holdings and shift the board read so far here   */
16002             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16003                 p++;
16004                 if((int) piece >= (int) BlackPawn ) {
16005                     i = (int)piece - (int)BlackPawn;
16006                     i = PieceToNumber((ChessSquare)i);
16007                     if( i >= gameInfo.holdingsSize ) return FALSE;
16008                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16009                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
16010                 } else {
16011                     i = (int)piece - (int)WhitePawn;
16012                     i = PieceToNumber((ChessSquare)i);
16013                     if( i >= gameInfo.holdingsSize ) return FALSE;
16014                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
16015                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
16016                 }
16017             }
16018         }
16019         if(*p == ']') p++;
16020     }
16021
16022     while(*p == ' ') p++;
16023
16024     /* Active color */
16025     c = *p++;
16026     if(appData.colorNickNames) {
16027       if( c == appData.colorNickNames[0] ) c = 'w'; else
16028       if( c == appData.colorNickNames[1] ) c = 'b';
16029     }
16030     switch (c) {
16031       case 'w':
16032         *blackPlaysFirst = FALSE;
16033         break;
16034       case 'b':
16035         *blackPlaysFirst = TRUE;
16036         break;
16037       default:
16038         return FALSE;
16039     }
16040
16041     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16042     /* return the extra info in global variiables             */
16043
16044     /* set defaults in case FEN is incomplete */
16045     board[EP_STATUS] = EP_UNKNOWN;
16046     for(i=0; i<nrCastlingRights; i++ ) {
16047         board[CASTLING][i] =
16048             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16049     }   /* assume possible unless obviously impossible */
16050     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16051     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16052     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16053                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16054     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16055     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16056     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16057                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16058     FENrulePlies = 0;
16059
16060     while(*p==' ') p++;
16061     if(nrCastlingRights) {
16062       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16063           /* castling indicator present, so default becomes no castlings */
16064           for(i=0; i<nrCastlingRights; i++ ) {
16065                  board[CASTLING][i] = NoRights;
16066           }
16067       }
16068       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16069              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16070              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16071              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
16072         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16073
16074         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16075             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16076             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
16077         }
16078         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16079             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16080         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16081                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16082         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16083                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16084         switch(c) {
16085           case'K':
16086               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16087               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16088               board[CASTLING][2] = whiteKingFile;
16089               break;
16090           case'Q':
16091               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16092               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16093               board[CASTLING][2] = whiteKingFile;
16094               break;
16095           case'k':
16096               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16097               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16098               board[CASTLING][5] = blackKingFile;
16099               break;
16100           case'q':
16101               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16102               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16103               board[CASTLING][5] = blackKingFile;
16104           case '-':
16105               break;
16106           default: /* FRC castlings */
16107               if(c >= 'a') { /* black rights */
16108                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16109                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16110                   if(i == BOARD_RGHT) break;
16111                   board[CASTLING][5] = i;
16112                   c -= AAA;
16113                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
16114                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
16115                   if(c > i)
16116                       board[CASTLING][3] = c;
16117                   else
16118                       board[CASTLING][4] = c;
16119               } else { /* white rights */
16120                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16121                     if(board[0][i] == WhiteKing) break;
16122                   if(i == BOARD_RGHT) break;
16123                   board[CASTLING][2] = i;
16124                   c -= AAA - 'a' + 'A';
16125                   if(board[0][c] >= WhiteKing) break;
16126                   if(c > i)
16127                       board[CASTLING][0] = c;
16128                   else
16129                       board[CASTLING][1] = c;
16130               }
16131         }
16132       }
16133       for(i=0; i<nrCastlingRights; i++)
16134         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16135     if (appData.debugMode) {
16136         fprintf(debugFP, "FEN castling rights:");
16137         for(i=0; i<nrCastlingRights; i++)
16138         fprintf(debugFP, " %d", board[CASTLING][i]);
16139         fprintf(debugFP, "\n");
16140     }
16141
16142       while(*p==' ') p++;
16143     }
16144
16145     /* read e.p. field in games that know e.p. capture */
16146     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
16147        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16148       if(*p=='-') {
16149         p++; board[EP_STATUS] = EP_NONE;
16150       } else {
16151          char c = *p++ - AAA;
16152
16153          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16154          if(*p >= '0' && *p <='9') p++;
16155          board[EP_STATUS] = c;
16156       }
16157     }
16158
16159
16160     if(sscanf(p, "%d", &i) == 1) {
16161         FENrulePlies = i; /* 50-move ply counter */
16162         /* (The move number is still ignored)    */
16163     }
16164
16165     return TRUE;
16166 }
16167
16168 void
16169 EditPositionPasteFEN(char *fen)
16170 {
16171   if (fen != NULL) {
16172     Board initial_position;
16173
16174     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16175       DisplayError(_("Bad FEN position in clipboard"), 0);
16176       return ;
16177     } else {
16178       int savedBlackPlaysFirst = blackPlaysFirst;
16179       EditPositionEvent();
16180       blackPlaysFirst = savedBlackPlaysFirst;
16181       CopyBoard(boards[0], initial_position);
16182       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16183       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16184       DisplayBothClocks();
16185       DrawPosition(FALSE, boards[currentMove]);
16186     }
16187   }
16188 }
16189
16190 static char cseq[12] = "\\   ";
16191
16192 Boolean set_cont_sequence(char *new_seq)
16193 {
16194     int len;
16195     Boolean ret;
16196
16197     // handle bad attempts to set the sequence
16198         if (!new_seq)
16199                 return 0; // acceptable error - no debug
16200
16201     len = strlen(new_seq);
16202     ret = (len > 0) && (len < sizeof(cseq));
16203     if (ret)
16204       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16205     else if (appData.debugMode)
16206       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16207     return ret;
16208 }
16209
16210 /*
16211     reformat a source message so words don't cross the width boundary.  internal
16212     newlines are not removed.  returns the wrapped size (no null character unless
16213     included in source message).  If dest is NULL, only calculate the size required
16214     for the dest buffer.  lp argument indicats line position upon entry, and it's
16215     passed back upon exit.
16216 */
16217 int wrap(char *dest, char *src, int count, int width, int *lp)
16218 {
16219     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16220
16221     cseq_len = strlen(cseq);
16222     old_line = line = *lp;
16223     ansi = len = clen = 0;
16224
16225     for (i=0; i < count; i++)
16226     {
16227         if (src[i] == '\033')
16228             ansi = 1;
16229
16230         // if we hit the width, back up
16231         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16232         {
16233             // store i & len in case the word is too long
16234             old_i = i, old_len = len;
16235
16236             // find the end of the last word
16237             while (i && src[i] != ' ' && src[i] != '\n')
16238             {
16239                 i--;
16240                 len--;
16241             }
16242
16243             // word too long?  restore i & len before splitting it
16244             if ((old_i-i+clen) >= width)
16245             {
16246                 i = old_i;
16247                 len = old_len;
16248             }
16249
16250             // extra space?
16251             if (i && src[i-1] == ' ')
16252                 len--;
16253
16254             if (src[i] != ' ' && src[i] != '\n')
16255             {
16256                 i--;
16257                 if (len)
16258                     len--;
16259             }
16260
16261             // now append the newline and continuation sequence
16262             if (dest)
16263                 dest[len] = '\n';
16264             len++;
16265             if (dest)
16266                 strncpy(dest+len, cseq, cseq_len);
16267             len += cseq_len;
16268             line = cseq_len;
16269             clen = cseq_len;
16270             continue;
16271         }
16272
16273         if (dest)
16274             dest[len] = src[i];
16275         len++;
16276         if (!ansi)
16277             line++;
16278         if (src[i] == '\n')
16279             line = 0;
16280         if (src[i] == 'm')
16281             ansi = 0;
16282     }
16283     if (dest && appData.debugMode)
16284     {
16285         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16286             count, width, line, len, *lp);
16287         show_bytes(debugFP, src, count);
16288         fprintf(debugFP, "\ndest: ");
16289         show_bytes(debugFP, dest, len);
16290         fprintf(debugFP, "\n");
16291     }
16292     *lp = dest ? line : old_line;
16293
16294     return len;
16295 }
16296
16297 // [HGM] vari: routines for shelving variations
16298
16299 void
16300 PushInner(int firstMove, int lastMove)
16301 {
16302         int i, j, nrMoves = lastMove - firstMove;
16303
16304         // push current tail of game on stack
16305         savedResult[storedGames] = gameInfo.result;
16306         savedDetails[storedGames] = gameInfo.resultDetails;
16307         gameInfo.resultDetails = NULL;
16308         savedFirst[storedGames] = firstMove;
16309         savedLast [storedGames] = lastMove;
16310         savedFramePtr[storedGames] = framePtr;
16311         framePtr -= nrMoves; // reserve space for the boards
16312         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16313             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16314             for(j=0; j<MOVE_LEN; j++)
16315                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16316             for(j=0; j<2*MOVE_LEN; j++)
16317                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16318             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16319             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16320             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16321             pvInfoList[firstMove+i-1].depth = 0;
16322             commentList[framePtr+i] = commentList[firstMove+i];
16323             commentList[firstMove+i] = NULL;
16324         }
16325
16326         storedGames++;
16327         forwardMostMove = firstMove; // truncate game so we can start variation
16328 }
16329
16330 void
16331 PushTail(int firstMove, int lastMove)
16332 {
16333         if(appData.icsActive) { // only in local mode
16334                 forwardMostMove = currentMove; // mimic old ICS behavior
16335                 return;
16336         }
16337         if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16338
16339         PushInner(firstMove, lastMove);
16340         if(storedGames == 1) GreyRevert(FALSE);
16341 }
16342
16343 void
16344 PopInner(Boolean annotate)
16345 {
16346         int i, j, nrMoves;
16347         char buf[8000], moveBuf[20];
16348
16349         storedGames--;
16350         ToNrEvent(savedFirst[storedGames]); // sets currentMove
16351         nrMoves = savedLast[storedGames] - currentMove;
16352         if(annotate) {
16353                 int cnt = 10;
16354                 if(!WhiteOnMove(currentMove))
16355                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16356                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16357                 for(i=currentMove; i<forwardMostMove; i++) {
16358                         if(WhiteOnMove(i))
16359                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16360                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16361                         strcat(buf, moveBuf);
16362                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16363                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16364                 }
16365                 strcat(buf, ")");
16366         }
16367         for(i=1; i<=nrMoves; i++) { // copy last variation back
16368             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16369             for(j=0; j<MOVE_LEN; j++)
16370                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16371             for(j=0; j<2*MOVE_LEN; j++)
16372                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16373             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16374             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16375             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16376             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16377             commentList[currentMove+i] = commentList[framePtr+i];
16378             commentList[framePtr+i] = NULL;
16379         }
16380         if(annotate) AppendComment(currentMove+1, buf, FALSE);
16381         framePtr = savedFramePtr[storedGames];
16382         gameInfo.result = savedResult[storedGames];
16383         if(gameInfo.resultDetails != NULL) {
16384             free(gameInfo.resultDetails);
16385       }
16386         gameInfo.resultDetails = savedDetails[storedGames];
16387         forwardMostMove = currentMove + nrMoves;
16388 }
16389
16390 Boolean
16391 PopTail(Boolean annotate)
16392 {
16393         if(appData.icsActive) return FALSE; // only in local mode
16394         if(!storedGames) return FALSE; // sanity
16395         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16396
16397         PopInner(annotate);
16398
16399         if(storedGames == 0) GreyRevert(TRUE);
16400         return TRUE;
16401 }
16402
16403 void
16404 CleanupTail()
16405 {       // remove all shelved variations
16406         int i;
16407         for(i=0; i<storedGames; i++) {
16408             if(savedDetails[i])
16409                 free(savedDetails[i]);
16410             savedDetails[i] = NULL;
16411         }
16412         for(i=framePtr; i<MAX_MOVES; i++) {
16413                 if(commentList[i]) free(commentList[i]);
16414                 commentList[i] = NULL;
16415         }
16416         framePtr = MAX_MOVES-1;
16417         storedGames = 0;
16418 }
16419
16420 void
16421 LoadVariation(int index, char *text)
16422 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16423         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16424         int level = 0, move;
16425
16426         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16427         // first find outermost bracketing variation
16428         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16429             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16430                 if(*p == '{') wait = '}'; else
16431                 if(*p == '[') wait = ']'; else
16432                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16433                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16434             }
16435             if(*p == wait) wait = NULLCHAR; // closing ]} found
16436             p++;
16437         }
16438         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16439         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16440         end[1] = NULLCHAR; // clip off comment beyond variation
16441         ToNrEvent(currentMove-1);
16442         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16443         // kludge: use ParsePV() to append variation to game
16444         move = currentMove;
16445         ParsePV(start, TRUE, TRUE);
16446         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16447         ClearPremoveHighlights();
16448         CommentPopDown();
16449         ToNrEvent(currentMove+1);
16450 }
16451